Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ea732ef3c | |||
| 80dc19d0c0 | |||
| 591ad72c13 | |||
| 44dd6161b7 | |||
| 3730b4d9ab | |||
| 9616795cf7 | |||
| 8718bc28fe | |||
| 7886ebae10 | |||
| f22887868f | |||
| 2e7c16fccd | |||
| b29db79928 | |||
| 8a369481d6 | |||
| 603ec56151 | |||
| 12cced51f1 | |||
| 439c50c5c4 | |||
| 87a3bcf9db | |||
| 8910345149 | |||
| af4e54a6b7 | |||
| 90a0132533 | |||
| fe425c3f57 | |||
| 8098d72bb2 | |||
| ebde1e3d9a | |||
| d9b4006aa1 | |||
| 0b78b5ade8 | |||
| 4750f06058 | |||
| 0cb8f7e240 | |||
| 49d7d77274 | |||
| d443d5b065 | |||
| 013a1d5f3a | |||
| 180c39709a | |||
| ad630cbb9c | |||
| 51ac15b375 | |||
| db1469f219 | |||
| 1665dec4e4 | |||
| 99e414efab | |||
| 7cb3815fb1 | |||
| 5c6bf5461d | |||
| 3076ce66be | |||
| d59471b6bd | |||
| 4ba5504e7a | |||
| 7db3f901fd | |||
| f4665aa7b5 | |||
| 6206ed2c51 | |||
| a06be5de68 | |||
| 0ea238f90c | |||
| 4bc038fc88 | |||
| ba24ab8a49 | |||
| b0e11e286b |
@@ -58,7 +58,7 @@ jobs:
|
||||
logdir: /tmp/log/
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: build-release-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
upload_src:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -17,11 +21,11 @@ jobs:
|
||||
build_tag: ${{ steps.get_tag.outputs.build_tag }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Source
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
@@ -29,7 +33,7 @@ jobs:
|
||||
fetch-tags: true
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: get tag and create release if weekly
|
||||
- name: Get tag and create release if weekly
|
||||
id: get_tag
|
||||
shell: bash -l {0}
|
||||
env:
|
||||
@@ -39,7 +43,10 @@ jobs:
|
||||
export BUILD_TAG="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
export BUILD_TAG=weekly-$(date "+%Y.%m.%d")
|
||||
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" -F .github/workflows/weekly-build-notes.md --prerelease || true
|
||||
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" \
|
||||
-F .github/workflows/weekly-build-notes.md \
|
||||
--prerelease || \
|
||||
gh release view ${BUILD_TAG} > /dev/null # fail if it doesn't exist
|
||||
fi
|
||||
echo "BUILD_TAG=${BUILD_TAG}" >> "$GITHUB_ENV"
|
||||
echo "build_tag=${BUILD_TAG}" >> "$GITHUB_OUTPUT"
|
||||
@@ -105,7 +112,7 @@ jobs:
|
||||
environment: weekly-build
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -118,7 +125,7 @@ jobs:
|
||||
remove-android: 'true' # (frees ~9 GB)
|
||||
remove-cached-tools: 'true' # (frees ~8.3 GB)
|
||||
|
||||
- name: Set Platform Environment Variables
|
||||
- name: Set platform environment variables
|
||||
shell: bash -l {0}
|
||||
env:
|
||||
OPERATING_SYSTEM: ${{ runner.os }}
|
||||
@@ -128,7 +135,7 @@ jobs:
|
||||
echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Checkout Source
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
@@ -142,7 +149,7 @@ jobs:
|
||||
cache: false
|
||||
|
||||
- name: Install the Apple certificate and provisioning profile
|
||||
id: get_cert
|
||||
id: macos_get_cert
|
||||
if: runner.os == 'macOS'
|
||||
env:
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
@@ -150,7 +157,6 @@ jobs:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
|
||||
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
|
||||
DEVELOPER_TEAM_ID: ${{ secrets.DEVELOPER_TEAM_ID }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then
|
||||
@@ -186,23 +192,81 @@ jobs:
|
||||
|
||||
xcrun notarytool store-credentials "FreeCAD" --keychain "$KEYCHAIN_PATH" --apple-id "${APPLE_ID}" --password "${APP_SPECIFIC_PASSWORD}" --team-id "${DEVELOPER_TEAM_ID}"
|
||||
|
||||
- name: Build and Release Packages
|
||||
- name: Setup .NET 10 SDK
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||
with:
|
||||
dotnet-version: '10.0.103'
|
||||
|
||||
- name: Install sign tool
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
# NOTE: The sign tool is ONLY available as a prerelease package, they never issue real releases.
|
||||
dotnet tool install --global --prerelease sign
|
||||
echo "$env:USERPROFILE\.dotnet\tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
dotnet --info
|
||||
sign --version
|
||||
|
||||
- name: Build packages
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
|
||||
SIGN_RELEASE: ${{ steps.get_cert.outputs.has_cert }}
|
||||
TARGET_PLATFORM: ${{ matrix.target }}
|
||||
MAKE_INSTALLER: "true"
|
||||
UPLOAD_RELEASE: "true"
|
||||
BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||
export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}"
|
||||
fi
|
||||
python3 package/scripts/write_version_info.py ../freecad_version.txt
|
||||
cd package/rattler-build
|
||||
pixi install
|
||||
|
||||
- name: Azure login for Windows build code signing
|
||||
id: azure_login
|
||||
if: runner.os == 'Windows'
|
||||
continue-on-error: true
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}'
|
||||
|
||||
- name: Release packages with optional code-signing on Windows and macOS
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_PLATFORM: ${{ matrix.target }}
|
||||
MAKE_INSTALLER: "true"
|
||||
UPLOAD_RELEASE: "true"
|
||||
BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
export MACOS_SIGN_RELEASE=0
|
||||
export WINDOWS_SIGN_RELEASE=0
|
||||
|
||||
case "${RUNNER_OS}" in
|
||||
Windows)
|
||||
export WINDOWS_SIGN_RELEASE="${{ steps.azure_login.outcome }}"
|
||||
export WINDOWS_AZURE_ENDPOINT="${{ vars.AZURE_TRUSTED_SIGNING_ENDPOINT }}"
|
||||
export WINDOWS_AZURE_CERTIFICATE_PROFILE="${{ vars.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}"
|
||||
export WINDOWS_AZURE_SIGNING_ACCOUNT="${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT }}"
|
||||
|
||||
# For good measure, normalize the azure_login result to 1 or 0
|
||||
if [[ "${WINDOWS_SIGN_RELEASE:-}" == "success" ]]; then
|
||||
export WINDOWS_SIGN_RELEASE=1
|
||||
else
|
||||
export WINDOWS_SIGN_RELEASE=0
|
||||
fi
|
||||
;;
|
||||
|
||||
macOS)
|
||||
export MACOS_SIGN_RELEASE="${{ steps.macos_get_cert.outputs.has_cert }}"
|
||||
export MACOS_SIGNING_KEY_ID="${{ secrets.SIGNING_KEY_ID }}"
|
||||
export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}"
|
||||
;;
|
||||
esac
|
||||
|
||||
cd package/rattler-build
|
||||
pixi run -e package create_bundle
|
||||
|
||||
## Needed if running on a self-hosted runner:
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4
|
||||
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
cat report.md >> $GITHUB_STEP_SUMMARY
|
||||
- name: Delete used artifacts
|
||||
continue-on-error: true
|
||||
uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0
|
||||
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
|
||||
with:
|
||||
name: |
|
||||
${{ env.usedArtifacts }}
|
||||
|
||||
@@ -76,9 +76,9 @@ if [ -d "${conda_env}/PlugIns" ]; then
|
||||
mv ${conda_env}/PlugIns ${conda_env}/..
|
||||
fi
|
||||
|
||||
if [[ "${SIGN_RELEASE}" == "true" ]]; then
|
||||
if [[ "${MACOS_SIGN_RELEASE}" == "true" ]]; then
|
||||
# create the signed dmg
|
||||
../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${SIGNING_KEY_ID} -o "${version_name}.dmg"
|
||||
../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${MACOS_SIGNING_KEY_ID} -o "${version_name}.dmg"
|
||||
else
|
||||
# Ad-hoc sign for local builds (required for QuickLook extensions to register)
|
||||
if [ -d "FreeCAD.app/Contents/PlugIns" ]; then
|
||||
|
||||
@@ -100,7 +100,7 @@ requirements:
|
||||
|
||||
host:
|
||||
- coin3d
|
||||
- eigen
|
||||
- eigen >=3.3,<5
|
||||
- fmt
|
||||
- freetype
|
||||
- hdf5
|
||||
|
||||
@@ -44,6 +44,9 @@ find ${copy_dir} -name \*arm\*.exe -delete # arm binaries that fail to extract u
|
||||
mv ${copy_dir}/bin/Lib/ssl.py .ssl-orig.py
|
||||
cp ssl-patch.py ${copy_dir}/bin/Lib/ssl.py
|
||||
|
||||
# Turn off the echo before we start actually calling "echo"
|
||||
set +x
|
||||
|
||||
echo '[Paths]' >> ${copy_dir}/bin/qt6.conf
|
||||
echo 'Prefix = ../lib/qt6' >> ${copy_dir}/bin/qt6.conf
|
||||
|
||||
@@ -67,6 +70,64 @@ sed -i '1s/.*/\nLIST OF PACKAGES:/' ${copy_dir}/packages.txt
|
||||
|
||||
mv ${copy_dir} ${version_name}
|
||||
|
||||
|
||||
# Sign the EXE, DLL, and PYD files (if we can access the Azure account for signing):
|
||||
set -euo pipefail
|
||||
SIGN_DIR="${version_name}"
|
||||
|
||||
|
||||
if [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]]; then
|
||||
TENANT="$(az account show --query tenantId -o tsv)"
|
||||
export AZURE_IDENTITY_DISABLE_WORKLOAD_IDENTITY=true
|
||||
export AZURE_IDENTITY_DISABLE_MANAGED_IDENTITY=true
|
||||
unset AZURE_IDENTITY_LOGGING_ENABLED
|
||||
|
||||
if az account get-access-token \
|
||||
--tenant "$TENANT" \
|
||||
--scope "https://codesigning.azure.net/.default" \
|
||||
>/dev/null 2>&1;
|
||||
then
|
||||
echo "Azure Artifact Signing access confirmed. Beginning signing process..."
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
FILES=(
|
||||
"$SIGN_DIR"/*.exe
|
||||
"$SIGN_DIR"/bin/*.exe
|
||||
"$SIGN_DIR"/bin/*.dll
|
||||
"$SIGN_DIR"/bin/*.pyd
|
||||
)
|
||||
|
||||
count=0
|
||||
total=${#FILES[@]}
|
||||
echo "Signing $total files"
|
||||
for f in "${FILES[@]}"; do
|
||||
((count+=1))
|
||||
echo "Signing [$count/$total]: $f"
|
||||
sign code artifact-signing \
|
||||
--artifact-signing-endpoint "${WINDOWS_AZURE_ENDPOINT}" \
|
||||
--artifact-signing-certificate-profile "${WINDOWS_AZURE_CERTIFICATE_PROFILE}" \
|
||||
--artifact-signing-account "${WINDOWS_AZURE_SIGNING_ACCOUNT}" \
|
||||
--timestamp-url https://timestamp.acs.microsoft.com \
|
||||
--timestamp-digest sha256 \
|
||||
"$f" >/dev/null 2>&1
|
||||
|
||||
# Output was redirected to /dev/null because Azure authentication is absurdly noisy, with constant misleading
|
||||
# "failure" messages about Managed Identity authentication failing. We don't use, or want to use, that
|
||||
# authentication, and the fact that it fails is not a problem as long as the real authentication succeeds.
|
||||
done
|
||||
|
||||
# Manually check the important one!
|
||||
signtool verify -pa "$SIGN_DIR/bin/FreeCAD.exe"
|
||||
|
||||
echo "Signing completed."
|
||||
else
|
||||
echo "Signing requested, but no Azure Artifact Signing available -- skipping signing."
|
||||
fi
|
||||
else
|
||||
echo "Not logged into Azure -- skipping signing."
|
||||
fi
|
||||
|
||||
7z a -t7z -mx9 -mmt=${NUMBER_OF_PROCESSORS} ${version_name}.7z ${version_name} -bb
|
||||
# create hash
|
||||
sha256sum ${version_name}.7z > ${version_name}.7z-SHA256.txt
|
||||
@@ -88,6 +149,28 @@ if [ "${MAKE_INSTALLER}" == "true" ]; then
|
||||
-X'SetCompressor /FINAL lzma' \
|
||||
../../WindowsInstaller/FreeCAD-installer.nsi
|
||||
mv ../../WindowsInstaller/${version_name}-installer.exe .
|
||||
echo "Created installer ${version_name}-installer.exe"
|
||||
|
||||
# See if we can sign the installer exe as well:
|
||||
if [[ "${WINDOWS_SIGN_RELEASE:-0}" == "1" ]] && \
|
||||
az account get-access-token \
|
||||
--tenant "$TENANT" \
|
||||
--scope "https://codesigning.azure.net/.default" \
|
||||
>/dev/null 2>&1;
|
||||
then
|
||||
echo "Signing the installer..."
|
||||
sign code artifact-signing \
|
||||
--artifact-signing-endpoint "${WINDOWS_AZURE_ENDPOINT}" \
|
||||
--artifact-signing-certificate-profile "${WINDOWS_AZURE_CERTIFICATE_PROFILE}" \
|
||||
--artifact-signing-account "${WINDOWS_AZURE_SIGNING_ACCOUNT}" \
|
||||
--timestamp-url https://timestamp.acs.microsoft.com \
|
||||
--timestamp-digest sha256 \
|
||||
${version_name}-installer.exe >/dev/null 2>&1 \
|
||||
|| { echo "Signing the installer failed!"; exit 1; }
|
||||
else
|
||||
echo "No code signing available, leaving the installer unsigned"
|
||||
fi
|
||||
|
||||
sha256sum ${version_name}-installer.exe > ${version_name}-installer.exe-SHA256.txt
|
||||
else
|
||||
echo "Error: Failed to get NsProcess plugin. Aborting installer creation..."
|
||||
@@ -96,8 +179,10 @@ if [ "${MAKE_INSTALLER}" == "true" ]; then
|
||||
fi
|
||||
|
||||
if [ "${UPLOAD_RELEASE}" == "true" ]; then
|
||||
echo "Uploading the release..."
|
||||
gh release upload --clobber ${BUILD_TAG} "${version_name}.7z" "${version_name}.7z-SHA256.txt"
|
||||
if [ "${MAKE_INSTALLER}" == "true" ]; then
|
||||
gh release upload --clobber ${BUILD_TAG} "${version_name}-installer.exe" "${version_name}-installer.exe-SHA256.txt"
|
||||
fi
|
||||
echo "Done uploading"
|
||||
fi
|
||||
|
||||
@@ -19,11 +19,12 @@ debugpy = "*"
|
||||
defusedxml = "*"
|
||||
docutils = "*"
|
||||
doxygen = "*"
|
||||
eigen = "*"
|
||||
eigen = ">=3.3,<5"
|
||||
fmt = "*"
|
||||
freetype = "*"
|
||||
git = "*"
|
||||
graphviz = "*"
|
||||
harfbuzz = "*"
|
||||
hdf5 = "*"
|
||||
ifcopenshell = "*"
|
||||
lark = "*"
|
||||
|
||||
@@ -587,6 +587,15 @@ Document* Application::getDocument(const char *Name) const
|
||||
|
||||
return pos->second;
|
||||
}
|
||||
Document* Application::getDocumentOrActive(const char *Name) const
|
||||
{
|
||||
if (!Base::Tools::isNullOrEmpty(Name)) {
|
||||
return getDocument(Name);
|
||||
}
|
||||
else {
|
||||
return getActiveDocument();
|
||||
}
|
||||
}
|
||||
|
||||
const char * Application::getDocumentName(const Document* doc) const
|
||||
{
|
||||
|
||||
+48
-27
@@ -35,10 +35,12 @@
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include <Base/Observer.h>
|
||||
#include <Base/Parameter.h>
|
||||
#include <Base/ProgressIndicator.h>
|
||||
#include "TransactionDefs.h"
|
||||
|
||||
// forward declarations
|
||||
using PyObject = struct _object;
|
||||
@@ -203,6 +205,8 @@ public:
|
||||
*/
|
||||
App::Document* getDocument(const char *Name) const;
|
||||
|
||||
App::Document* getDocumentOrActive(const char *Name) const;
|
||||
|
||||
/// %Path matching modes for getDocumentByPath()
|
||||
enum class PathMatchMode
|
||||
{
|
||||
@@ -289,9 +293,11 @@ public:
|
||||
/**
|
||||
* @brief Setup a pending application-wide active transaction.
|
||||
*
|
||||
* Call this function to setup an application-wide transaction. All current
|
||||
* pending transactions of opening documents will be committed first.
|
||||
* However, no new transaction is created by this call. Any subsequent
|
||||
* Call this function to setup a transaction in the currently active document
|
||||
* if no document is active, a global transaction is created. If the current
|
||||
* active document already has a transaction setup it will either commit the
|
||||
* current transaction or rename it, depending on the tmpName flag of the
|
||||
* currently setup transaction. No new transaction is created by this call. Any subsequent
|
||||
* changes in any current opening document will auto create a transaction
|
||||
* with the given name and ID. If more than one document is changed, the
|
||||
* transactions will share the same ID, and will be undo/redo together.
|
||||
@@ -303,32 +309,41 @@ public:
|
||||
*
|
||||
* @return The new transaction ID.
|
||||
*/
|
||||
int setActiveTransaction(const char* name, bool persist = false);
|
||||
|
||||
/**
|
||||
* @brief Get the current active transaction name and ID.
|
||||
int setActiveTransaction(TransactionName name);
|
||||
|
||||
int openGlobalTransaction(TransactionName name);
|
||||
int getGlobalTransaction() const;
|
||||
|
||||
bool transactionIsActive(int tid) const;
|
||||
std::string getTransactionName(int tid) const;
|
||||
bool transactionTmpName(int tid) const;
|
||||
Document* transactionInitiator(int tid) const;
|
||||
std::optional<TransactionDescription> transactionDescription(int tid) const;
|
||||
void setTransactionDescription(int tid, const TransactionDescription& desc);
|
||||
void setTransactionName(int tid, const TransactionName& name);
|
||||
|
||||
/** Commit/abort current active transactions
|
||||
*
|
||||
* If there is no active transaction, an empty string is returned.
|
||||
*
|
||||
* @param[out] tid If not `nullptr`, the current active transaction ID is
|
||||
* returned through this pointer.
|
||||
* @return The current active transaction name.
|
||||
*/
|
||||
const char* getActiveTransaction(int* tid = nullptr) const;
|
||||
|
||||
/**
|
||||
* @brief Commit/abort current active transactions.
|
||||
*
|
||||
* @param[in] abort: whether to abort or commit the transactions
|
||||
* @param[in] id: by default 0 meaning that the current active transaction ID is used.
|
||||
*
|
||||
* Besides calling this function directly, it will be called by
|
||||
* automatically if 1) any new transaction is created with a different ID,
|
||||
* or 2) any transaction with the current active transaction ID is either
|
||||
* committed or aborted.
|
||||
* @param[in] id: by default 0 meaning that the current global transaction ID is used.
|
||||
*
|
||||
* Bsides calling this function directly, it will be called by automatically
|
||||
* if 1) any new transaction is created with a different ID, or 2) any
|
||||
* transaction with the current active transaction ID is either committed or
|
||||
* aborted
|
||||
* returns true if it succeeded in closing the transaction
|
||||
*/
|
||||
void closeActiveTransaction(bool abort=false, int id=0);
|
||||
/// @}
|
||||
bool closeActiveTransaction(TransactionCloseMode mode = TransactionCloseMode::Commit, int id=0);
|
||||
|
||||
/// Internally call closeActiveTransaction(), but it makes the call site clearer
|
||||
bool commitTransaction(int tid);
|
||||
bool abortTransaction(int tid);
|
||||
|
||||
|
||||
//@}
|
||||
|
||||
// NOLINTBEGIN
|
||||
// clang-format off
|
||||
@@ -1040,10 +1055,16 @@ private:
|
||||
|
||||
friend class AutoTransaction;
|
||||
|
||||
std::string _activeTransactionName;
|
||||
int _activeTransactionID{0};
|
||||
int _activeTransactionGuard{0};
|
||||
bool _activeTransactionTmpName{false};
|
||||
std::map<int, TransactionDescription> _activeTransactionDescriptions; // Maps transaction ID to transaction name
|
||||
|
||||
int currentlyClosingID {0};
|
||||
|
||||
// This is the transaction ID for a global transaction
|
||||
// Documents will take this ID if it is non-zero
|
||||
// and generate their own otherwise
|
||||
int _globalTransactionID { 0 };
|
||||
bool _globalTransactionTmpName {false};
|
||||
std::string _globalTransactionName;
|
||||
|
||||
Base::ProgressIndicator _progressIndicator;
|
||||
|
||||
|
||||
@@ -242,9 +242,7 @@ PyMethodDef ApplicationPy::Methods[] = {
|
||||
METH_VARARGS,
|
||||
"setActiveTransaction(name, persist=False) -- setup active transaction with the given name\n\n"
|
||||
"name: the transaction name\n"
|
||||
"persist(False): by default, if the calling code is inside any invocation of a command, it\n"
|
||||
" will be auto closed once all commands within the current stack exists. To\n"
|
||||
" disable auto closing, set persist=True\n"
|
||||
"persist(False): This parameter has no effect and is kept for compatibility reasonss"
|
||||
"Returns the transaction ID for the active transaction. An application-wide\n"
|
||||
"active transaction causes any document changes to open a transaction with\n"
|
||||
"the given name and ID."},
|
||||
@@ -1201,14 +1199,14 @@ PyObject* ApplicationPy::sGetDependentObjects(PyObject* /*self*/, PyObject* args
|
||||
PyObject* ApplicationPy::sSetActiveTransaction(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
char* name {};
|
||||
PyObject* persist = Py_False;
|
||||
PyObject* persist = Py_False; // Not used
|
||||
if (!PyArg_ParseTuple(args, "s|O!", &name, &PyBool_Type, &persist)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PY_TRY
|
||||
{
|
||||
Py::Long ret(GetApplication().setActiveTransaction(name, Base::asBoolean(persist)));
|
||||
Py::Long ret(GetApplication().setActiveTransaction(TransactionName {.name=name, .temporary=false}));
|
||||
return Py::new_reference_to(ret);
|
||||
}
|
||||
PY_CATCH;
|
||||
@@ -1223,9 +1221,10 @@ PyObject* ApplicationPy::sGetActiveTransaction(PyObject* /*self*/, PyObject* arg
|
||||
PY_TRY
|
||||
{
|
||||
int id = 0;
|
||||
const char* name = GetApplication().getActiveTransaction(&id);
|
||||
if (!name || id <= 0) {
|
||||
Py_Return;
|
||||
std::string name = "";
|
||||
if (Document* doc = GetApplication().getActiveDocument()) {
|
||||
id = doc->getBookedTransactionID();
|
||||
name = GetApplication().getTransactionName(id);
|
||||
}
|
||||
Py::Tuple ret(2);
|
||||
ret.setItem(0, Py::String(name));
|
||||
@@ -1245,7 +1244,10 @@ PyObject* ApplicationPy::sCloseActiveTransaction(PyObject* /*self*/, PyObject* a
|
||||
|
||||
PY_TRY
|
||||
{
|
||||
GetApplication().closeActiveTransaction(Base::asBoolean(abort), id);
|
||||
TransactionCloseMode mode = Base::asBoolean(abort)
|
||||
? TransactionCloseMode::Abort
|
||||
: TransactionCloseMode::Commit;
|
||||
GetApplication().closeActiveTransaction(mode, id);
|
||||
Py_Return;
|
||||
}
|
||||
PY_CATCH;
|
||||
|
||||
+160
-167
@@ -35,204 +35,210 @@ FC_LOG_LEVEL_INIT("App", true, true)
|
||||
|
||||
using namespace App;
|
||||
|
||||
static int _TransactionLock;
|
||||
static int _TransactionClosed;
|
||||
|
||||
AutoTransaction::AutoTransaction(const char* name, bool tmpName)
|
||||
AutoTransaction::AutoTransaction(int tid)
|
||||
: tid(tid)
|
||||
{
|
||||
auto& app = GetApplication();
|
||||
if (name && app._activeTransactionGuard >= 0) {
|
||||
if (!app.getActiveTransaction() || (!tmpName && app._activeTransactionTmpName)) {
|
||||
FC_LOG("auto transaction '" << name << "', " << tmpName);
|
||||
tid = app.setActiveTransaction(name);
|
||||
app._activeTransactionTmpName = tmpName;
|
||||
}
|
||||
}
|
||||
// We use negative transaction guard to disable auto transaction from here
|
||||
// and any stack below. This is to support user setting active transaction
|
||||
// before having any existing AutoTransaction on stack, or 'persist'
|
||||
// transaction that can out live AutoTransaction.
|
||||
if (app._activeTransactionGuard < 0) {
|
||||
--app._activeTransactionGuard;
|
||||
}
|
||||
else if (tid || app._activeTransactionGuard > 0) {
|
||||
++app._activeTransactionGuard;
|
||||
}
|
||||
else if (app.getActiveTransaction()) {
|
||||
FC_LOG("auto transaction disabled because of '" << app._activeTransactionName << "'");
|
||||
--app._activeTransactionGuard;
|
||||
}
|
||||
else {
|
||||
++app._activeTransactionGuard;
|
||||
}
|
||||
FC_TRACE("construct auto Transaction " << app._activeTransactionGuard);
|
||||
|
||||
}
|
||||
AutoTransaction::AutoTransaction(Document* doc, const std::string& name)
|
||||
: AutoTransaction(doc->openTransaction(name))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AutoTransaction::~AutoTransaction()
|
||||
{
|
||||
auto& app = GetApplication();
|
||||
FC_TRACE("before destruct auto Transaction " << app._activeTransactionGuard);
|
||||
if (app._activeTransactionGuard < 0) {
|
||||
++app._activeTransactionGuard;
|
||||
}
|
||||
else if (!app._activeTransactionGuard) {
|
||||
#ifdef FC_DEBUG
|
||||
FC_ERR("Transaction guard error");
|
||||
#endif
|
||||
}
|
||||
else if (--app._activeTransactionGuard == 0) {
|
||||
try {
|
||||
// We don't call close() here, because close() only closes
|
||||
// transaction that we opened during construction time. However,
|
||||
// when _activeTransactionGuard reaches zero here, we are supposed
|
||||
// to close any transaction opened.
|
||||
app.closeActiveTransaction();
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
e.reportException();
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
FC_TRACE("destruct auto Transaction " << app._activeTransactionGuard);
|
||||
close(TransactionCloseMode::Commit);
|
||||
}
|
||||
|
||||
void AutoTransaction::close(bool abort)
|
||||
void AutoTransaction::close(TransactionCloseMode mode)
|
||||
{
|
||||
if (tid || abort) {
|
||||
GetApplication().closeActiveTransaction(abort, abort ? 0 : tid);
|
||||
if (tid != NullTransaction) {
|
||||
GetApplication().closeActiveTransaction(mode, tid);
|
||||
tid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AutoTransaction::setEnable(bool enable)
|
||||
int Application::setActiveTransaction(TransactionName name)
|
||||
{
|
||||
auto& app = GetApplication();
|
||||
if (!app._activeTransactionGuard) {
|
||||
return;
|
||||
if (name.name.empty()) {
|
||||
name.name = "Command";
|
||||
}
|
||||
if ((enable && app._activeTransactionGuard > 0)
|
||||
|| (!enable && app._activeTransactionGuard < 0)) {
|
||||
return;
|
||||
}
|
||||
app._activeTransactionGuard = -app._activeTransactionGuard;
|
||||
FC_TRACE("toggle auto Transaction " << app._activeTransactionGuard);
|
||||
if (!enable && app._activeTransactionTmpName) {
|
||||
bool close = true;
|
||||
for (auto& v : app.DocMap) {
|
||||
if (v.second->hasPendingTransaction()) {
|
||||
close = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (close) {
|
||||
app.closeActiveTransaction();
|
||||
}
|
||||
|
||||
if (_pActiveDoc != nullptr) {
|
||||
return _pActiveDoc->setActiveTransaction(name);
|
||||
}
|
||||
return openGlobalTransaction(name);
|
||||
}
|
||||
|
||||
int Application::setActiveTransaction(const char* name, bool persist)
|
||||
int Application::openGlobalTransaction(TransactionName name)
|
||||
{
|
||||
if (name.name.empty()) {
|
||||
name.name = "Command";
|
||||
}
|
||||
|
||||
if (!name || !name[0]) {
|
||||
name = "Command";
|
||||
}
|
||||
|
||||
this->signalBeforeOpenTransaction(name);
|
||||
if (_activeTransactionGuard > 0 && getActiveTransaction()) {
|
||||
if (_activeTransactionTmpName) {
|
||||
FC_LOG("transaction rename to '" << name << "'");
|
||||
for (auto& v : DocMap) {
|
||||
v.second->renameTransaction(name, _activeTransactionID);
|
||||
FC_WARN("Setting a global transaction with name='" << name.name);
|
||||
if (_globalTransactionID != 0 && transactionTmpName(_globalTransactionID)) {
|
||||
setTransactionName(_globalTransactionID, name);
|
||||
} else {
|
||||
FC_LOG("set global transaction '" << name.name << "'");
|
||||
|
||||
if (_globalTransactionID != 0 && !commitTransaction(_globalTransactionID)) {
|
||||
FC_WARN("could not close current global transaction");
|
||||
return _globalTransactionID;
|
||||
}
|
||||
|
||||
_globalTransactionID = Transaction::getNewID();
|
||||
setTransactionDescription(
|
||||
_globalTransactionID,
|
||||
TransactionDescription {
|
||||
.initiator = nullptr,
|
||||
.name = name
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (persist) {
|
||||
AutoTransaction::setEnable(false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (_TransactionLock) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
|
||||
}
|
||||
return 0;
|
||||
|
||||
return _globalTransactionID;
|
||||
}
|
||||
int Application::getGlobalTransaction() const
|
||||
{
|
||||
return _globalTransactionID;
|
||||
}
|
||||
bool Application::transactionIsActive(int tid) const
|
||||
{
|
||||
return transactionDescription(tid) != std::nullopt;
|
||||
}
|
||||
std::string Application::getTransactionName(int tid) const
|
||||
{
|
||||
auto desc = transactionDescription(tid);
|
||||
return desc ? desc->name.name : "";
|
||||
}
|
||||
bool Application::transactionTmpName(int tid) const
|
||||
{
|
||||
auto desc = transactionDescription(tid);
|
||||
return desc ? desc->name.temporary : false;
|
||||
}
|
||||
Document* Application::transactionInitiator(int tid) const
|
||||
{
|
||||
auto desc = transactionDescription(tid);
|
||||
return desc ? desc->initiator : nullptr;
|
||||
}
|
||||
std::optional<TransactionDescription> Application::transactionDescription(int tid) const
|
||||
{
|
||||
if (tid == NullTransaction) {
|
||||
return std::nullopt;
|
||||
}
|
||||
else {
|
||||
FC_LOG("set active transaction '" << name << "'");
|
||||
_activeTransactionID = 0;
|
||||
auto found = _activeTransactionDescriptions.find(tid);
|
||||
if (found != _activeTransactionDescriptions.end()) {
|
||||
return std::optional<TransactionDescription>(found->second);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
void Application::setTransactionDescription(int tid, const TransactionDescription& desc)
|
||||
{
|
||||
if (tid == NullTransaction) {
|
||||
return;
|
||||
}
|
||||
auto found = _activeTransactionDescriptions.find(tid);
|
||||
bool wasPresent = (found != _activeTransactionDescriptions.end());
|
||||
|
||||
if (wasPresent && found->second.name.temporary) {
|
||||
for (auto& v : DocMap) {
|
||||
v.second->_commitTransaction();
|
||||
v.second->renameTransaction(desc.name.name, tid);
|
||||
}
|
||||
_activeTransactionID = Transaction::getNewID();
|
||||
}
|
||||
_activeTransactionTmpName = false;
|
||||
_activeTransactionName = name;
|
||||
if (persist) {
|
||||
AutoTransaction::setEnable(false);
|
||||
|
||||
if (!wasPresent || found->second.name.temporary) {
|
||||
_activeTransactionDescriptions[tid] = desc;
|
||||
FC_LOG("transaction rename to '" << desc.name.name << "'");
|
||||
}
|
||||
return _activeTransactionID;
|
||||
}
|
||||
void Application::setTransactionName(int tid, const TransactionName& name)
|
||||
{
|
||||
if (tid == NullTransaction || !transactionIsActive(tid)) {
|
||||
return;
|
||||
}
|
||||
auto found = _activeTransactionDescriptions.find(tid);
|
||||
if (found == _activeTransactionDescriptions.end() || found->second.name.temporary) {
|
||||
_activeTransactionDescriptions[tid].name = name;
|
||||
FC_LOG("transaction rename to '" << name.name << "'");
|
||||
for (auto& v : DocMap) {
|
||||
v.second->renameTransaction(name.name, tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* Application::getActiveTransaction(int* id) const
|
||||
bool Application::closeActiveTransaction(TransactionCloseMode mode, int id)
|
||||
{
|
||||
int tid = 0;
|
||||
if (Transaction::getLastID() == _activeTransactionID) {
|
||||
tid = _activeTransactionID;
|
||||
}
|
||||
if (id) {
|
||||
*id = tid;
|
||||
}
|
||||
return tid ? _activeTransactionName.c_str() : nullptr;
|
||||
}
|
||||
|
||||
void Application::closeActiveTransaction(bool abort, int id)
|
||||
{
|
||||
if (!id) {
|
||||
id = _activeTransactionID;
|
||||
}
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeTransactionGuard > 0 && !abort) {
|
||||
FC_LOG("ignore close transaction");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_TransactionLock) {
|
||||
if (_TransactionClosed >= 0) {
|
||||
_TransactionLock = abort ? -1 : 1;
|
||||
bool abort = (mode == TransactionCloseMode::Abort);
|
||||
|
||||
if (id == NullTransaction) {
|
||||
if (_pActiveDoc != nullptr && _pActiveDoc->getBookedTransactionID() != NullTransaction) {
|
||||
id = _pActiveDoc->getBookedTransactionID();
|
||||
} else {
|
||||
id = _globalTransactionID;
|
||||
}
|
||||
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
|
||||
return;
|
||||
}
|
||||
if (id == NullTransaction || id == currentlyClosingID) {
|
||||
return false;
|
||||
}
|
||||
currentlyClosingID = id;
|
||||
|
||||
FC_LOG("close transaction '" << _activeTransactionName << "' " << abort);
|
||||
_activeTransactionID = 0;
|
||||
|
||||
TransactionSignaller signaller(abort, false);
|
||||
for (auto& v : DocMap) {
|
||||
if (v.second->getTransactionID(true) != id) {
|
||||
std::vector<Document*> docsToPoke;
|
||||
for (auto& docNameAndDoc : DocMap) {
|
||||
if (docNameAndDoc.second->getBookedTransactionID() != id) {
|
||||
continue;
|
||||
}
|
||||
if(docNameAndDoc.second->isTransactionLocked() || docNameAndDoc.second->transacting()) {
|
||||
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
|
||||
currentlyClosingID = 0;
|
||||
return false;
|
||||
}
|
||||
if(docNameAndDoc.second->transacting()) {
|
||||
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
|
||||
currentlyClosingID = 0;
|
||||
return false;
|
||||
}
|
||||
docsToPoke.push_back(docNameAndDoc.second);
|
||||
}
|
||||
|
||||
FC_LOG("close transaction '" << _activeTransactionDescriptions[id].name.name << "' " << abort);
|
||||
_activeTransactionDescriptions.erase(id);
|
||||
if (id == _globalTransactionID) {
|
||||
_globalTransactionID = 0;
|
||||
}
|
||||
|
||||
TransactionSignaller signaller(abort, false);
|
||||
|
||||
for (auto& doc : docsToPoke) {
|
||||
if (abort) {
|
||||
v.second->_abortTransaction();
|
||||
doc->_abortTransaction();
|
||||
}
|
||||
else {
|
||||
v.second->_commitTransaction();
|
||||
doc->_commitTransaction();
|
||||
}
|
||||
}
|
||||
currentlyClosingID = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool Application::commitTransaction(int tid)
|
||||
{
|
||||
return closeActiveTransaction(TransactionCloseMode::Commit, tid);
|
||||
}
|
||||
bool Application::abortTransaction(int tid)
|
||||
{
|
||||
return closeActiveTransaction(TransactionCloseMode::Abort, tid);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TransactionLocker::TransactionLocker(bool lock)
|
||||
TransactionLocker::TransactionLocker(Document* doc, bool lock)
|
||||
: active(lock)
|
||||
, doc(doc)
|
||||
{
|
||||
if (lock) {
|
||||
++_TransactionLock;
|
||||
doc->lockTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,22 +273,9 @@ void TransactionLocker::activate(bool enable)
|
||||
|
||||
active = enable;
|
||||
if (active) {
|
||||
++_TransactionLock;
|
||||
doc->lockTransaction();
|
||||
return;
|
||||
}
|
||||
|
||||
if (--_TransactionLock != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_TransactionClosed) {
|
||||
bool abort = (_TransactionClosed < 0);
|
||||
_TransactionClosed = 0;
|
||||
GetApplication().closeActiveTransaction(abort);
|
||||
}
|
||||
}
|
||||
|
||||
bool TransactionLocker::isLocked()
|
||||
{
|
||||
return _TransactionLock > 0;
|
||||
doc->unlockTransaction();
|
||||
}
|
||||
|
||||
+22
-48
@@ -26,11 +26,15 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <FCGlobal.h>
|
||||
#include <string>
|
||||
|
||||
#include "TransactionDefs.h"
|
||||
|
||||
namespace App
|
||||
{
|
||||
|
||||
class Application;
|
||||
class Document;
|
||||
|
||||
/**
|
||||
* @brief A helper class to manage transactions (i.e. undo/redo).
|
||||
@@ -45,59 +49,31 @@ public:
|
||||
void* operator new(std::size_t) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct an auto transaction.
|
||||
*
|
||||
* @param[in] name: optional new transaction name on construction
|
||||
* @param[in] tmpName: if true and a new transaction is setup, the name given is
|
||||
* considered as temporary, and subsequent construction of this class (or
|
||||
* calling Application::setActiveTransaction()) can override the transaction
|
||||
* name.
|
||||
*
|
||||
* The constructor increments an internal counter
|
||||
* (Application::_activeTransactionGuard). The counter prevents any new
|
||||
* active transactions being setup. It also prevents to close
|
||||
* (i.e. commits) the current active transaction until it reaches zero. It
|
||||
* does not have any effect on aborting transactions though.
|
||||
/** Constructor
|
||||
*
|
||||
* @param tid the ID of the transaction to manage
|
||||
*
|
||||
* No action is done in the constructor
|
||||
*/
|
||||
AutoTransaction(const char* name = nullptr, bool tmpName = false);
|
||||
|
||||
/**
|
||||
* @brief Destruct an auto transaction.
|
||||
*
|
||||
* This destructor decrease an internal counter
|
||||
* (Application::_activeTransactionGuard), and will commit any current
|
||||
* active transaction when the counter reaches zero.
|
||||
explicit AutoTransaction(int tid);
|
||||
AutoTransaction(Document* doc, const std::string& name);
|
||||
|
||||
/** Destructor
|
||||
*
|
||||
* This destructor attempts to commit the transaction it manages
|
||||
*/
|
||||
~AutoTransaction();
|
||||
|
||||
/**
|
||||
* @brief Close or abort the transaction.
|
||||
*
|
||||
* This function can be used to explicitly close (i.e. commit) the
|
||||
* transaction, if the current transaction ID matches the one created inside
|
||||
* the constructor. For aborting, it will abort any current transaction.
|
||||
*
|
||||
* @param[in] abort: if true, abort the transaction; otherwise, commit it.
|
||||
* This function can be used to explicitly close (i.e. commit / abort) the
|
||||
* transaction,
|
||||
*/
|
||||
void close(bool abort = false);
|
||||
|
||||
/**
|
||||
* @brief Enable/Disable any AutoTransaction instance on the current stack.
|
||||
*
|
||||
* Once disabled, any empty temporary named transaction is closed. If there
|
||||
* are non-empty or non-temporary named active transaction, it will not be
|
||||
* auto closed.
|
||||
*
|
||||
* This function may be used in, for example, Gui::Document::setEdit() to
|
||||
* allow a transaction live past any command scope.
|
||||
*
|
||||
* @param[in] enable: if true, enable the AutoTransaction; otherwise, disable it.
|
||||
*/
|
||||
static void setEnable(bool enable);
|
||||
void close(TransactionCloseMode mode = TransactionCloseMode::Commit);
|
||||
|
||||
private:
|
||||
int tid = 0;
|
||||
int tid { 0 };
|
||||
};
|
||||
|
||||
|
||||
@@ -116,7 +92,7 @@ public:
|
||||
*
|
||||
* @param[in] lock: whether to activate the lock
|
||||
*/
|
||||
TransactionLocker(bool lock = true);
|
||||
TransactionLocker(Document* doc, bool lock = true);
|
||||
|
||||
/**
|
||||
* @brief Destruct a transaction locker.
|
||||
@@ -142,10 +118,7 @@ public:
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
/// Check if transaction is being locked.
|
||||
static bool isLocked();
|
||||
|
||||
|
||||
friend class Application;
|
||||
|
||||
public:
|
||||
@@ -154,6 +127,7 @@ public:
|
||||
|
||||
private:
|
||||
bool active;
|
||||
Document* doc;
|
||||
};
|
||||
|
||||
} // namespace App
|
||||
+199
-101
@@ -35,6 +35,7 @@
|
||||
#include <list>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/bimap.hpp>
|
||||
@@ -190,6 +191,7 @@ bool Document::undo(const int id)
|
||||
mRedoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
||||
mRedoTransactions.push_back(d->activeUndoTransaction);
|
||||
d->activeUndoTransaction = nullptr;
|
||||
d->bookedTransaction = 0;
|
||||
|
||||
mUndoMap.erase(mUndoTransactions.back()->getID());
|
||||
delete mUndoTransactions.back();
|
||||
@@ -242,6 +244,7 @@ bool Document::redo(const int id)
|
||||
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
||||
mUndoTransactions.push_back(d->activeUndoTransaction);
|
||||
d->activeUndoTransaction = nullptr;
|
||||
d->bookedTransaction = 0;
|
||||
|
||||
mRedoMap.erase(mRedoTransactions.back()->getID());
|
||||
delete mRedoTransactions.back();
|
||||
@@ -271,10 +274,10 @@ void Document::changePropertyOfObject(TransactionalObject* obj,
|
||||
}
|
||||
if ((d->iUndoMode != 0) && !isPerformingTransaction() && !d->activeUndoTransaction) {
|
||||
if (!testStatus(Restoring) || testStatus(Importing)) {
|
||||
int tid = 0;
|
||||
const char* name = GetApplication().getActiveTransaction(&tid);
|
||||
if (name && tid > 0) {
|
||||
_openTransaction(name, tid);
|
||||
if (d->bookedTransaction == NullTransaction) {
|
||||
d->bookedTransaction = GetApplication().getGlobalTransaction();
|
||||
} else {
|
||||
_openTransaction(GetApplication().getTransactionName(d->bookedTransaction), d->bookedTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,71 +332,92 @@ std::vector<std::string> Document::getAvailableRedoNames() const
|
||||
return vList;
|
||||
}
|
||||
|
||||
void Document::openTransaction(const char* name) // NOLINT
|
||||
int Document::openTransaction(TransactionName name, int tid) // NOLINT
|
||||
{
|
||||
if (isPerformingTransaction() || d->committing) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Cannot open transaction while transacting");
|
||||
}
|
||||
return;
|
||||
if (tid != NullTransaction && tid == d->bookedTransaction) {
|
||||
return tid; // Early exit without warning
|
||||
}
|
||||
if (isTransactionLocked()) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Transaction locked, ignore new transaction '" << name.name << "'");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
GetApplication().setActiveTransaction(name ? name : "<empty>");
|
||||
}
|
||||
|
||||
int Document::_openTransaction(const char* name, int id)
|
||||
{
|
||||
if (isPerformingTransaction() || d->committing) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Cannot open transaction while transacting");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (d->iUndoMode != 0) {
|
||||
// Avoid recursive calls that is possible while
|
||||
// clearing the redo transactions and will cause
|
||||
// a double deletion of some transaction and thus
|
||||
// a segmentation fault
|
||||
if (d->opentransaction) {
|
||||
return 0;
|
||||
}
|
||||
Base::FlagToggler<> flag(d->opentransaction);
|
||||
|
||||
if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
|
||||
throw Base::RuntimeError("invalid transaction id");
|
||||
}
|
||||
if (d->activeUndoTransaction) {
|
||||
_commitTransaction(true);
|
||||
}
|
||||
_clearRedos();
|
||||
|
||||
d->activeUndoTransaction = new Transaction(id);
|
||||
if (!name) {
|
||||
name = "<empty>";
|
||||
}
|
||||
d->activeUndoTransaction->Name = name;
|
||||
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
||||
id = d->activeUndoTransaction->getID();
|
||||
|
||||
signalOpenTransaction(*this, name);
|
||||
|
||||
auto& app = GetApplication();
|
||||
auto activeDoc = app.getActiveDocument();
|
||||
if (activeDoc && activeDoc != this && !activeDoc->hasPendingTransaction()) {
|
||||
std::string aname("-> ");
|
||||
aname += d->activeUndoTransaction->Name;
|
||||
FC_LOG("auto transaction " << getName() << " -> " << activeDoc->getName());
|
||||
activeDoc->_openTransaction(aname.c_str(), id);
|
||||
}
|
||||
return id;
|
||||
if (name.name.empty()) {
|
||||
name.name = "<empty>";
|
||||
}
|
||||
return 0;
|
||||
return setActiveTransaction(name, tid);
|
||||
}
|
||||
int Document::openTransaction(std::string name, int tid)
|
||||
{
|
||||
return openTransaction(TransactionName {.name = name, .temporary = false}, tid);
|
||||
}
|
||||
|
||||
void Document::renameTransaction(const char* name, const int id) const
|
||||
int Document::_openTransaction(std::string name, int id)
|
||||
{
|
||||
if (name && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
|
||||
if (isTransactionLocked() && id != d->bookedTransaction) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
|
||||
}
|
||||
}
|
||||
if (isPerformingTransaction() || d->committing) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Cannot open transaction while transacting");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (d->iUndoMode == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Avoid recursive calls that is possible while
|
||||
// clearing the redo transactions and will cause
|
||||
// a double deletion of some transaction and thus
|
||||
// a segmentation fault
|
||||
if (d->opentransaction) {
|
||||
return 0;
|
||||
}
|
||||
Base::FlagToggler<> flag(d->opentransaction);
|
||||
|
||||
if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
|
||||
throw Base::RuntimeError("invalid transaction id");
|
||||
}
|
||||
if (d->activeUndoTransaction) {
|
||||
_commitTransaction(true);
|
||||
}
|
||||
_clearRedos();
|
||||
|
||||
// When id == 0, this creates a new id
|
||||
// for instance, when there is no global transaction
|
||||
// from the application to stick to
|
||||
d->activeUndoTransaction = new Transaction(id);
|
||||
if (name.empty()) {
|
||||
name = "<empty>";
|
||||
}
|
||||
d->activeUndoTransaction->Name = name;
|
||||
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
||||
id = d->activeUndoTransaction->getID();
|
||||
|
||||
signalOpenTransaction(*this, name);
|
||||
|
||||
Document* transactionInitiator = GetApplication().transactionInitiator(id);
|
||||
if (transactionInitiator && transactionInitiator != this && !transactionInitiator->hasPendingTransaction()) {
|
||||
std::string aname = std::format("-> {}", d->activeUndoTransaction->Name);
|
||||
FC_LOG("auto transaction " << getName() << " -> " << transactionInitiator->getName());
|
||||
transactionInitiator->_openTransaction(aname, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void Document::renameTransaction(const std::string& name, const int id) const
|
||||
{
|
||||
if (!name.empty() && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
|
||||
if (boost::starts_with(d->activeUndoTransaction->Name, "-> ")) {
|
||||
d->activeUndoTransaction->Name.resize(3);
|
||||
}
|
||||
@@ -403,48 +427,108 @@ void Document::renameTransaction(const char* name, const int id) const
|
||||
d->activeUndoTransaction->Name += name;
|
||||
}
|
||||
}
|
||||
int Document::setActiveTransaction(TransactionName name, int tid)
|
||||
{
|
||||
// Probably a group transaction situation
|
||||
if (tid != NullTransaction) {
|
||||
if (!GetApplication().transactionIsActive(tid)) {
|
||||
FC_LOG("Could not set active transaction to inactive ID");
|
||||
return NullTransaction;
|
||||
}
|
||||
if (d->bookedTransaction != NullTransaction && d->bookedTransaction != tid && !_commitTransaction(true)) {
|
||||
FC_LOG("Could not book transaction for document");
|
||||
return NullTransaction;
|
||||
}
|
||||
d->bookedTransaction = tid;
|
||||
|
||||
if (GetApplication().transactionTmpName(d->bookedTransaction)) {
|
||||
GetApplication().setTransactionName(d->bookedTransaction, name);
|
||||
}
|
||||
return d->bookedTransaction;
|
||||
}
|
||||
|
||||
// Rename the transaction if it had a tmp name
|
||||
if (d->bookedTransaction != NullTransaction && GetApplication().transactionTmpName(d->bookedTransaction)) {
|
||||
GetApplication().setTransactionName(d->bookedTransaction, name);
|
||||
return d->bookedTransaction;
|
||||
}
|
||||
if (d->bookedTransaction != NullTransaction && !_commitTransaction(true)) {
|
||||
FC_LOG("Could not book transaction for document");
|
||||
return NullTransaction;
|
||||
}
|
||||
d->bookedTransaction = Transaction::getNewID();
|
||||
|
||||
GetApplication().setTransactionDescription(d->bookedTransaction, TransactionDescription {.initiator = this, .name = name});
|
||||
return d->bookedTransaction;
|
||||
}
|
||||
|
||||
void Document::lockTransaction()
|
||||
{
|
||||
d->TransactionLock++;
|
||||
}
|
||||
void Document::unlockTransaction()
|
||||
{
|
||||
if (d->TransactionLock > 0) {
|
||||
d->TransactionLock--;
|
||||
}
|
||||
}
|
||||
bool Document::isTransactionLocked() const
|
||||
{
|
||||
return d->TransactionLock > 0;
|
||||
}
|
||||
bool Document::transacting() const
|
||||
{
|
||||
return isPerformingTransaction() || d->committing;
|
||||
}
|
||||
|
||||
void Document::_checkTransaction(DocumentObject* pcDelObj, const Property* What, int line)
|
||||
{
|
||||
// if the undo is active but no transaction open, open one!
|
||||
if ((d->iUndoMode != 0) && !isPerformingTransaction()) {
|
||||
if (!d->activeUndoTransaction) {
|
||||
if (!testStatus(Restoring) || testStatus(Importing)) {
|
||||
int tid = 0;
|
||||
const char* name = GetApplication().getActiveTransaction(&tid);
|
||||
if (name && tid > 0) {
|
||||
bool ignore = false;
|
||||
if (What && What->testStatus(Property::NoModify)) {
|
||||
ignore = true;
|
||||
}
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
if (What) {
|
||||
FC_LOG((ignore ? "ignore" : "auto")
|
||||
<< " transaction (" << line << ") '" << What->getFullName());
|
||||
}
|
||||
else {
|
||||
FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
|
||||
<< name << "' in " << getName());
|
||||
}
|
||||
}
|
||||
if (!ignore) {
|
||||
_openTransaction(name, tid);
|
||||
}
|
||||
return;
|
||||
if (d->iUndoMode == 0 || isPerformingTransaction() || d->activeUndoTransaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!testStatus(Restoring) || testStatus(Importing)) {
|
||||
|
||||
// Priority to a transaction that has been booked
|
||||
// explicitly for this document, it there are none
|
||||
// get a sticky transaction from application
|
||||
if (!d->bookedTransaction) {
|
||||
d->bookedTransaction = GetApplication().getGlobalTransaction();
|
||||
}
|
||||
|
||||
if (d->bookedTransaction != NullTransaction) {
|
||||
std::string name = GetApplication().getTransactionName(d->bookedTransaction);
|
||||
bool ignore = false;
|
||||
if (What && What->testStatus(Property::NoModify)) {
|
||||
ignore = true;
|
||||
}
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
if (What) {
|
||||
FC_LOG((ignore ? "ignore" : "auto")
|
||||
<< " transaction (" << line << ") '" << What->getFullName());
|
||||
}
|
||||
else {
|
||||
FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
|
||||
<< name << "' in " << getName());
|
||||
}
|
||||
}
|
||||
if (!pcDelObj) {
|
||||
return;
|
||||
}
|
||||
// When the object is going to be deleted we have to check if it has already been added
|
||||
// to the undo transactions
|
||||
std::list<Transaction*>::iterator it;
|
||||
for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
|
||||
if ((*it)->hasObject(pcDelObj)) {
|
||||
_openTransaction("Delete");
|
||||
break;
|
||||
}
|
||||
if (!ignore) {
|
||||
_openTransaction(name, d->bookedTransaction);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!pcDelObj) {
|
||||
return;
|
||||
}
|
||||
// When the object is going to be deleted we have to check if it has already been added
|
||||
// to the undo transactions
|
||||
std::list<Transaction*>::iterator it;
|
||||
for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
|
||||
if ((*it)->hasObject(pcDelObj)) {
|
||||
_openTransaction("Delete");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,29 +557,36 @@ void Document::commitTransaction() // NOLINT
|
||||
}
|
||||
|
||||
if (d->activeUndoTransaction) {
|
||||
GetApplication().closeActiveTransaction(false, d->activeUndoTransaction->getID());
|
||||
// This will iterate over all documents and ask them to
|
||||
// commit their transaction if their ID matches
|
||||
GetApplication().commitTransaction(d->activeUndoTransaction->getID());
|
||||
} else {
|
||||
d->bookedTransaction = 0; // Reset booked transaction even if it was not used
|
||||
}
|
||||
}
|
||||
|
||||
void Document::_commitTransaction(const bool notify)
|
||||
bool Document::_commitTransaction(const bool notify)
|
||||
{
|
||||
if (isPerformingTransaction()) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
FC_WARN("Cannot commit transaction while transacting");
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (d->committing) {
|
||||
// for a recursive call return without printing a warning
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
d->bookedTransaction = 0;
|
||||
if (d->activeUndoTransaction) {
|
||||
Base::FlagToggler<> flag(d->committing);
|
||||
Application::TransactionSignaller signaller(false, true);
|
||||
const int id = d->activeUndoTransaction->getID();
|
||||
|
||||
mUndoTransactions.push_back(d->activeUndoTransaction);
|
||||
d->activeUndoTransaction = nullptr;
|
||||
|
||||
// check the stack for the limits
|
||||
if (mUndoTransactions.size() > d->UndoMaxStackSize) {
|
||||
mUndoMap.erase(mUndoTransactions.front()->getID());
|
||||
@@ -504,11 +595,12 @@ void Document::_commitTransaction(const bool notify)
|
||||
}
|
||||
signalCommitTransaction(*this);
|
||||
|
||||
// closeActiveTransaction() may call again _commitTransaction()
|
||||
// commitTransaction() may call again _commitTransaction()
|
||||
if (notify) {
|
||||
GetApplication().closeActiveTransaction(false, id);
|
||||
GetApplication().commitTransaction(id);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Document::abortTransaction() const
|
||||
@@ -520,7 +612,9 @@ void Document::abortTransaction() const
|
||||
return;
|
||||
}
|
||||
if (d->activeUndoTransaction) {
|
||||
GetApplication().closeActiveTransaction(true, d->activeUndoTransaction->getID());
|
||||
GetApplication().abortTransaction(d->activeUndoTransaction->getID());
|
||||
} else {
|
||||
d->bookedTransaction = 0; // Reset booked transaction even if it was not used
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,6 +626,7 @@ void Document::_abortTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
d->bookedTransaction = 0;
|
||||
if (d->activeUndoTransaction) {
|
||||
Base::FlagToggler<bool> flag(d->rollback);
|
||||
Application::TransactionSignaller signaller(true, true);
|
||||
@@ -575,7 +670,10 @@ int Document::getTransactionID(const bool undo, unsigned pos) const
|
||||
for (; pos != 0U; ++rit, --pos) {}
|
||||
return (*rit)->getID();
|
||||
}
|
||||
|
||||
int Document::getBookedTransactionID() const
|
||||
{
|
||||
return d->bookedTransaction;
|
||||
}
|
||||
bool Document::isTransactionEmpty() const
|
||||
{
|
||||
return !d->activeUndoTransaction;
|
||||
@@ -3312,7 +3410,7 @@ void Document::_removeObject(DocumentObject* pcObject, RemoveObjectOptions optio
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionLocker tlock;
|
||||
TransactionLocker tlock(this);
|
||||
|
||||
_checkTransaction(pcObject, nullptr, __LINE__);
|
||||
|
||||
|
||||
+18
-7
@@ -35,6 +35,7 @@
|
||||
#include "PropertyLinks.h"
|
||||
#include "PropertyStandard.h"
|
||||
#include "ExportInfo.h"
|
||||
#include "TransactionDefs.h"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
@@ -865,7 +866,19 @@ public:
|
||||
* setup a potential transaction that will only be created if there are
|
||||
* actual changes.
|
||||
*/
|
||||
void openTransaction(const char* name = nullptr);
|
||||
int openTransaction(TransactionName name, int tid = 0);
|
||||
int openTransaction(std::string name, int tid = 0);
|
||||
|
||||
// If the tid != 0, it will take the transaction id if it exists
|
||||
int setActiveTransaction(TransactionName name, int tid = 0);
|
||||
|
||||
void lockTransaction();
|
||||
void unlockTransaction();
|
||||
bool isTransactionLocked() const;
|
||||
|
||||
bool transacting() const;
|
||||
|
||||
int getBookedTransactionID() const;
|
||||
|
||||
/**
|
||||
* @brief Rename the current transaction.
|
||||
@@ -875,7 +888,7 @@ public:
|
||||
* @param[in] name The new name of the transaction.
|
||||
* @param[in] id The transaction ID to match.
|
||||
*/
|
||||
void renameTransaction(const char* name, int id) const;
|
||||
void renameTransaction(const std::string& name, int id) const;
|
||||
|
||||
/**
|
||||
* @brief Commit the Command transaction.
|
||||
@@ -1244,7 +1257,7 @@ public:
|
||||
|
||||
/// Check if there is any document restoring/importing.
|
||||
static bool isAnyRestoring();
|
||||
|
||||
|
||||
/// Register a new label.
|
||||
void registerLabel(const std ::string& newLabel);
|
||||
/// Unregister a label.
|
||||
@@ -1409,8 +1422,7 @@ protected:
|
||||
* This function creates an actual transaction regardless of Application
|
||||
* AutoTransaction setting.
|
||||
*/
|
||||
int _openTransaction(const char* name = nullptr, int id = 0);
|
||||
|
||||
int _openTransaction(std::string name = "", int id = 0);
|
||||
/**
|
||||
* @brief Commit the Command transaction.
|
||||
*
|
||||
@@ -1419,8 +1431,7 @@ protected:
|
||||
*
|
||||
* @param notify If true, notify the application to close the transaction.
|
||||
*/
|
||||
void _commitTransaction(bool notify = false);
|
||||
|
||||
bool _commitTransaction(bool notify = false);
|
||||
/**
|
||||
* @brief Abort the running transaction.
|
||||
*
|
||||
|
||||
@@ -447,3 +447,12 @@ class Document(PropertyContainer):
|
||||
sort: whether to topologically sort the return list
|
||||
"""
|
||||
...
|
||||
|
||||
def getBookedTransactionID(self) -> int:
|
||||
"""
|
||||
getBookedTransactionID() -> int
|
||||
|
||||
Returns the currently booked transaction id, which is the id of the current transaction OR the id
|
||||
the next transaction will stick to if no change has occured yet
|
||||
"""
|
||||
...
|
||||
@@ -1175,6 +1175,15 @@ PyObject* DocumentPy::getDependentDocuments(PyObject* args)
|
||||
}
|
||||
PY_CATCH;
|
||||
}
|
||||
PyObject* DocumentPy::getBookedTransactionID(PyObject* args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
int tid = getDocumentPtr()->getBookedTransactionID();
|
||||
return Py::new_reference_to(Py::Long(tid));
|
||||
}
|
||||
|
||||
|
||||
Py::Boolean DocumentPy::getRestoring() const
|
||||
{
|
||||
|
||||
@@ -1546,11 +1546,11 @@ void PropertyString::setValue(const char* newValue)
|
||||
// OnProposedLabelChange has changed the new value to what the current value is
|
||||
return;
|
||||
}
|
||||
if (!propChanges.empty() && !GetApplication().getActiveTransaction()) {
|
||||
if (!propChanges.empty() && obj->getDocument()->getBookedTransactionID() == 0) {
|
||||
commit = true;
|
||||
std::ostringstream str;
|
||||
str << "Change " << obj->getNameInDocument() << ".Label";
|
||||
GetApplication().setActiveTransaction(str.str().c_str());
|
||||
obj->getDocument()->openTransaction(str.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1563,7 +1563,7 @@ void PropertyString::setValue(const char* newValue)
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
GetApplication().closeActiveTransaction();
|
||||
obj->getDocument()->commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef APP_TRANSACTIONDEFS_H_
|
||||
#define APP_TRANSACTIONDEFS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace App {
|
||||
|
||||
class Document;
|
||||
|
||||
constexpr int NullTransaction = 0;
|
||||
|
||||
struct TransactionName {
|
||||
// Name of the transaction as it will appear in the GUI
|
||||
std::string name;
|
||||
|
||||
// If true, the transaction is allowed to be renamed
|
||||
bool temporary { false };
|
||||
};
|
||||
|
||||
struct TransactionDescription {
|
||||
// Document on which the transaction was first created
|
||||
// useful for mass transaction (e.g. delete from an assembly)
|
||||
// where other documents may use the same transaction id
|
||||
Document* initiator { nullptr };
|
||||
TransactionName name;
|
||||
};
|
||||
|
||||
enum class TransactionCloseMode {
|
||||
Commit = 0,
|
||||
Abort = 1,
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -556,4 +556,4 @@ TransactionObject* TransactionFactory::createTransaction(const Base::Type& type)
|
||||
|
||||
Base::Console().log("Cannot create transaction object from %s\n", type.getName());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <Base/Factory.h>
|
||||
#include <Base/Persistence.h>
|
||||
#include <App/PropertyContainer.h>
|
||||
#include "TransactionDefs.h"
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
@@ -61,6 +62,7 @@ class AppExport Transaction: public Base::Persistence
|
||||
{
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a transaction.
|
||||
@@ -71,7 +73,7 @@ public:
|
||||
* transactions from different document, so that they can be undone/redone
|
||||
* together.
|
||||
*/
|
||||
explicit Transaction(int id = 0);
|
||||
explicit Transaction(int id = NullTransaction);
|
||||
|
||||
~Transaction() override;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
@@ -93,6 +94,11 @@ struct DocumentP
|
||||
int iUndoMode {0};
|
||||
unsigned int UndoMemSize {0};
|
||||
unsigned int UndoMaxStackSize {20};
|
||||
unsigned int TransactionLock {0};
|
||||
// Id and name that the next transaction will take
|
||||
// as soon as there is a change to the document
|
||||
int bookedTransaction { 0 };
|
||||
|
||||
std::string programVersion;
|
||||
mutable HasherMap hashers;
|
||||
std::multimap<const App::DocumentObject*, std::unique_ptr<App::DocumentObjectExecReturn>>
|
||||
|
||||
@@ -157,7 +157,7 @@ long NavlibInterface::SetActiveCommand(std::string commandId)
|
||||
if (!std::string(command->getName()).compare(parsedData.commandName)) {
|
||||
if (parsedData.actionIndex == -1) {
|
||||
Gui::Action* pAction = command->getAction();
|
||||
if (pAction != nullptr) {
|
||||
if (pAction) {
|
||||
pAction->action()->trigger();
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ void NavlibInterface::unpackCommands(Gui::Command& command,
|
||||
TDxCategory& category,
|
||||
std::vector<TDx::CImage>& images)
|
||||
{
|
||||
if (command.getAction() == nullptr)
|
||||
if (!command.getAction())
|
||||
return;
|
||||
|
||||
QList<QAction*> pQActions;
|
||||
@@ -185,7 +185,7 @@ void NavlibInterface::unpackCommands(Gui::Command& command,
|
||||
int32_t index = -1;
|
||||
auto actionGroup = qobject_cast<Gui::ActionGroup*>(command.getAction());
|
||||
|
||||
if (actionGroup != nullptr) {
|
||||
if (actionGroup) {
|
||||
pQActions = actionGroup->actions();
|
||||
std::string subCategoryName = actionGroup->text().toStdString();
|
||||
subCategory = TDxCategory(subCategoryName, subCategoryName);
|
||||
|
||||
@@ -58,7 +58,7 @@ NavlibInterface::~NavlibInterface()
|
||||
{
|
||||
disableNavigation();
|
||||
|
||||
if (pivot.pVisibility != nullptr)
|
||||
if (pivot.pVisibility)
|
||||
pivot.pVisibility->unref();
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ long NavlibInterface::GetPointerPosition(navlib::point_t& position) const
|
||||
|
||||
if (is3DView()) {
|
||||
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
|
||||
if (inventorViewer == nullptr)
|
||||
if (!inventorViewer)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
QPoint viewPoint = currentView.pView3d->mapFromGlobal(QCursor::pos());
|
||||
@@ -122,7 +122,7 @@ CameraType NavlibInterface::getCamera() const
|
||||
{
|
||||
if (is3DView()) {
|
||||
const Gui::View3DInventorViewer* inventorViewer = currentView.pView3d->getViewer();
|
||||
if (inventorViewer != nullptr)
|
||||
if (inventorViewer)
|
||||
return dynamic_cast<CameraType>(inventorViewer->getCamera());
|
||||
}
|
||||
return nullptr;
|
||||
@@ -132,18 +132,18 @@ template SoCamera* NavlibInterface::getCamera<SoCamera*>() const;
|
||||
|
||||
void NavlibInterface::onViewChanged(const Gui::MDIView* view)
|
||||
{
|
||||
if (view == nullptr)
|
||||
if (!view)
|
||||
return;
|
||||
|
||||
currentView.pView3d = dynamic_cast<const Gui::View3DInventor*>(view);
|
||||
currentView.pView2d = nullptr;
|
||||
if (currentView.pView3d != nullptr) {
|
||||
if (currentView.pView3d) {
|
||||
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
|
||||
if (inventorViewer == nullptr)
|
||||
if (!inventorViewer)
|
||||
return;
|
||||
|
||||
auto pGroup = dynamic_cast<SoGroup* const>(inventorViewer->getSceneGraph());
|
||||
if (pGroup == nullptr)
|
||||
if (!pGroup)
|
||||
return;
|
||||
|
||||
if (pGroup->findChild(pivot.pVisibility) == -1)
|
||||
@@ -198,11 +198,11 @@ void NavlibInterface::enableNavigation()
|
||||
void NavlibInterface::connectActiveTab()
|
||||
{
|
||||
auto pQMdiArea = Gui::MainWindow::getInstance()->findChild<QMdiArea*>();
|
||||
if (pQMdiArea == nullptr)
|
||||
if (!pQMdiArea)
|
||||
return;
|
||||
|
||||
auto pQTabBar = pQMdiArea->findChild<QTabBar*>();
|
||||
if (pQTabBar == nullptr)
|
||||
if (!pQTabBar)
|
||||
return;
|
||||
|
||||
pQTabBar->connect(pQTabBar, &QTabBar::currentChanged, [this, pQTabBar](int idx) {
|
||||
@@ -222,7 +222,7 @@ long NavlibInterface::GetCameraMatrix(navlib::matrix_t& matrix) const
|
||||
|
||||
if (is3DView()) {
|
||||
auto pCamera = getCamera<SoCamera*>();
|
||||
if (pCamera == nullptr)
|
||||
if (!pCamera)
|
||||
return navlib::make_result_code(navlib::navlib_errc::function_not_supported);
|
||||
|
||||
SbMatrix cameraMatrix;
|
||||
@@ -268,7 +268,7 @@ long NavlibInterface::SetCameraMatrix(const navlib::matrix_t& matrix)
|
||||
|
||||
if (is3DView()) {
|
||||
auto pCamera = getCamera<SoCamera*>();
|
||||
if (pCamera == nullptr)
|
||||
if (!pCamera)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
SbMatrix cameraMatrix(matrix(0, 0), matrix(0, 1), matrix(0, 2), matrix(0, 3),
|
||||
@@ -288,7 +288,7 @@ long NavlibInterface::SetCameraMatrix(const navlib::matrix_t& matrix)
|
||||
long NavlibInterface::GetViewFrustum(navlib::frustum_t& frustum) const
|
||||
{
|
||||
const auto pCamera = getCamera<SoPerspectiveCamera* const>();
|
||||
if (pCamera == nullptr)
|
||||
if (!pCamera)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
const SbViewVolume viewVolume = pCamera->getViewVolume(pCamera->aspectRatio.getValue());
|
||||
@@ -327,7 +327,7 @@ long NavlibInterface::GetViewExtents(navlib::box_t& extents) const
|
||||
}
|
||||
|
||||
const auto pCamera = getCamera<SoOrthographicCamera* const>();
|
||||
if (pCamera == nullptr)
|
||||
if (!pCamera)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
const SbViewVolume viewVolume = pCamera->getViewVolume(pCamera->aspectRatio.getValue());
|
||||
@@ -371,7 +371,7 @@ long NavlibInterface::SetViewExtents(const navlib::box_t& extents)
|
||||
|
||||
if (is3DView()) {
|
||||
auto pCamera = getCamera<SoOrthographicCamera* const>();
|
||||
if (pCamera == nullptr)
|
||||
if (!pCamera)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
navlib::box_t oldExtents;
|
||||
@@ -399,13 +399,13 @@ long NavlibInterface::SetViewFOV(double)
|
||||
long NavlibInterface::GetIsViewPerspective(navlib::bool_t& perspective) const
|
||||
{
|
||||
auto pPerspectiveCamera = getCamera<SoPerspectiveCamera* const>();
|
||||
if (pPerspectiveCamera != nullptr) {
|
||||
if (pPerspectiveCamera) {
|
||||
perspective = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto pOrthographicCamera = getCamera<SoOrthographicCamera* const>();
|
||||
if (pOrthographicCamera != nullptr || is2DView()) {
|
||||
if (pOrthographicCamera || is2DView()) {
|
||||
perspective = false;
|
||||
return 0;
|
||||
}
|
||||
@@ -417,7 +417,7 @@ long NavlibInterface::GetModelExtents(navlib::box_t& extents) const
|
||||
{
|
||||
if (is3DView()) {
|
||||
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
|
||||
if (inventorViewer == nullptr)
|
||||
if (!inventorViewer)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
SoGetBoundingBoxAction action(inventorViewer->getSoRenderManager()->getViewportRegion());
|
||||
|
||||
@@ -71,7 +71,7 @@ long NavlibInterface::GetPivotPosition(navlib::point_t&) const
|
||||
|
||||
long NavlibInterface::SetPivotPosition(const navlib::point_t& position)
|
||||
{
|
||||
if (pivot.pTransform == nullptr)
|
||||
if (!pivot.pTransform)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
pivot.pTransform->translation.setValue(position.x, position.y, position.z);
|
||||
@@ -86,7 +86,7 @@ long NavlibInterface::IsUserPivot(navlib::bool_t& userPivot) const
|
||||
|
||||
long NavlibInterface::GetPivotVisible(navlib::bool_t& visible) const
|
||||
{
|
||||
if (pivot.pVisibility == nullptr)
|
||||
if (!pivot.pVisibility)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
visible = pivot.pVisibility->whichChild.getValue() == SO_SWITCH_ALL;
|
||||
@@ -96,7 +96,7 @@ long NavlibInterface::GetPivotVisible(navlib::bool_t& visible) const
|
||||
|
||||
long NavlibInterface::SetPivotVisible(bool visible)
|
||||
{
|
||||
if (pivot.pVisibility == nullptr)
|
||||
if (!pivot.pVisibility)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
if (visible)
|
||||
@@ -115,11 +115,11 @@ long NavlibInterface::GetHitLookAt(navlib::point_t& position) const
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
const Gui::View3DInventorViewer* const inventorViewer = currentView.pView3d->getViewer();
|
||||
if (inventorViewer == nullptr)
|
||||
if (!inventorViewer)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
SoNode* pSceneGraph = inventorViewer->getSceneGraph();
|
||||
if (pSceneGraph == nullptr)
|
||||
if (!pSceneGraph)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
// Prepare the ray-picking object
|
||||
@@ -131,7 +131,7 @@ long NavlibInterface::GetHitLookAt(navlib::point_t& position) const
|
||||
// Get the camera rotation
|
||||
SoCamera* pCamera = getCamera<SoCamera*>();
|
||||
|
||||
if (pCamera == nullptr)
|
||||
if (!pCamera)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
pCamera->orientation.getValue().getValue(cameraMatrix);
|
||||
@@ -177,7 +177,7 @@ long NavlibInterface::GetHitLookAt(navlib::point_t& position) const
|
||||
SoPickedPoint* pickedPoint = rayPickAction.getPickedPoint();
|
||||
|
||||
// Check if there was a hit
|
||||
if (pickedPoint != nullptr) {
|
||||
if (pickedPoint) {
|
||||
SbVec3f hitPoint = pickedPoint->getPoint();
|
||||
float distance = (origin - hitPoint).length();
|
||||
|
||||
@@ -213,7 +213,7 @@ long NavlibInterface::GetSelectionExtents(navlib::box_t& extents) const
|
||||
Gui::ViewProvider* pViewProvider =
|
||||
Gui::Application::Instance->getViewProvider(selection.pObject);
|
||||
|
||||
if (pViewProvider == nullptr)
|
||||
if (!pViewProvider)
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
|
||||
boundingBox.Add(pViewProvider->getBoundingBox(selection.SubName, true));
|
||||
@@ -254,7 +254,7 @@ long NavlibInterface::SetHitLookFrom(const navlib::point_t& eye)
|
||||
}
|
||||
else {
|
||||
auto pCamera = getCamera<SoCamera*>();
|
||||
if (pCamera == nullptr) {
|
||||
if (!pCamera) {
|
||||
return navlib::make_result_code(navlib::navlib_errc::no_data_available);
|
||||
}
|
||||
|
||||
|
||||
+70
-19
@@ -227,7 +227,7 @@ struct ApplicationP
|
||||
std::map<const App::Document*, Gui::Document*> documents;
|
||||
/// Active document
|
||||
Gui::Document* activeDocument {nullptr};
|
||||
Gui::Document* editDocument {nullptr};
|
||||
std::vector<Gui::Document*> editDocuments;
|
||||
|
||||
MacroManager* macroMngr;
|
||||
PreferencePackManager* prefPackManager;
|
||||
@@ -631,7 +631,7 @@ Application::Application(bool GUIenabled)
|
||||
PyObject* module = PyImport_AddModule("FreeCADGui");
|
||||
PyMethodDef* meth = FreeCADGui_methods;
|
||||
PyObject* dict = PyModule_GetDict(module);
|
||||
for (; meth->ml_name != nullptr; meth++) {
|
||||
for (; meth->ml_name; meth++) {
|
||||
PyObject* descr;
|
||||
descr = PyCFunction_NewEx(meth, nullptr, nullptr);
|
||||
if (!descr) {
|
||||
@@ -1285,6 +1285,9 @@ void Application::slotActiveDocument(const App::Document& Doc)
|
||||
Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"), Py::None());
|
||||
}
|
||||
}
|
||||
if (!d->activeDocument->workbench().empty()) {
|
||||
activateWorkbench(d->activeDocument->workbench().c_str());
|
||||
}
|
||||
|
||||
// Update the application to show the unit change
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
@@ -1491,26 +1494,65 @@ Gui::Document* Application::activeDocument() const
|
||||
|
||||
Gui::Document* Application::editDocument() const
|
||||
{
|
||||
return d->editDocument;
|
||||
}
|
||||
|
||||
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
|
||||
{
|
||||
return d->editDocument ? d->editDocument->getViewOfNode(node) : nullptr;
|
||||
}
|
||||
|
||||
void Application::setEditDocument(Gui::Document* doc)
|
||||
{
|
||||
if (!doc) {
|
||||
d->editDocument = nullptr;
|
||||
if (d->editDocuments.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
else if (doc == d->editDocument) {
|
||||
return d->editDocuments[0];
|
||||
}
|
||||
Gui::Document* Application::editDocument(const std::function<bool(Gui::Document*)>& eval)
|
||||
{
|
||||
auto found = std::ranges::find_if(d->editDocuments, eval);
|
||||
|
||||
return found == d->editDocuments.end() ? nullptr : *found;
|
||||
}
|
||||
std::vector<Gui::Document*> Application::editDocuments() const
|
||||
{
|
||||
return d->editDocuments;
|
||||
}
|
||||
bool Application::isInEdit(Gui::Document* pcDocument) const
|
||||
{
|
||||
return std::ranges::find(d->editDocuments, pcDocument) != d->editDocuments.end();
|
||||
}
|
||||
void Application::unsetEditDocument(Gui::Document* pcDocument)
|
||||
{
|
||||
if (std::erase(d->editDocuments, pcDocument) == 0) {
|
||||
return;
|
||||
}
|
||||
for (auto& v : d->documents) {
|
||||
v.second->_resetEdit();
|
||||
|
||||
pcDocument->_resetEdit();
|
||||
updateActions();
|
||||
}
|
||||
void Application::unsetEditDocumentIf(const std::function<bool(Gui::Document*)>& eval)
|
||||
{
|
||||
std::erase_if(d->editDocuments, [&](Gui::Document* doc) {
|
||||
if (eval(doc)) {
|
||||
doc->_resetEdit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
updateActions();
|
||||
}
|
||||
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
|
||||
{
|
||||
for (auto editDoc : d->editDocuments) {
|
||||
if (Gui::MDIView* view = editDoc->getViewOfNode(node)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
d->editDocument = doc;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Application::setEditDocument(Gui::Document* pcDocument)
|
||||
{
|
||||
if (pcDocument == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (std::ranges::find(d->editDocuments, pcDocument) != d->editDocuments.end()) {
|
||||
return;
|
||||
}
|
||||
d->editDocuments.push_back(pcDocument);
|
||||
|
||||
updateActions();
|
||||
}
|
||||
|
||||
@@ -1531,12 +1573,18 @@ void Application::setActiveDocument(Gui::Document* pcDocument)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (d->activeDocument) {
|
||||
d->activeDocument->setIsActive(false);
|
||||
}
|
||||
d->activeDocument = pcDocument;
|
||||
|
||||
std::string nameApp, nameGui;
|
||||
|
||||
// This adds just a line to the macro file but does not set the active document
|
||||
// Macro recording of this is problematic, thus it's written out as comment.
|
||||
if (pcDocument) {
|
||||
pcDocument->setIsActive(true);
|
||||
|
||||
nameApp += "App.setActiveDocument(\"";
|
||||
nameApp += pcDocument->getDocument()->getName();
|
||||
nameApp += "\")\n";
|
||||
@@ -1893,6 +1941,9 @@ bool Application::activateWorkbench(const char* name)
|
||||
}
|
||||
newWb->activated();
|
||||
}
|
||||
if (activeDocument()) {
|
||||
activeDocument()->setWorkbench(name);
|
||||
}
|
||||
}
|
||||
catch (Py::Exception&) {
|
||||
Base::PyException e; // extract the Python error text
|
||||
@@ -2790,7 +2841,7 @@ void Application::setStyle(const QString& name)
|
||||
return qobject_cast<FreeCADStyle*>(style) != nullptr;
|
||||
};
|
||||
|
||||
if (auto* current = qApp->style(); current != nullptr && requiresEventFilter(current)) {
|
||||
if (auto* current = qApp->style(); current && requiresEventFilter(current)) {
|
||||
qApp->removeEventFilter(current);
|
||||
}
|
||||
|
||||
|
||||
+17
-2
@@ -181,11 +181,26 @@ public:
|
||||
Gui::Document* activeDocument() const;
|
||||
/// Set the active document
|
||||
void setActiveDocument(Gui::Document* pcDocument);
|
||||
/// Getter for the editing document
|
||||
|
||||
/// Getter for the editing document, will be removed soon
|
||||
Gui::Document* editDocument() const;
|
||||
/// Getter for the first editing document that matches a functor
|
||||
Gui::Document* editDocument(const std::function<bool(Gui::Document*)>& eval);
|
||||
/// Getter for all currently editing documents, all pointers are guaranteed to be non-null
|
||||
std::vector<Gui::Document*> editDocuments() const;
|
||||
|
||||
// Returns true if the document is in edit (will make more sense once the edit document it is a
|
||||
// vector)
|
||||
bool isInEdit(Gui::Document* pcDocument) const;
|
||||
// Reset edit if eval returns true for a document in edit
|
||||
|
||||
Gui::MDIView* editViewOfNode(SoNode* node) const;
|
||||
/// Set editing document, which will reset editing of all other document
|
||||
/// Adds a document in edit
|
||||
void setEditDocument(Gui::Document* pcDocument);
|
||||
// After this, isInEdit(pcDocument) returns false
|
||||
void unsetEditDocument(Gui::Document* pcDocument);
|
||||
void unsetEditDocumentIf(const std::function<bool(Gui::Document*)>& eval);
|
||||
|
||||
/** Retrieves a pointer to the Gui::Document whose App::Document has the name \a name.
|
||||
* If no such document exists 0 is returned.
|
||||
*/
|
||||
|
||||
@@ -490,6 +490,7 @@ SET(Gui_UIC_SRCS
|
||||
InputVector.ui
|
||||
Placement.ui
|
||||
TaskTransform.ui
|
||||
TaskCommandLink.ui
|
||||
TextureMapping.ui
|
||||
TaskView/TaskAppearance.ui
|
||||
TaskView/TaskOrientation.ui
|
||||
@@ -578,6 +579,7 @@ SET(Dialog_CPP_SRCS
|
||||
TaskDlgRelocation.cpp
|
||||
Dialogs/DlgCheckableMessageBox.cpp
|
||||
TaskTransform.cpp
|
||||
TaskCommandLink.cpp
|
||||
Dialogs/DlgUndoRedo.cpp
|
||||
InputVector.cpp
|
||||
Placement.cpp
|
||||
@@ -620,6 +622,7 @@ SET(Dialog_HPP_SRCS
|
||||
Dialogs/DlgVersionMigrator.h
|
||||
TaskDlgRelocation.h
|
||||
TaskTransform.h
|
||||
TaskCommandLink.h
|
||||
Dialogs/DlgUndoRedo.h
|
||||
InputVector.h
|
||||
Placement.h
|
||||
|
||||
@@ -226,7 +226,7 @@ QMap<QString, CallTip> CallTipsList::extractTips(const QString& context) const
|
||||
try {
|
||||
PyErr_Clear();
|
||||
Py::Module module("__main__");
|
||||
if (module.ptr() == nullptr) {
|
||||
if (!module.ptr()) {
|
||||
return tips;
|
||||
}
|
||||
Py::Dict dict = module.getDict();
|
||||
|
||||
+27
-4
@@ -21,6 +21,7 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
|
||||
#include <Inventor/actions/SoGetBoundingBoxAction.h>
|
||||
#include <Inventor/nodes/SoClipPlane.h>
|
||||
@@ -29,6 +30,8 @@
|
||||
#include <QDockWidget>
|
||||
#include <QPointer>
|
||||
|
||||
#include <App/Application.h>
|
||||
|
||||
#include "Clipping.h"
|
||||
#include "ui_Clipping.h"
|
||||
#include "DockWindowManager.h"
|
||||
@@ -51,6 +54,10 @@ public:
|
||||
bool flipY {false};
|
||||
bool flipZ {false};
|
||||
SoTimerSensor* sensor;
|
||||
App::Document* shownOn {nullptr};
|
||||
QDockWidget* dockWidget {nullptr};
|
||||
fastsignals::scoped_connection activeDocConnection;
|
||||
|
||||
Private()
|
||||
{
|
||||
clipX = new SoClipPlane();
|
||||
@@ -99,7 +106,7 @@ public:
|
||||
|
||||
/* TRANSLATOR Gui::Dialog::Clipping */
|
||||
|
||||
Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
|
||||
Clipping::Clipping(Gui::View3DInventor* view, App::Document* showOn, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, d(new Private)
|
||||
{
|
||||
@@ -124,6 +131,7 @@ Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
|
||||
d->ui.dirZ->setRange(-max, max);
|
||||
d->ui.dirZ->setSingleStep(0.1f);
|
||||
d->ui.dirZ->setValue(1.0f);
|
||||
d->shownOn = showOn;
|
||||
|
||||
d->view = view;
|
||||
View3DInventorViewer* viewer = view->getViewer();
|
||||
@@ -190,14 +198,15 @@ Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
|
||||
}
|
||||
}
|
||||
|
||||
Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view)
|
||||
Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view, App::Document* showOn)
|
||||
{
|
||||
// embed this dialog into a QDockWidget
|
||||
auto clipping = new Clipping(view);
|
||||
auto clipping = new Clipping(view, showOn);
|
||||
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
|
||||
QDockWidget* dw = pDockMgr->addDockWindow("Clipping", clipping, Qt::LeftDockWidgetArea);
|
||||
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
|
||||
dw->show();
|
||||
clipping->d->dockWidget = dw;
|
||||
|
||||
return clipping;
|
||||
}
|
||||
@@ -205,6 +214,7 @@ Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view)
|
||||
/** Destroys the object and frees any allocated resources */
|
||||
Clipping::~Clipping()
|
||||
{
|
||||
d->activeDocConnection.disconnect();
|
||||
d->node->removeChild(d->clipX);
|
||||
d->node->removeChild(d->clipY);
|
||||
d->node->removeChild(d->clipZ);
|
||||
@@ -216,6 +226,8 @@ Clipping::~Clipping()
|
||||
void Clipping::setupConnections()
|
||||
{
|
||||
// clang-format off
|
||||
d->activeDocConnection = App::GetApplication().signalActiveDocument.connect(
|
||||
std::bind(&Clipping::onActiveDocument, this, std::placeholders::_1));
|
||||
connect(d->ui.groupBoxX, &QGroupBox::toggled,
|
||||
this, &Clipping::onGroupBoxXToggled);
|
||||
connect(d->ui.groupBoxY, &QGroupBox::toggled,
|
||||
@@ -259,7 +271,18 @@ void Clipping::reject()
|
||||
dw->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void Clipping::onActiveDocument(const App::Document& doc)
|
||||
{
|
||||
if (!d || !d->dockWidget) {
|
||||
return;
|
||||
}
|
||||
if (&doc == d->shownOn) {
|
||||
d->dockWidget->show();
|
||||
}
|
||||
else {
|
||||
d->dockWidget->hide();
|
||||
}
|
||||
}
|
||||
void Clipping::onGroupBoxXToggled(bool on)
|
||||
{
|
||||
if (on) {
|
||||
|
||||
+8
-2
@@ -26,6 +26,11 @@
|
||||
#include <QDialog>
|
||||
#include <FCGlobal.h>
|
||||
|
||||
namespace App
|
||||
{
|
||||
class Document;
|
||||
}
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class View3DInventor;
|
||||
@@ -40,12 +45,13 @@ class GuiExport Clipping: public QDialog
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static Clipping* makeDockWidget(Gui::View3DInventor*);
|
||||
Clipping(Gui::View3DInventor* view, QWidget* parent = nullptr);
|
||||
static Clipping* makeDockWidget(Gui::View3DInventor*, App::Document* showOn);
|
||||
Clipping(Gui::View3DInventor* view, App::Document* showOn, QWidget* parent = nullptr);
|
||||
~Clipping() override;
|
||||
|
||||
protected:
|
||||
void setupConnections();
|
||||
void onActiveDocument(const App::Document& doc);
|
||||
void onGroupBoxXToggled(bool);
|
||||
void onGroupBoxYToggled(bool);
|
||||
void onGroupBoxZToggled(bool);
|
||||
|
||||
+59
-24
@@ -36,7 +36,7 @@
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/AutoTransaction.h>
|
||||
#include <App/Transactions.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Interpreter.h>
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#include "Command.h"
|
||||
#include "Action.h"
|
||||
#include "App/Application.h"
|
||||
#include "Application.h"
|
||||
#include "BitmapFactory.h"
|
||||
#include "Control.h"
|
||||
@@ -447,11 +448,6 @@ void Command::invoke(int i, TriggerSource trigger)
|
||||
void Command::_invoke(int id, bool disablelog)
|
||||
{
|
||||
try {
|
||||
// Because Transaction now captures ViewObject changes, auto named
|
||||
// transaction is disabled here to avoid too many unnecessary transactions.
|
||||
//
|
||||
App::AutoTransaction committer(nullptr, true);
|
||||
|
||||
// set the application module type for the macro
|
||||
getGuiApplication()->macroManager()->setModule(sAppModule);
|
||||
|
||||
@@ -463,7 +459,6 @@ void Command::_invoke(int id, bool disablelog)
|
||||
// check if it really works NOW (could be a delay between click deactivation of the button)
|
||||
if (isActive()) {
|
||||
auto manager = getGuiApplication()->macroManager();
|
||||
auto editDoc = getGuiApplication()->editDocument();
|
||||
|
||||
if (!logdisabler) {
|
||||
activated(id);
|
||||
@@ -499,12 +494,10 @@ void Command::_invoke(int id, bool disablelog)
|
||||
}
|
||||
|
||||
getMainWindow()->updateActions();
|
||||
|
||||
// If this command starts an editing, let the transaction persist
|
||||
if (!editDoc && getGuiApplication()->editDocument()) {
|
||||
committer.setEnable(false);
|
||||
}
|
||||
}
|
||||
// here we assume that the overriden activated() function
|
||||
// commited, aborted or gave the transaction id to a dialog
|
||||
currentTransactionID = App::NullTransaction; // Get ready for next invoke
|
||||
}
|
||||
catch (const Base::SystemExitException&) {
|
||||
throw;
|
||||
@@ -550,9 +543,10 @@ void Command::testActive()
|
||||
|
||||
if (!(eType & ForEdit)) { // special case for commands which are only in some edit modes active
|
||||
|
||||
if ((!Gui::Control().isAllowedAlterDocument() && eType & AlterDoc)
|
||||
|| (!Gui::Control().isAllowedAlterView() && eType & Alter3DView)
|
||||
|| (!Gui::Control().isAllowedAlterSelection() && eType & AlterSelection)) {
|
||||
App::Document* doc = getDocument();
|
||||
if ((!Gui::Control().isAllowedAlterDocument(doc) && eType & AlterDoc)
|
||||
|| (!Gui::Control().isAllowedAlterView(doc) && eType & Alter3DView)
|
||||
|| (!Gui::Control().isAllowedAlterSelection(doc) && eType & AlterSelection)) {
|
||||
_pcAction->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
@@ -681,27 +675,68 @@ QString Command::translatedGroupName() const
|
||||
* operation default is the Command name.
|
||||
* @see CommitCommand(),AbortCommand()
|
||||
*/
|
||||
void Command::openCommand(const char* sCmdName)
|
||||
int Command::openCommand(App::TransactionName name)
|
||||
{
|
||||
if (!sCmdName) {
|
||||
sCmdName = "Command";
|
||||
currentTransactionID = openActiveDocumentCommand(name);
|
||||
return currentTransactionID;
|
||||
}
|
||||
int Command::openCommand(std::string name)
|
||||
{
|
||||
return openCommand(App::TransactionName {.name = name, .temporary = false});
|
||||
}
|
||||
int Command::openActiveDocumentCommand(App::TransactionName name, int tid)
|
||||
{
|
||||
if (Gui::Document* guidoc = getGuiApplication()->activeDocument()) {
|
||||
return guidoc->getDocument()->setActiveTransaction(name, tid);
|
||||
}
|
||||
App::GetApplication().setActiveTransaction(sCmdName);
|
||||
return 0;
|
||||
}
|
||||
int Command::openActiveDocumentCommand(std::string name, int tid)
|
||||
{
|
||||
return openActiveDocumentCommand(App::TransactionName {.name = name, .temporary = false}, tid);
|
||||
}
|
||||
void Command::rename(const std::string& name)
|
||||
{
|
||||
App::GetApplication().setTransactionName(
|
||||
currentTransactionID,
|
||||
App::TransactionName {.name = name, .temporary = false}
|
||||
);
|
||||
}
|
||||
|
||||
void Command::commitCommand()
|
||||
{
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
commitCommand(currentTransactionID);
|
||||
currentTransactionID = App::NullTransaction;
|
||||
}
|
||||
void Command::commitCommand(int tid)
|
||||
{
|
||||
if (tid != App::NullTransaction) {
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
}
|
||||
}
|
||||
|
||||
void Command::abortCommand()
|
||||
{
|
||||
App::GetApplication().closeActiveTransaction(true);
|
||||
abortCommand(currentTransactionID);
|
||||
currentTransactionID = App::NullTransaction;
|
||||
}
|
||||
void Command::abortCommand(int tid)
|
||||
{
|
||||
if (tid != App::NullTransaction) {
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
}
|
||||
}
|
||||
int Command::transactionID() const
|
||||
{
|
||||
return currentTransactionID;
|
||||
}
|
||||
void Command::resetTransactionID()
|
||||
{
|
||||
currentTransactionID = App::NullTransaction;
|
||||
}
|
||||
|
||||
bool Command::hasPendingCommand()
|
||||
{
|
||||
return !!App::GetApplication().getActiveTransaction();
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
return doc && doc->getBookedTransactionID() != App::NullTransaction;
|
||||
}
|
||||
|
||||
bool Command::_blockCmd = false;
|
||||
|
||||
+18
-3
@@ -473,11 +473,24 @@ public:
|
||||
/** @name Helper methods for the Undo/Redo and Update handling */
|
||||
//@{
|
||||
/// Open a new Undo transaction on the active document
|
||||
static void openCommand(const char* sName = nullptr);
|
||||
int openCommand(App::TransactionName name);
|
||||
int openCommand(std::string name);
|
||||
static int openActiveDocumentCommand(App::TransactionName name, int tid = App::NullTransaction);
|
||||
static int openActiveDocumentCommand(std::string name, int tid = App::NullTransaction);
|
||||
|
||||
void rename(const std::string& name);
|
||||
|
||||
/// Commit the Undo transaction on the active document
|
||||
static void commitCommand();
|
||||
void commitCommand();
|
||||
static void commitCommand(int tid);
|
||||
|
||||
/// Abort the Undo transaction on the active document
|
||||
static void abortCommand();
|
||||
void abortCommand();
|
||||
static void abortCommand(int tid);
|
||||
|
||||
int transactionID() const;
|
||||
void resetTransactionID();
|
||||
|
||||
/// Check if an Undo transaction is open on the active document
|
||||
static bool hasPendingCommand();
|
||||
/// Updates the (active) document (propagate changes)
|
||||
@@ -715,6 +728,8 @@ protected:
|
||||
/// Indicate if the command shall log to MacroManager
|
||||
bool bCanLog;
|
||||
//@}
|
||||
|
||||
int currentTransactionID {0}; // TransactionID created in _invoke
|
||||
private:
|
||||
static int _busy;
|
||||
bool bEnabled;
|
||||
|
||||
+51
-26
@@ -710,6 +710,7 @@ StdCmdNew::StdCmdNew()
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "document-new";
|
||||
sAccel = keySequenceToAccel(QKeySequence::New);
|
||||
eType = NoTransaction;
|
||||
}
|
||||
|
||||
void StdCmdNew::activated(int iMsg)
|
||||
@@ -1446,41 +1447,57 @@ void StdCmdDelete::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
|
||||
std::set<App::Document*> docs;
|
||||
int tid = 0;
|
||||
try {
|
||||
openCommand(QT_TRANSLATE_NOOP("Command", "Delete"));
|
||||
std::set<App::Document*> docs;
|
||||
std::vector<App::TransactionLocker> tlocks;
|
||||
auto manageDocCommand = [&tid, &tlocks](App::Document* doc) {
|
||||
// The tid will not be updated if non-zero
|
||||
tid = doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Delete"), tid);
|
||||
tlocks.emplace_back(doc);
|
||||
};
|
||||
|
||||
if (getGuiApplication()->sendHasMsgToFocusView(getName())) {
|
||||
commitCommand();
|
||||
// no command has been opened yet so we can skip this commit
|
||||
// commitCommand();
|
||||
return;
|
||||
}
|
||||
|
||||
App::TransactionLocker tlock;
|
||||
// Ensure that the document from which we send the command
|
||||
// can undo it (e.g delete a subobject of an assembly
|
||||
// from the assembly file)
|
||||
manageDocCommand(getActiveGuiDocument()->getDocument());
|
||||
|
||||
Gui::getMainWindow()->setUpdatesEnabled(false);
|
||||
auto editDoc = Application::Instance->editDocument();
|
||||
ViewProviderDocumentObject* vpedit = nullptr;
|
||||
if (editDoc) {
|
||||
vpedit = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
|
||||
}
|
||||
if (vpedit && !vpedit->acceptDeletionsInEdit()) {
|
||||
for (auto& sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
|
||||
if (sel.getObject() == vpedit->getObject()) {
|
||||
if (!sel.getSubNames().empty()) {
|
||||
vpedit->onDelete(sel.getSubNames());
|
||||
docs.insert(editDoc->getDocument());
|
||||
|
||||
bool deletedSelectionOfEditDocument = false;
|
||||
std::vector<Gui::Document*> editDocs = Application::Instance->editDocuments();
|
||||
for (auto& editDoc : editDocs) {
|
||||
auto vpedit = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
|
||||
|
||||
// In practice, no ViewProviderDocumentObject accepts deletion in edit - 2025-06-17
|
||||
if (vpedit && !vpedit->acceptDeletionsInEdit()) {
|
||||
for (auto& sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
|
||||
if (sel.getObject() == vpedit->getObject()) {
|
||||
if (!sel.getSubNames().empty()) {
|
||||
deletedSelectionOfEditDocument = true;
|
||||
manageDocCommand(editDoc->getDocument());
|
||||
vpedit->onDelete(sel.getSubNames());
|
||||
docs.insert(editDoc->getDocument());
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (!deletedSelectionOfEditDocument) {
|
||||
std::set<QString> affectedLabels;
|
||||
bool more = false;
|
||||
auto sels = Selection().getSelectionEx();
|
||||
bool autoDeletion = true;
|
||||
for (auto& sel : sels) {
|
||||
auto obj = sel.getObject();
|
||||
if (obj == nullptr) {
|
||||
if (!obj) {
|
||||
Base::Console().developerWarning(
|
||||
"StdCmdDelete::activated",
|
||||
"App::DocumentObject pointer is nullptr\n"
|
||||
@@ -1548,6 +1565,7 @@ void StdCmdDelete::activated(int iMsg)
|
||||
auto obj = sel.getObject();
|
||||
Gui::ViewProvider* vp = Application::Instance->getViewProvider(obj);
|
||||
if (vp) {
|
||||
manageDocCommand(obj->getDocument());
|
||||
// ask the ViewProvider if it wants to do some clean up
|
||||
if (vp->onDelete(sel.getSubNames())) {
|
||||
docs.insert(obj->getDocument());
|
||||
@@ -1581,6 +1599,8 @@ void StdCmdDelete::activated(int iMsg)
|
||||
QString::fromLatin1(e.what())
|
||||
);
|
||||
e.reportException();
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
tid = 0;
|
||||
}
|
||||
catch (...) {
|
||||
QMessageBox::critical(
|
||||
@@ -1588,8 +1608,11 @@ void StdCmdDelete::activated(int iMsg)
|
||||
QObject::tr("Delete Failed"),
|
||||
QStringLiteral("Unknown error")
|
||||
);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
tid = 0;
|
||||
}
|
||||
commitCommand();
|
||||
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
Gui::getMainWindow()->setUpdatesEnabled(true);
|
||||
Gui::getMainWindow()->update();
|
||||
}
|
||||
@@ -1635,7 +1658,8 @@ void StdCmdRefresh::activated([[maybe_unused]] int iMsg)
|
||||
return;
|
||||
}
|
||||
|
||||
App::AutoTransaction trans((eType & NoTransaction) ? nullptr : "Recompute");
|
||||
App::AutoTransaction trans((eType & NoTransaction) ? 0 : openActiveDocumentCommand("Recompute"));
|
||||
|
||||
try {
|
||||
doCommand(Doc, "App.activeDocument().recompute(None,True,True)");
|
||||
}
|
||||
@@ -1738,7 +1762,7 @@ void StdCmdPlacement::activated(int iMsg)
|
||||
plm->clearSelection();
|
||||
}
|
||||
}
|
||||
Gui::Control().showDialog(plm);
|
||||
Gui::Control().showDialog(plm, getDocument());
|
||||
}
|
||||
|
||||
bool StdCmdPlacement::isActive()
|
||||
@@ -2133,9 +2157,10 @@ protected:
|
||||
return;
|
||||
}
|
||||
|
||||
openCommand(QT_TRANSLATE_NOOP("Command", "Paste expressions"));
|
||||
int tid = App::NullTransaction;
|
||||
try {
|
||||
for (auto& v : exprs) {
|
||||
tid = v.first->openTransaction(QT_TRANSLATE_NOOP("Command", "Paste expressions"), tid);
|
||||
for (auto& v2 : v.second) {
|
||||
auto& expressions = v2.second;
|
||||
auto old = v2.first->getExpressions();
|
||||
@@ -2152,13 +2177,13 @@ protected:
|
||||
}
|
||||
}
|
||||
}
|
||||
commitCommand();
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
abortCommand();
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
QMessageBox::critical(
|
||||
getMainWindow(),
|
||||
QObject::tr("Failed to Paste Expressions"),
|
||||
QObject::tr("Failed to paste expressions"),
|
||||
QString::fromLatin1(e.what())
|
||||
);
|
||||
e.reportException();
|
||||
|
||||
@@ -330,7 +330,7 @@ Gui::Action* StdCmdToggleSkipRecompute::createAction()
|
||||
void StdCmdToggleSkipRecompute::activated(int iMsg)
|
||||
{
|
||||
const auto doc = this->getDocument();
|
||||
if (doc == nullptr) {
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ void StdCmdToggleSkipRecompute::activated(int iMsg)
|
||||
bool StdCmdToggleSkipRecompute::isActive()
|
||||
{
|
||||
const auto doc = this->getDocument();
|
||||
if (doc == nullptr) {
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+62
-44
@@ -34,9 +34,11 @@
|
||||
#include "Action.h"
|
||||
#include "Application.h"
|
||||
#include "Command.h"
|
||||
#include "Control.h"
|
||||
#include "Document.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Selection.h"
|
||||
#include "TaskCommandLink.h"
|
||||
#include "Tree.h"
|
||||
#include "ViewProviderDocumentObject.h"
|
||||
#include "WaitCursor.h"
|
||||
@@ -280,6 +282,51 @@ void StdCmdLinkMake::activated(int)
|
||||
return;
|
||||
}
|
||||
|
||||
auto exec = [=](std::vector<App::DocumentObject*> objs) {
|
||||
doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Make link"));
|
||||
try {
|
||||
if (objs.empty()) {
|
||||
std::string name = doc->getUniqueObjectName("Link");
|
||||
Command::doCommand(
|
||||
Command::Doc,
|
||||
"App.getDocument('%s').addObject('App::Link','%s')",
|
||||
doc->getName(),
|
||||
name.c_str()
|
||||
);
|
||||
Selection().addSelection(doc->getName(), name.c_str());
|
||||
}
|
||||
else {
|
||||
for (auto obj : objs) {
|
||||
std::string name = doc->getUniqueObjectName("Link");
|
||||
Command::doCommand(
|
||||
Command::Doc,
|
||||
"App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument("
|
||||
"'%s'"
|
||||
").%s)",
|
||||
doc->getName(),
|
||||
name.c_str(),
|
||||
obj->getDocument()->getName(),
|
||||
obj->getNameInDocument()
|
||||
);
|
||||
setLinkLabel(obj, doc->getName(), name.c_str());
|
||||
Selection().addSelection(doc->getName(), name.c_str());
|
||||
}
|
||||
}
|
||||
Selection().selStackPush();
|
||||
doc->commitTransaction();
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
doc->abortTransaction();
|
||||
QMessageBox::critical(
|
||||
getMainWindow(),
|
||||
QObject::tr("Create link failed"),
|
||||
QString::fromLatin1(e.what())
|
||||
);
|
||||
e.reportException();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
std::set<App::DocumentObject*> objs;
|
||||
for (auto& sel : Selection().getCompleteSelection()) {
|
||||
if (sel.pObject && sel.pObject->isAttachedToDocument()) {
|
||||
@@ -287,48 +334,13 @@ void StdCmdLinkMake::activated(int)
|
||||
}
|
||||
}
|
||||
|
||||
Selection().selStackPush();
|
||||
Selection().clearCompleteSelection();
|
||||
|
||||
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Make link"));
|
||||
try {
|
||||
if (objs.empty()) {
|
||||
std::string name = doc->getUniqueObjectName("Link");
|
||||
Command::doCommand(
|
||||
Command::Doc,
|
||||
"App.getDocument('%s').addObject('App::Link','%s')",
|
||||
doc->getName(),
|
||||
name.c_str()
|
||||
);
|
||||
Selection().addSelection(doc->getName(), name.c_str());
|
||||
}
|
||||
else {
|
||||
for (auto obj : objs) {
|
||||
std::string name = doc->getUniqueObjectName("Link");
|
||||
Command::doCommand(
|
||||
Command::Doc,
|
||||
"App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument('%s'"
|
||||
").%s)",
|
||||
doc->getName(),
|
||||
name.c_str(),
|
||||
obj->getDocument()->getName(),
|
||||
obj->getNameInDocument()
|
||||
);
|
||||
setLinkLabel(obj, doc->getName(), name.c_str());
|
||||
Selection().addSelection(doc->getName(), name.c_str());
|
||||
}
|
||||
}
|
||||
Selection().selStackPush();
|
||||
Command::commitCommand();
|
||||
if (objs.empty()) {
|
||||
Gui::Control().showDialog(new TaskCommandLinkDialog(exec));
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
Command::abortCommand();
|
||||
QMessageBox::critical(
|
||||
getMainWindow(),
|
||||
QObject::tr("Create link failed"),
|
||||
QString::fromLatin1(e.what())
|
||||
);
|
||||
e.reportException();
|
||||
else {
|
||||
Selection().selStackPush();
|
||||
Selection().clearCompleteSelection();
|
||||
exec(std::vector<App::DocumentObject*>(objs.begin(), objs.end()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +488,7 @@ static void linkConvert(bool unlink)
|
||||
|
||||
// now, do actual operation
|
||||
const char* transactionName = unlink ? "Unlink" : "Replace with link";
|
||||
Command::openCommand(transactionName);
|
||||
int tid = 0;
|
||||
try {
|
||||
std::unordered_map<App::DocumentObject*, App::DocumentObjectT> recomputeSet;
|
||||
for (auto& v : infos) {
|
||||
@@ -493,6 +505,12 @@ static void linkConvert(bool unlink)
|
||||
recomputeSet.emplace(parent, parent);
|
||||
}
|
||||
auto doc = parent->getDocument();
|
||||
|
||||
tid = doc->openTransaction(
|
||||
App::TransactionName {.name = transactionName, .temporary = false},
|
||||
tid
|
||||
);
|
||||
|
||||
App::DocumentObject* replaceObj;
|
||||
if (unlink) {
|
||||
replaceObj = obj->getLinkedObject(false);
|
||||
@@ -552,10 +570,10 @@ static void linkConvert(bool unlink)
|
||||
recomputes.front()->getDocument()->recompute(recomputes);
|
||||
}
|
||||
|
||||
Command::commitCommand();
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
Command::abortCommand();
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
auto title = unlink ? QObject::tr("Unlink failed") : QObject::tr("Replace link failed");
|
||||
QMessageBox::critical(getMainWindow(), title, QString::fromLatin1(e.what()));
|
||||
e.reportException();
|
||||
|
||||
@@ -101,6 +101,8 @@ void StdCmdPart::activated(int iMsg)
|
||||
PartName.c_str()
|
||||
);
|
||||
|
||||
commitCommand();
|
||||
|
||||
updateActive();
|
||||
}
|
||||
|
||||
|
||||
+66
-7
@@ -21,6 +21,9 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <Inventor/events/SoMouseButtonEvent.h>
|
||||
#include <Inventor/nodes/SoOrthographicCamera.h>
|
||||
#include <Inventor/nodes/SoPerspectiveCamera.h>
|
||||
@@ -394,7 +397,7 @@ void StdCmdFreezeViews::activated(int iMsg)
|
||||
else if (iMsg == 3) {
|
||||
// Create a new view
|
||||
auto* view3d = freecad_cast<View3DInventor*>(getGuiApplication()->activeView());
|
||||
if (view3d == nullptr) {
|
||||
if (!view3d) {
|
||||
Base::Console().developerError(
|
||||
"StdCmdFreezeViews",
|
||||
"Expected the active view to be View3DInventor\n"
|
||||
@@ -640,7 +643,34 @@ void StdCmdFreezeViews::languageChange()
|
||||
// Std_ToggleClipPlane
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD_AC(StdCmdToggleClipPlane)
|
||||
class StdCmdToggleClipPlane: public Gui::Command
|
||||
{
|
||||
public:
|
||||
StdCmdToggleClipPlane();
|
||||
virtual ~StdCmdToggleClipPlane()
|
||||
{}
|
||||
virtual const char* className() const
|
||||
{
|
||||
return "StdCmdToggleClipPlane";
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void activated(int iMsg);
|
||||
virtual bool isActive(void);
|
||||
virtual Gui::Action* createAction(void);
|
||||
|
||||
private:
|
||||
StdCmdToggleClipPlane(const StdCmdToggleClipPlane&) = delete;
|
||||
StdCmdToggleClipPlane(StdCmdToggleClipPlane&&) = delete;
|
||||
StdCmdToggleClipPlane& operator=(const StdCmdToggleClipPlane&) = delete;
|
||||
StdCmdToggleClipPlane& operator=(StdCmdToggleClipPlane&&) = delete;
|
||||
|
||||
void garbageCollect();
|
||||
bool hasClipping(App::Document* doc) const;
|
||||
|
||||
private:
|
||||
std::vector<std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>> clippings;
|
||||
};
|
||||
|
||||
StdCmdToggleClipPlane::StdCmdToggleClipPlane()
|
||||
: Command("Std_ToggleClipPlane")
|
||||
@@ -663,11 +693,15 @@ Action* StdCmdToggleClipPlane::createAction()
|
||||
void StdCmdToggleClipPlane::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
static QPointer<Gui::Dialog::Clipping> clipping = nullptr;
|
||||
if (!clipping) {
|
||||
App::Document* doc = getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
garbageCollect(); // remove dead pointers
|
||||
if (!hasClipping(doc)) {
|
||||
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
|
||||
if (view) {
|
||||
clipping = Gui::Dialog::Clipping::makeDockWidget(view);
|
||||
clippings.push_back(std::make_pair(doc, Gui::Dialog::Clipping::makeDockWidget(view, doc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,6 +712,31 @@ bool StdCmdToggleClipPlane::isActive()
|
||||
return view ? true : false;
|
||||
}
|
||||
|
||||
void StdCmdToggleClipPlane::garbageCollect()
|
||||
{
|
||||
// We assume the vector to be small
|
||||
std::vector<std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>> newClippings;
|
||||
newClippings.reserve(clippings.size());
|
||||
std::copy_if(
|
||||
clippings.begin(),
|
||||
clippings.end(),
|
||||
std::back_inserter(newClippings),
|
||||
[](const std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>& clipPair) -> bool {
|
||||
return clipPair.second != nullptr;
|
||||
}
|
||||
);
|
||||
clippings = newClippings;
|
||||
}
|
||||
bool StdCmdToggleClipPlane::hasClipping(App::Document* doc) const
|
||||
{
|
||||
return std::ranges::find(
|
||||
clippings,
|
||||
doc,
|
||||
&std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>::first
|
||||
)
|
||||
!= clippings.end();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// StdCmdDrawStyle
|
||||
//===========================================================================
|
||||
@@ -2639,7 +2698,7 @@ void StdCmdViewIvIssueCamPos::activated(int iMsg)
|
||||
Q_UNUSED(iMsg);
|
||||
|
||||
auto* view3d = freecad_cast<View3DInventor*>(getGuiApplication()->activeView());
|
||||
if (view3d == nullptr) {
|
||||
if (!view3d) {
|
||||
Base::Console().developerError(
|
||||
"StdCmdViewIvIssueCameraPos",
|
||||
"Expected the active view to be View3DInventor\n"
|
||||
@@ -3431,7 +3490,7 @@ StdCmdTextureMapping::StdCmdTextureMapping()
|
||||
void StdCmdTextureMapping::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
Gui::Control().showDialog(new Gui::Dialog::TaskTextureMapping);
|
||||
Gui::Control().showDialog(new Gui::Dialog::TaskTextureMapping, getDocument());
|
||||
}
|
||||
|
||||
bool StdCmdTextureMapping::isActive()
|
||||
|
||||
+110
-51
@@ -26,10 +26,12 @@
|
||||
#include <QDockWidget>
|
||||
#include <QPointer>
|
||||
|
||||
#include <App/AutoTransaction.h>
|
||||
#include <App/Document.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/ComboView.h>
|
||||
#include <Gui/DockWindowManager.h>
|
||||
#include <Gui/MainWindow.h>
|
||||
#include <Gui/Document.h>
|
||||
|
||||
#include "Control.h"
|
||||
#include "BitmapFactory.h"
|
||||
@@ -45,8 +47,7 @@ using namespace std;
|
||||
ControlSingleton* ControlSingleton::_pcSingleton = nullptr;
|
||||
|
||||
ControlSingleton::ControlSingleton()
|
||||
: ActiveDialog(nullptr)
|
||||
, oldTabIndex(-1)
|
||||
: oldTabIndex(-1)
|
||||
{}
|
||||
|
||||
ControlSingleton::~ControlSingleton() = default;
|
||||
@@ -145,14 +146,28 @@ void ControlSingleton::showModelView()
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg)
|
||||
void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg, App::Document* attachTo)
|
||||
{
|
||||
attachTo = docOrDefault(attachTo);
|
||||
if (!attachTo) {
|
||||
qWarning() << "ControlSingleton::showDialog: Cannot attach to nullptr document";
|
||||
return;
|
||||
}
|
||||
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
// should return the pointer to combo view
|
||||
if (!taskView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only one dialog at a time, print a warning instead of raising an assert
|
||||
if (ActiveDialog && ActiveDialog != dlg) {
|
||||
TaskView::TaskDialog* foundDialog = taskView->dialog(attachTo);
|
||||
if (!dlg || foundDialog == dlg) {
|
||||
if (dlg) {
|
||||
qWarning() << "ControlSingleton::showDialog: Can't show "
|
||||
<< dlg->metaObject()->className()
|
||||
<< " since there is already an active task dialog";
|
||||
<< " since there is already an active task dialog in Document "
|
||||
<< (attachTo ? attachTo->getName() : "''");
|
||||
}
|
||||
else {
|
||||
qWarning() << "ControlSingleton::showDialog: Task dialog is null";
|
||||
@@ -160,69 +175,87 @@ void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg)
|
||||
return;
|
||||
}
|
||||
|
||||
// Since the caller sets up a modeless task panel, it indicates intention
|
||||
// for prolonged editing. So disable auto transaction in the current call
|
||||
// stack.
|
||||
// Do this before showing the dialog because its open() function is called
|
||||
// which may open a transaction but fails when auto transaction is still active.
|
||||
App::AutoTransaction::setEnable(false);
|
||||
bool addedDialog = taskView->showDialog(dlg, attachTo);
|
||||
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
// should return the pointer to combo view
|
||||
if (taskView) {
|
||||
taskView->showDialog(dlg);
|
||||
|
||||
// make sure that the combo view is shown
|
||||
auto dw = qobject_cast<QDockWidget*>(taskView->parentWidget());
|
||||
if (dw) {
|
||||
aboutToShowDialog(dw);
|
||||
dw->setVisible(true);
|
||||
dw->toggleViewAction()->setVisible(true);
|
||||
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
|
||||
}
|
||||
|
||||
if (ActiveDialog == dlg) {
|
||||
return; // dialog is already defined
|
||||
}
|
||||
ActiveDialog = dlg;
|
||||
connect(dlg, &TaskView::TaskDialog::aboutToBeDestroyed, this, &ControlSingleton::closedDialog);
|
||||
// make sure that the combo view is shown
|
||||
if (auto dw = qobject_cast<QDockWidget*>(taskView->parentWidget())) {
|
||||
aboutToShowDialog(dw);
|
||||
dw->setVisible(true);
|
||||
dw->toggleViewAction()->setVisible(true);
|
||||
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
|
||||
}
|
||||
|
||||
if (!addedDialog) {
|
||||
return; // dialog is already defined
|
||||
}
|
||||
|
||||
connect(dlg, &TaskView::TaskDialog::aboutToBeDestroyed, this, [this, attachTo] {
|
||||
closedDialog(attachTo);
|
||||
});
|
||||
}
|
||||
|
||||
Gui::TaskView::TaskDialog* ControlSingleton::activeDialog() const
|
||||
Gui::TaskView::TaskDialog* ControlSingleton::activeDialog(App::Document* attachedTo) const
|
||||
{
|
||||
return ActiveDialog;
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
|
||||
if (taskView) {
|
||||
return taskView->dialog(attachedTo);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ControlSingleton::accept()
|
||||
void ControlSingleton::accept(App::Document* attachedTo)
|
||||
{
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
qWarning() << "ControlSingleton::accept: Cannot accept dialog of nullptr document";
|
||||
return;
|
||||
}
|
||||
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
if (taskView) {
|
||||
taskView->accept();
|
||||
taskView->accept(attachedTo);
|
||||
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSingleton::reject()
|
||||
void ControlSingleton::reject(App::Document* attachedTo)
|
||||
{
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
qWarning() << "ControlSingleton::reject: Cannot reject dialog of nullptr document";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
if (taskView) {
|
||||
taskView->reject();
|
||||
taskView->reject(attachedTo);
|
||||
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSingleton::closeDialog()
|
||||
void ControlSingleton::closeDialog(App::Document* attachedTo)
|
||||
{
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
qWarning() << "ControlSingleton::closeDialog: Cannot close dialog of nullptr document";
|
||||
return;
|
||||
}
|
||||
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
if (taskView) {
|
||||
taskView->removeDialog();
|
||||
taskView->removeDialog(attachedTo);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSingleton::closedDialog()
|
||||
void ControlSingleton::closedDialog(App::Document* attachedTo)
|
||||
{
|
||||
ActiveDialog = nullptr;
|
||||
Gui::TaskView::TaskView* taskView = taskPanel();
|
||||
assert(taskView);
|
||||
|
||||
@@ -237,31 +270,57 @@ void ControlSingleton::closedDialog()
|
||||
}
|
||||
}
|
||||
|
||||
bool ControlSingleton::isAllowedAlterDocument() const
|
||||
bool ControlSingleton::isAllowedAlterDocument(App::Document* attachedTo) const
|
||||
{
|
||||
if (ActiveDialog) {
|
||||
return ActiveDialog->isAllowedAlterDocument();
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
return true;
|
||||
}
|
||||
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
|
||||
|
||||
if (dlg) {
|
||||
return dlg->isAllowedAlterDocument();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ControlSingleton::isAllowedAlterView() const
|
||||
bool ControlSingleton::isAllowedAlterView(App::Document* attachedTo) const
|
||||
{
|
||||
if (ActiveDialog) {
|
||||
return ActiveDialog->isAllowedAlterView();
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
return true;
|
||||
}
|
||||
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
|
||||
|
||||
if (dlg) {
|
||||
return dlg->isAllowedAlterView();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ControlSingleton::isAllowedAlterSelection() const
|
||||
bool ControlSingleton::isAllowedAlterSelection(App::Document* attachedTo) const
|
||||
{
|
||||
if (ActiveDialog) {
|
||||
return ActiveDialog->isAllowedAlterSelection();
|
||||
attachedTo = docOrDefault(attachedTo);
|
||||
if (!attachedTo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
|
||||
|
||||
if (dlg) {
|
||||
return dlg->isAllowedAlterSelection();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
App::Document* ControlSingleton::docOrDefault(App::Document* attachedTo)
|
||||
{
|
||||
if (!attachedTo && Application::Instance->activeDocument()) {
|
||||
attachedTo = Application::Instance->activeDocument()->getDocument();
|
||||
}
|
||||
return attachedTo;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
ControlSingleton& ControlSingleton::instance()
|
||||
|
||||
+16
-11
@@ -64,8 +64,9 @@ public:
|
||||
*/
|
||||
//@{
|
||||
/// This method starts a task dialog in the task view
|
||||
void showDialog(Gui::TaskView::TaskDialog* dlg);
|
||||
Gui::TaskView::TaskDialog* activeDialog() const;
|
||||
/// The dialog is relative to a specific document
|
||||
void showDialog(Gui::TaskView::TaskDialog* dlg, App::Document* attachTo = nullptr);
|
||||
Gui::TaskView::TaskDialog* activeDialog(App::Document* attachedTo = nullptr) const;
|
||||
// void closeDialog();
|
||||
//@}
|
||||
|
||||
@@ -81,28 +82,29 @@ public:
|
||||
If a task dialog is open then it indicates whether this task dialog allows other commands to
|
||||
modify the document while it is open. If no task dialog is open true is returned.
|
||||
*/
|
||||
bool isAllowedAlterDocument() const;
|
||||
bool isAllowedAlterDocument(App::Document* attachedTo = nullptr) const;
|
||||
/*!
|
||||
If a task dialog is open then it indicates whether this task dialog allows other commands to
|
||||
modify the 3d view while it is open. If no task dialog is open true is returned.
|
||||
*/
|
||||
bool isAllowedAlterView() const;
|
||||
bool isAllowedAlterView(App::Document* attachedTo = nullptr) const;
|
||||
/*!
|
||||
If a task dialog is open then it indicates whether this task dialog allows other commands to
|
||||
modify the selection while it is open. If no task dialog is open true is returned.
|
||||
*/
|
||||
bool isAllowedAlterSelection() const;
|
||||
bool isAllowedAlterSelection(App::Document* attachedTo = nullptr) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void accept();
|
||||
void reject();
|
||||
void closeDialog();
|
||||
void accept(App::Document* attachedTo = nullptr);
|
||||
void reject(App::Document* attachedTo = nullptr);
|
||||
void closeDialog(App::Document* attachedTo = nullptr);
|
||||
|
||||
/// raises the task view panel
|
||||
void showTaskView();
|
||||
|
||||
private Q_SLOTS:
|
||||
private:
|
||||
/// This get called by the TaskView when the Dialog is finished
|
||||
void closedDialog();
|
||||
void closedDialog(App::Document* attachedTo = nullptr);
|
||||
|
||||
private:
|
||||
struct status
|
||||
@@ -112,7 +114,7 @@ private:
|
||||
|
||||
std::stack<status> StatusStack;
|
||||
|
||||
Gui::TaskView::TaskDialog* ActiveDialog;
|
||||
std::map<App::Document*, Gui::TaskView::TaskDialog*> ActiveDialogs;
|
||||
int oldTabIndex;
|
||||
|
||||
private:
|
||||
@@ -125,6 +127,9 @@ private:
|
||||
void aboutToShowDialog(QDockWidget* widget);
|
||||
void aboutToHideDialog(QDockWidget* widget);
|
||||
|
||||
// Returns attachTo if not nullptr, otherwise return the active document
|
||||
static App::Document* docOrDefault(App::Document* attachedTo);
|
||||
|
||||
static ControlSingleton* _pcSingleton;
|
||||
};
|
||||
|
||||
|
||||
@@ -194,14 +194,13 @@ void DemoMode::onAngleSliderValueChanged(int v)
|
||||
void DemoMode::reorientCamera(SoCamera* cam, const SbRotation& rot)
|
||||
{
|
||||
// Find global coordinates of focal point.
|
||||
SbVec3f direction;
|
||||
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
|
||||
SbVec3f focalpoint = cam->position.getValue() + cam->focalDistance.getValue() * direction;
|
||||
SbVec3f focalpoint = activeView()->getViewer()->getFocalPoint();
|
||||
|
||||
// Set new orientation value by accumulating the new rotation.
|
||||
cam->orientation = rot * cam->orientation.getValue();
|
||||
|
||||
// Reposition camera so we are still pointing at the same old focal point.
|
||||
SbVec3f direction;
|
||||
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
|
||||
cam->position = focalpoint - cam->focalDistance.getValue() * direction;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ int DlgAddProperty::findLabelRow(const char* labelName, QFormLayout* layout)
|
||||
{
|
||||
for (int row = 0; row < layout->rowCount(); ++row) {
|
||||
QLayoutItem* labelItem = layout->itemAt(row, QFormLayout::LabelRole);
|
||||
if (labelItem == nullptr) {
|
||||
if (!labelItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ void DlgAddProperty::removeExistingWidget(QFormLayout* formLayout, int labelRow)
|
||||
void DlgAddProperty::setWidgetForLabel(const char* labelName, QWidget* widget, QLayout* layout)
|
||||
{
|
||||
auto formLayout = qobject_cast<QFormLayout*>(layout);
|
||||
if (formLayout == nullptr) {
|
||||
if (!formLayout) {
|
||||
FC_ERR("Form layout not found");
|
||||
return;
|
||||
}
|
||||
@@ -403,7 +403,7 @@ void DlgAddProperty::addEditor(PropertyItem* propertyItem)
|
||||
else {
|
||||
addNormalEditor(propertyItem);
|
||||
}
|
||||
if (editor == nullptr) {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -451,12 +451,12 @@ bool DlgAddProperty::isExcluded(const Base::Type& type) const
|
||||
|
||||
bool DlgAddProperty::isTypeWithEditor(PropertyItem* propertyItem) const
|
||||
{
|
||||
if (propertyItem == nullptr) {
|
||||
if (!propertyItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
App::Property* prop = propertyItem->getFirstProperty();
|
||||
if (prop == nullptr) {
|
||||
if (!prop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -689,7 +689,7 @@ void DlgAddProperty::showStatusMessage()
|
||||
|
||||
void DlgAddProperty::removeEditor()
|
||||
{
|
||||
if (editor == nullptr) {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@ QVariant DlgAddProperty::getEditorData() const
|
||||
{
|
||||
if (isEnumPropertyItem()) {
|
||||
PropertyItem* child = propertyItem->child(0);
|
||||
if (child == nullptr) {
|
||||
if (!child) {
|
||||
return {};
|
||||
}
|
||||
return child->editorData(editor.get());
|
||||
@@ -726,7 +726,7 @@ void DlgAddProperty::setEditorData(const QVariant& data)
|
||||
{
|
||||
if (isEnumPropertyItem()) {
|
||||
PropertyItem* child = propertyItem->child(0);
|
||||
if (child == nullptr) {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
child->setEditorData(editor.get(), data);
|
||||
@@ -741,7 +741,7 @@ void DlgAddProperty::setEditor(bool valueNeedsReset)
|
||||
if (editor && !valueNeedsReset) {
|
||||
QVariant data = getEditorData();
|
||||
addEditor(propertyItem.get());
|
||||
if (editor == nullptr) {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
setEditorData(data);
|
||||
@@ -762,15 +762,15 @@ void DlgAddProperty::setEditor(bool valueNeedsReset)
|
||||
|
||||
void DlgAddProperty::setPropertyItem(App::Property* prop, bool supportsExpressions)
|
||||
{
|
||||
if (prop == nullptr) {
|
||||
if (!prop) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyItem == nullptr) {
|
||||
if (!propertyItem) {
|
||||
propertyItem.reset(createPropertyItem(prop));
|
||||
}
|
||||
|
||||
if (propertyItem == nullptr) {
|
||||
if (!propertyItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -816,7 +816,7 @@ bool DlgAddProperty::clearBoundProperty()
|
||||
|
||||
bool DlgAddProperty::clear(FieldChange fieldChange)
|
||||
{
|
||||
if (propertyItem == nullptr) {
|
||||
if (!propertyItem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -890,7 +890,7 @@ void DlgAddProperty::changeEvent(QEvent* e)
|
||||
void DlgAddProperty::valueChangedEnum()
|
||||
{
|
||||
auto* propEnum = static_cast<App::PropertyEnumeration*>(propertyItem->getFirstProperty());
|
||||
if (propEnum == nullptr || propertyItem->childCount() == 0) {
|
||||
if (!propEnum || propertyItem->childCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -920,7 +920,9 @@ void DlgAddProperty::valueChanged()
|
||||
*/
|
||||
void DlgAddProperty::openTransaction()
|
||||
{
|
||||
transactionID = App::GetApplication().setActiveTransaction("Add property");
|
||||
transactionID = App::GetApplication().setActiveTransaction(
|
||||
App::TransactionName {.name = "Add property", .temporary = false}
|
||||
);
|
||||
}
|
||||
|
||||
void DlgAddProperty::critical(const QString& title, const QString& text)
|
||||
@@ -993,7 +995,12 @@ void DlgAddProperty::closeTransaction(TransactionOption option)
|
||||
return;
|
||||
}
|
||||
|
||||
App::GetApplication().closeActiveTransaction(static_cast<bool>(option), transactionID);
|
||||
if (option == TransactionOption::Abort) {
|
||||
App::GetApplication().abortTransaction(transactionID);
|
||||
}
|
||||
else {
|
||||
App::GetApplication().commitTransaction(transactionID);
|
||||
}
|
||||
transactionID = 0;
|
||||
}
|
||||
|
||||
@@ -1019,13 +1026,13 @@ void DlgAddProperty::addDocumentation()
|
||||
std::string group = comboBoxGroup.currentText().toStdString();
|
||||
std::string doc = ui->lineEditToolTip->text().toStdString();
|
||||
|
||||
if (propertyItem == nullptr) {
|
||||
if (!propertyItem) {
|
||||
// If there is no property item, we cannot add documentation.
|
||||
return;
|
||||
}
|
||||
|
||||
App::Property* prop = propertyItem->getFirstProperty();
|
||||
if (prop == nullptr) {
|
||||
if (!prop) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -697,7 +697,7 @@ static App::Document* getPreselectedDocument()
|
||||
}
|
||||
|
||||
App::Document* doc = App::GetApplication().getDocument(lastDoc.c_str());
|
||||
if (doc == nullptr) {
|
||||
if (!doc) {
|
||||
return App::GetApplication().getActiveDocument();
|
||||
}
|
||||
|
||||
@@ -728,7 +728,7 @@ int DlgExpressionInput::getVarSetIndex(const App::Document* doc) const
|
||||
void DlgExpressionInput::preselectVarSet()
|
||||
{
|
||||
const App::Document* doc = getPreselectedDocument();
|
||||
if (doc == nullptr) {
|
||||
if (!doc) {
|
||||
FC_ERR("No active document found");
|
||||
}
|
||||
ui->comboBoxVarSet->setCurrentIndex(getVarSetIndex(doc));
|
||||
@@ -763,7 +763,7 @@ static void addVarSetsVarSetComboBox(
|
||||
auto* vp = freecad_cast<Gui::ViewProviderDocumentObject*>(
|
||||
Gui::Application::Instance->getViewProvider(varSet)
|
||||
);
|
||||
if (vp == nullptr) {
|
||||
if (!vp) {
|
||||
FC_ERR("No ViewProvider found for VarSet: " << varSet->getNameInDocument());
|
||||
continue;
|
||||
}
|
||||
@@ -889,13 +889,13 @@ void DlgExpressionInput::onVarSetSelected(int /*index*/)
|
||||
}
|
||||
|
||||
App::Document* doc = App::GetApplication().getDocument(docName.toUtf8());
|
||||
if (doc == nullptr) {
|
||||
if (!doc) {
|
||||
FC_ERR("Document not found: " << docName.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
App::DocumentObject* varSet = doc->getObject(varSetName.toUtf8());
|
||||
if (varSet == nullptr) {
|
||||
if (!varSet) {
|
||||
FC_ERR("Variable set not found: " << varSetName.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
+61
-27
@@ -90,6 +90,7 @@ struct DocumentP
|
||||
bool _isClosing;
|
||||
bool _isModified;
|
||||
bool _isTransacting;
|
||||
bool _isActive;
|
||||
bool _changeViewTouchDocument;
|
||||
bool _editWantsRestore;
|
||||
bool _editWantsRestorePrevious;
|
||||
@@ -101,6 +102,7 @@ struct DocumentP
|
||||
ViewProviderDocumentObject* _editViewProviderParent;
|
||||
std::string _editSubname;
|
||||
std::string _editSubElement;
|
||||
std::string _workbenchName; // Name of the workbench acting on this document
|
||||
Base::Matrix4D _editingTransform;
|
||||
View3DInventorViewer* _editingViewer;
|
||||
std::set<const App::DocumentObject*> _editObjs;
|
||||
@@ -284,6 +286,8 @@ struct DocumentP
|
||||
{
|
||||
_editingObject = sobj;
|
||||
_editMode = ModNum;
|
||||
_editViewProvider = svp; // Used to resolve start editing (find the document in edit from
|
||||
// within the viewprovider)
|
||||
_editViewProvider = svp->startEditing(ModNum);
|
||||
if (!_editViewProvider) {
|
||||
_editViewProviderParent = nullptr;
|
||||
@@ -313,7 +317,7 @@ struct DocumentP
|
||||
|
||||
void setDocumentNameOfTaskDialog(App::Document* doc)
|
||||
{
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(_pcDocument);
|
||||
if (dlg) {
|
||||
dlg->setDocumentName(doc->getName());
|
||||
}
|
||||
@@ -432,6 +436,7 @@ Document::Document(App::Document* pcDocument, Application* app)
|
||||
d->_isClosing = false;
|
||||
d->_isModified = false;
|
||||
d->_isTransacting = false;
|
||||
d->_isActive = false;
|
||||
d->_pcAppWnd = app;
|
||||
d->_pcDocument = pcDocument;
|
||||
d->_editViewProvider = nullptr;
|
||||
@@ -690,7 +695,6 @@ bool Document::trySetEdit(Gui::ViewProvider* p, int ModNum, const char* subname)
|
||||
d->setEditingViewerIfPossible(view3d, ModNum);
|
||||
d->signalEditMode();
|
||||
|
||||
App::AutoTransaction::setEnable(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -717,7 +721,7 @@ void Document::resetEdit()
|
||||
Gui::ViewProvider* vpToRestore = d->_editViewProviderPrevious;
|
||||
bool shouldRestorePrevious = d->_editWantsRestorePrevious;
|
||||
|
||||
Application::Instance->setEditDocument(nullptr);
|
||||
Application::Instance->unsetEditDocument(this);
|
||||
|
||||
if (vpIsNotNull && vpHasChanged && shouldRestorePrevious) {
|
||||
setEdit(vpToRestore, modeToRestore);
|
||||
@@ -755,18 +759,16 @@ void Document::_resetEdit()
|
||||
|
||||
// The logic below is not necessary anymore, because this method is
|
||||
// changed into a private one, _resetEdit(). And the exposed
|
||||
// resetEdit() above calls into Application->setEditDocument(0) which
|
||||
// resetEdit() above calls into Application->unsetEditDocument() which
|
||||
// will prevent recursive calling.
|
||||
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
App::GetApplication().commitTransaction(getDocument()->getBookedTransactionID());
|
||||
}
|
||||
d->_editViewProviderParent = nullptr;
|
||||
d->_editingViewer = nullptr;
|
||||
d->_editObjs.clear();
|
||||
d->_editingObject = nullptr;
|
||||
if (Application::Instance->editDocument() == this) {
|
||||
Application::Instance->setEditDocument(nullptr);
|
||||
}
|
||||
Application::Instance->unsetEditDocument(this);
|
||||
}
|
||||
|
||||
ViewProvider* Document::getInEdit(
|
||||
@@ -799,6 +801,10 @@ ViewProvider* Document::getInEdit(
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
ViewProvider* Document::getEditViewProvider() const
|
||||
{
|
||||
return d->_editViewProvider;
|
||||
}
|
||||
|
||||
void Document::setInEdit(ViewProviderDocumentObject* parentVp, const char* subname)
|
||||
{
|
||||
@@ -1068,12 +1074,11 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj)
|
||||
if (d->_editViewProvider == viewProvider || d->_editViewProviderParent == viewProvider) {
|
||||
_resetEdit();
|
||||
}
|
||||
else if (Application::Instance->editDocument()) {
|
||||
auto editDoc = Application::Instance->editDocument();
|
||||
if (editDoc->d->_editViewProvider == viewProvider
|
||||
|| editDoc->d->_editViewProviderParent == viewProvider) {
|
||||
Application::Instance->setEditDocument(nullptr);
|
||||
}
|
||||
else {
|
||||
Application::Instance->unsetEditDocumentIf([&viewProvider](Gui::Document* editdoc) {
|
||||
return editdoc->d->_editViewProvider == viewProvider
|
||||
|| editdoc->d->_editViewProviderParent == viewProvider;
|
||||
});
|
||||
}
|
||||
|
||||
handleChildren3D(viewProvider, true);
|
||||
@@ -1096,15 +1101,13 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj)
|
||||
|
||||
void Document::beforeDelete()
|
||||
{
|
||||
auto editDoc = Application::Instance->editDocument();
|
||||
if (editDoc) {
|
||||
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editDoc) {
|
||||
auto vp = freecad_cast<ViewProviderDocumentObject*>(editDoc->d->_editViewProvider);
|
||||
auto vpp = freecad_cast<ViewProviderDocumentObject*>(editDoc->d->_editViewProviderParent);
|
||||
if (editDoc == this || (vp && vp->getDocument() == this)
|
||||
|| (vpp && vpp->getDocument() == this)) {
|
||||
Application::Instance->setEditDocument(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return editDoc == this || (vp && vp->getDocument() == this)
|
||||
|| (vpp && vpp->getDocument() == this);
|
||||
});
|
||||
for (auto& v : d->_ViewProviderMap) {
|
||||
v.second->beforeDelete();
|
||||
}
|
||||
@@ -1251,6 +1254,18 @@ void Document::slotSkipRecompute(const App::Document& doc, const std::vector<App
|
||||
return;
|
||||
}
|
||||
App::DocumentObject* obj = nullptr;
|
||||
|
||||
if (Gui::Application::Instance->isInEdit(this)) {
|
||||
auto vp = freecad_cast<ViewProviderDocumentObject*>(getInEdit());
|
||||
if (vp) {
|
||||
obj = vp->getObject();
|
||||
}
|
||||
}
|
||||
if (objs.size() > 1 || App::GetApplication().getActiveDocument() != &doc
|
||||
|| !doc.testStatus(App::Document::AllowPartialRecompute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto editDoc = Application::Instance->editDocument();
|
||||
if (editDoc) {
|
||||
auto vp = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
|
||||
@@ -1329,6 +1344,14 @@ bool Document::isModified() const
|
||||
{
|
||||
return d->_isModified;
|
||||
}
|
||||
void Document::setWorkbench(const std::string& name)
|
||||
{
|
||||
d->_workbenchName = name;
|
||||
}
|
||||
std::string Document::workbench() const
|
||||
{
|
||||
return d->_workbenchName;
|
||||
}
|
||||
|
||||
bool Document::isAboutToClose() const
|
||||
{
|
||||
@@ -1399,6 +1422,17 @@ App::Document* Document::getDocument() const
|
||||
{
|
||||
return d->_pcDocument;
|
||||
}
|
||||
void Document::setIsActive(bool active)
|
||||
{
|
||||
d->_isActive = active;
|
||||
if (d->_editViewProvider) {
|
||||
d->_editViewProvider->setActive(active);
|
||||
}
|
||||
}
|
||||
bool Document::isActive() const
|
||||
{
|
||||
return d->_isActive;
|
||||
}
|
||||
|
||||
static bool checkCanonicalPath(const std::map<App::Document*, bool>& docs)
|
||||
{
|
||||
@@ -1568,7 +1602,7 @@ bool Document::save()
|
||||
for (auto doc : docs) {
|
||||
// Changed 'mustExecute' status may be triggered by saving external document
|
||||
if (!dmap[doc] && doc->mustExecute()) {
|
||||
App::AutoTransaction trans("Recompute");
|
||||
App::AutoTransaction trans(doc, "Recompute");
|
||||
Command::doCommand(
|
||||
Command::Doc,
|
||||
"App.getDocument(\"%s\").recompute()",
|
||||
@@ -1711,7 +1745,7 @@ void Document::saveAll()
|
||||
try {
|
||||
// Changed 'mustExecute' status may be triggered by saving external document
|
||||
if (!dmap[doc] && doc->mustExecute()) {
|
||||
App::AutoTransaction trans("Recompute");
|
||||
App::AutoTransaction trans(doc, "Recompute");
|
||||
Command::doCommand(Command::Doc, "App.getDocument('%s').recompute()", doc->getName());
|
||||
}
|
||||
Command::doCommand(Command::Doc, "App.getDocument('%s').save()", doc->getName());
|
||||
@@ -2476,8 +2510,8 @@ bool Document::canClose(bool checkModify, bool checkLink)
|
||||
// If a task dialog is open that doesn't allow other commands to modify
|
||||
// the document it must be closed by resetting the edit mode of the
|
||||
// corresponding view provider.
|
||||
if (!Gui::Control().isAllowedAlterDocument()) {
|
||||
std::string name = Gui::Control().activeDialog()->getDocumentName();
|
||||
if (!Gui::Control().isAllowedAlterDocument(getDocument())) {
|
||||
std::string name = Gui::Control().activeDialog(getDocument())->getDocumentName();
|
||||
if (name == this->getDocument()->getName()) {
|
||||
// getInEdit() only checks if the currently active MDI view is
|
||||
// a 3D view and that it is in edit mode. However, when closing a
|
||||
@@ -2733,9 +2767,9 @@ Gui::MDIView* Document::getEditingViewOfViewProvider(Gui::ViewProvider* vp) cons
|
||||
* operation default is the command name.
|
||||
* @see CommitCommand(),AbortCommand()
|
||||
*/
|
||||
void Document::openCommand(const char* sName)
|
||||
int Document::openCommand(const char* sName)
|
||||
{
|
||||
getDocument()->openTransaction(sName);
|
||||
return getDocument()->openTransaction(App::TransactionName {.name = sName, .temporary = false});
|
||||
}
|
||||
|
||||
void Document::commitCommand()
|
||||
|
||||
+12
-1
@@ -191,12 +191,21 @@ public:
|
||||
void setModified(bool);
|
||||
bool isModified() const;
|
||||
|
||||
/// getter-setter for workbench name
|
||||
void setWorkbench(const std::string& name);
|
||||
std::string workbench() const;
|
||||
|
||||
/// Returns true if the document is about to be closed, false otherwise
|
||||
bool isAboutToClose() const;
|
||||
|
||||
/// Getter for the App Document
|
||||
App::Document* getDocument() const;
|
||||
|
||||
/// Notify the document when it becomes
|
||||
/// the active document/stops being the active document
|
||||
void setIsActive(bool active);
|
||||
bool isActive() const;
|
||||
|
||||
/** @name methods for View handling */
|
||||
//@{
|
||||
/// Getter for the active view
|
||||
@@ -287,6 +296,8 @@ public:
|
||||
int* mode = nullptr,
|
||||
std::string* subElement = nullptr
|
||||
) const;
|
||||
ViewProvider* getEditViewProvider() const; // Returns the _editViewProvider even if it is not
|
||||
// in edit at the moment
|
||||
/// set the in edit ViewProvider subname reference
|
||||
void setInEdit(ViewProviderDocumentObject* parentVp, const char* subname);
|
||||
/** Add or remove view provider from scene graphs of all views
|
||||
@@ -300,7 +311,7 @@ public:
|
||||
/** @name methods for the UNDO REDO handling */
|
||||
//@{
|
||||
/// Open a new Undo transaction on the document
|
||||
void openCommand(const char* sName = nullptr);
|
||||
int openCommand(const char* sName = nullptr);
|
||||
/// Commit the Undo transaction on the document
|
||||
void commitCommand();
|
||||
/// Abort the Undo transaction on the document
|
||||
|
||||
@@ -187,6 +187,31 @@ class Document(Persistence):
|
||||
obj : Gui.ViewProvider
|
||||
"""
|
||||
...
|
||||
|
||||
def openCommand(self, name: str) -> int:
|
||||
"""
|
||||
openCommand(name) -> int
|
||||
|
||||
Opens a named transaction for the document and returns it's
|
||||
id or 0 on failure
|
||||
"""
|
||||
...
|
||||
|
||||
def commitCommand(self) -> None:
|
||||
"""
|
||||
commitCommand() -> None
|
||||
|
||||
Commits the current transaction of the document
|
||||
"""
|
||||
...
|
||||
|
||||
def abortCommand(self) -> None:
|
||||
"""
|
||||
abortCommand() -> None
|
||||
|
||||
Aborts the current transaction of the document
|
||||
"""
|
||||
...
|
||||
ActiveObject: Any = ...
|
||||
"""The active object of the document."""
|
||||
|
||||
|
||||
@@ -577,6 +577,34 @@ Py::Long DocumentPy::getEditMode() const
|
||||
return Py::Long(mode);
|
||||
}
|
||||
|
||||
PyObject* DocumentPy::openCommand(PyObject* arg, PyObject* kwd)
|
||||
{
|
||||
const char* name = nullptr;
|
||||
if (!PyArg_ParseTuple(arg, "s", &name)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
int tid = getDocumentPtr()->openCommand(name);
|
||||
|
||||
return Py::new_reference_to(Py::Long(tid));
|
||||
}
|
||||
PyObject* DocumentPy::commitCommand(PyObject* args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
getDocumentPtr()->commitCommand();
|
||||
|
||||
Py_Return;
|
||||
}
|
||||
PyObject* DocumentPy::abortCommand(PyObject* args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, "")) {
|
||||
return nullptr;
|
||||
}
|
||||
getDocumentPtr()->abortCommand();
|
||||
|
||||
Py_Return;
|
||||
}
|
||||
Py::Boolean DocumentPy::getTransacting() const
|
||||
{
|
||||
return {getDocumentPtr()->isPerformingTransaction()};
|
||||
|
||||
@@ -75,11 +75,11 @@ void Gui::ExpressionBinding::setExpression(std::shared_ptr<Expression> expr)
|
||||
|
||||
lastExpression = getExpression();
|
||||
|
||||
bool transaction = !App::GetApplication().getActiveTransaction();
|
||||
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
|
||||
if (transaction) {
|
||||
std::ostringstream ss;
|
||||
ss << (expr ? "Set" : "Discard") << " expression " << docObj->Label.getValue();
|
||||
App::GetApplication().setActiveTransaction(ss.str().c_str());
|
||||
docObj->getDocument()->openTransaction(ss.str().c_str());
|
||||
}
|
||||
|
||||
docObj->ExpressionEngine.setValue(path, expr);
|
||||
@@ -89,7 +89,7 @@ void Gui::ExpressionBinding::setExpression(std::shared_ptr<Expression> expr)
|
||||
}
|
||||
|
||||
if (transaction) {
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
docObj->getDocument()->commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ void ExpressionBinding::bind(const App::ObjectIdentifier& _path)
|
||||
{
|
||||
const Property* prop = _path.getProperty();
|
||||
|
||||
Q_ASSERT(prop != nullptr);
|
||||
Q_ASSERT(prop);
|
||||
|
||||
path = prop->canonicalPath(_path);
|
||||
|
||||
@@ -133,7 +133,7 @@ std::shared_ptr<App::Expression> ExpressionBinding::getExpression() const
|
||||
{
|
||||
DocumentObject* docObj = path.getDocumentObject();
|
||||
|
||||
Q_ASSERT(isBound() && docObj != nullptr);
|
||||
Q_ASSERT(isBound() && docObj);
|
||||
|
||||
return docObj->getExpression(path).expression;
|
||||
}
|
||||
@@ -214,11 +214,11 @@ bool ExpressionBinding::apply(const std::string& propName)
|
||||
throw Base::RuntimeError("Document object not found.");
|
||||
}
|
||||
|
||||
bool transaction = !App::GetApplication().getActiveTransaction();
|
||||
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
|
||||
if (transaction) {
|
||||
std::ostringstream ss;
|
||||
ss << "Set expression " << docObj->Label.getValue();
|
||||
App::GetApplication().setActiveTransaction(ss.str().c_str());
|
||||
docObj->getDocument()->openTransaction(ss.str().c_str());
|
||||
}
|
||||
Gui::Command::doCommand(
|
||||
Gui::Command::Doc,
|
||||
@@ -229,7 +229,7 @@ bool ExpressionBinding::apply(const std::string& propName)
|
||||
getEscapedExpressionString().c_str()
|
||||
);
|
||||
if (transaction) {
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
docObj->getDocument()->commitTransaction();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -242,11 +242,11 @@ bool ExpressionBinding::apply(const std::string& propName)
|
||||
}
|
||||
|
||||
if (lastExpression) {
|
||||
bool transaction = !App::GetApplication().getActiveTransaction();
|
||||
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
|
||||
if (transaction) {
|
||||
std::ostringstream ss;
|
||||
ss << "Discard expression " << docObj->Label.getValue();
|
||||
App::GetApplication().setActiveTransaction(ss.str().c_str());
|
||||
docObj->getDocument()->openTransaction(ss.str().c_str());
|
||||
}
|
||||
Gui::Command::doCommand(
|
||||
Gui::Command::Doc,
|
||||
@@ -256,7 +256,7 @@ bool ExpressionBinding::apply(const std::string& propName)
|
||||
path.toEscapedString().c_str()
|
||||
);
|
||||
if (transaction) {
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
docObj->getDocument()->commitTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ public:
|
||||
void setUserSchema(int userSchema)
|
||||
{
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (doc != nullptr) {
|
||||
if (doc) {
|
||||
if (doc->UnitSystem.getValue() != userSchema) {
|
||||
doc->UnitSystem.setValue(userSchema);
|
||||
}
|
||||
@@ -275,7 +275,7 @@ private:
|
||||
bool ignore = hGrpu->GetBool("IgnoreProjectSchema", false);
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
int userSchema = getWindowParameter()->GetInt("UserSchema", 0);
|
||||
if (doc != nullptr && !ignore) {
|
||||
if (doc && !ignore) {
|
||||
userSchema = doc->UnitSystem.getValue();
|
||||
}
|
||||
auto actions = menu()->actions();
|
||||
@@ -937,7 +937,7 @@ int MainWindow::confirmSave(App::Document* doc, QWidget* parent, bool addCheckbo
|
||||
const QList<QWidget*> listOfMDIs = this->windows();
|
||||
for (QWidget* widget : listOfMDIs) {
|
||||
auto mdiView = qobject_cast<MDIView*>(widget);
|
||||
if (mdiView != nullptr && mdiView->getAppDocument() == doc) {
|
||||
if (mdiView && mdiView->getAppDocument() == doc) {
|
||||
this->setActiveWindow(mdiView);
|
||||
}
|
||||
}
|
||||
@@ -974,7 +974,7 @@ bool MainWindow::closeAllDocuments(bool close)
|
||||
// moves the active document to the front
|
||||
MDIView* activeView = this->activeWindow();
|
||||
App::Document* activeDoc = (activeView ? activeView->getAppDocument() : nullptr);
|
||||
if (activeDoc != nullptr) {
|
||||
if (activeDoc) {
|
||||
for (auto it = ++docs.begin(); it != docs.end(); it++) {
|
||||
if (*it == activeDoc) {
|
||||
docs.erase(it);
|
||||
|
||||
@@ -1220,7 +1220,7 @@ bool NaviCubeImplementation::mouseReleased(short x, short y)
|
||||
// If the previous flat button animation is still active then apply the rotation to the
|
||||
// previous target orientation, otherwise apply the rotation to the current camera
|
||||
// orientation
|
||||
if (m_flatButtonAnimation != nullptr
|
||||
if (m_flatButtonAnimation
|
||||
&& m_flatButtonAnimation->state() == QAbstractAnimation::Running) {
|
||||
m_flatButtonTargetOrientation = rotation * m_flatButtonTargetOrientation;
|
||||
}
|
||||
|
||||
@@ -710,7 +710,7 @@ public:
|
||||
: my_base(ctx)
|
||||
{
|
||||
auto& ns = this->outermost_context().ns;
|
||||
ns.setRotationCenter(ns.getFocalPoint());
|
||||
ns.setRotationCenter(ns.viewer->getFocalPoint());
|
||||
ns.setViewingMode(NavigationStyle::DRAGGING);
|
||||
this->base_pos
|
||||
= static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition();
|
||||
|
||||
@@ -589,7 +589,7 @@ SbBool MayaGestureNavigationStyle::processSoEvent(const SoEvent* const ev)
|
||||
if (comboAfter & BUTTON1DOWN || comboAfter & BUTTON2DOWN) {
|
||||
// don't leave navigation till all buttons have been released
|
||||
if (comboAfter & BUTTON1DOWN && comboAfter & BUTTON2DOWN) {
|
||||
setRotationCenter(getFocalPoint());
|
||||
setRotationCenter(viewer->getFocalPoint());
|
||||
}
|
||||
else {
|
||||
saveCursorPosition(ev);
|
||||
|
||||
@@ -96,7 +96,7 @@ void NavigationAnimator::stop()
|
||||
*/
|
||||
bool NavigationAnimator::isAnimating() const
|
||||
{
|
||||
if (activeAnimation != nullptr) {
|
||||
if (activeAnimation) {
|
||||
return activeAnimation->state() == QAbstractAnimation::State::Running;
|
||||
}
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ void NavigationStyle::lookAtPoint(const SbVec2s screenpos)
|
||||
void NavigationStyle::lookAtPoint(const SbVec3f& position)
|
||||
{
|
||||
this->rotationCenterFound = false;
|
||||
translateCamera(position - getFocalPoint());
|
||||
translateCamera(position - viewer->getFocalPoint());
|
||||
}
|
||||
|
||||
SoCamera* NavigationStyle::getCamera() const
|
||||
@@ -524,7 +524,7 @@ std::shared_ptr<NavigationAnimation> NavigationStyle::setCameraOrientation(
|
||||
|
||||
animator->stop();
|
||||
|
||||
const SbVec3f focalPoint = getFocalPoint();
|
||||
const SbVec3f focalPoint = viewer->getFocalPoint();
|
||||
SbVec3f translation(0, 0, 0);
|
||||
|
||||
if (moveToCenter) {
|
||||
@@ -618,6 +618,7 @@ void NavigationStyle::boxZoom(const SbBox2s& box)
|
||||
|
||||
doScale(cam, scaleFactor);
|
||||
}
|
||||
|
||||
void NavigationStyle::scale(float factor)
|
||||
{
|
||||
SoCamera* cam = viewer->getSoRenderManager()->getCamera();
|
||||
@@ -626,9 +627,7 @@ void NavigationStyle::scale(float factor)
|
||||
}
|
||||
|
||||
// Find the current center of the screen
|
||||
SbVec3f direction;
|
||||
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
|
||||
SbVec3f initCenter = cam->position.getValue() + cam->focalDistance.getValue() * direction;
|
||||
SbVec3f initCenter = viewer->getFocalPoint();
|
||||
|
||||
// Move the camera to the origin for scaling
|
||||
cam->position = cam->position.getValue() - initCenter;
|
||||
@@ -702,7 +701,7 @@ void NavigationStyle::findBoundingSphere()
|
||||
*/
|
||||
void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation)
|
||||
{
|
||||
reorientCamera(camera, rotation, getFocalPoint());
|
||||
reorientCamera(camera, rotation, viewer->getFocalPoint());
|
||||
}
|
||||
|
||||
/** Rotate the camera by the given amount, then reposition it so the rotation center stays in the
|
||||
@@ -751,7 +750,7 @@ void NavigationStyle::reorientCamera(
|
||||
#else
|
||||
constexpr float orthographicFocalDistance = 1;
|
||||
#endif
|
||||
camera->position = getFocalPoint() - orthographicFocalDistance * direction;
|
||||
camera->position = viewer->getFocalPoint() - orthographicFocalDistance * direction;
|
||||
camera->focalDistance = orthographicFocalDistance;
|
||||
}
|
||||
|
||||
@@ -1004,7 +1003,7 @@ void NavigationStyle::doZoom(SoCamera* camera, float logfactor, const SbVec2f& p
|
||||
// Change the position of the rotation center indicator after zooming at cursor
|
||||
// Rotation mode is WindowCenter
|
||||
if (!rotationCenterMode) {
|
||||
viewer->changeRotationCenterPosition(getFocalPoint());
|
||||
viewer->changeRotationCenterPosition(viewer->getFocalPoint());
|
||||
|
||||
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
|
||||
findBoundingSphere();
|
||||
@@ -1083,20 +1082,6 @@ void NavigationStyle::setRotationCenter(const SbVec3f& cnt)
|
||||
}
|
||||
}
|
||||
|
||||
SbVec3f NavigationStyle::getFocalPoint() const
|
||||
{
|
||||
SoCamera* cam = viewer->getSoRenderManager()->getCamera();
|
||||
if (!cam) {
|
||||
return {0, 0, 0};
|
||||
}
|
||||
|
||||
// Find global coordinates of focal point.
|
||||
SbVec3f direction;
|
||||
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
|
||||
SbVec3f focal = cam->position.getValue() + cam->focalDistance.getValue() * direction;
|
||||
return focal;
|
||||
}
|
||||
|
||||
/** Uses the sphere sheet projector to map the mouse position onto
|
||||
* a 3D point and find a rotation from this and the last calculated point.
|
||||
*/
|
||||
@@ -1296,7 +1281,7 @@ void NavigationStyle::saveCursorPosition(const SoEvent* const ev)
|
||||
|
||||
// mode is WindowCenter
|
||||
if (!this->rotationCenterMode) {
|
||||
setRotationCenter(getFocalPoint());
|
||||
setRotationCenter(viewer->getFocalPoint());
|
||||
}
|
||||
|
||||
// Option to get point on model (slow) or always on focal plane (fast)
|
||||
|
||||
@@ -166,7 +166,6 @@ public:
|
||||
void setRotationCenterMode(RotationCenterModes);
|
||||
RotationCenterModes getRotationCenterMode() const;
|
||||
void setRotationCenter(const SbVec3f& cnt);
|
||||
SbVec3f getFocalPoint() const;
|
||||
|
||||
SoCamera* getCamera() const;
|
||||
std::shared_ptr<NavigationAnimation> setCameraOrientation(
|
||||
|
||||
@@ -1244,16 +1244,16 @@ bool OverlayTabWidget::checkAutoHide() const
|
||||
}
|
||||
}
|
||||
|
||||
bool activeDocInEdit = Application::Instance->isInEdit(Application::Instance->activeDocument());
|
||||
if (autoMode == AutoMode::EditShow) {
|
||||
return !Application::Instance->editDocument()
|
||||
&& (!Control().taskPanel() || Control().taskPanel()->isEmpty(false));
|
||||
return !activeDocInEdit && (!Control().taskPanel() || Control().taskPanel()->isEmpty(false));
|
||||
}
|
||||
|
||||
if (autoMode == AutoMode::TaskShow) {
|
||||
return (!Control().taskPanel() || Control().taskPanel()->isEmpty());
|
||||
}
|
||||
|
||||
if (autoMode == AutoMode::EditHide && Application::Instance->editDocument()) {
|
||||
if (autoMode == AutoMode::EditHide && activeDocInEdit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -351,7 +351,7 @@ void DlgSettingsLightSources::resetSettingsToDefaults()
|
||||
|
||||
void DlgSettingsLightSources::zoomIn() const
|
||||
{
|
||||
if (camera == nullptr) {
|
||||
if (!camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ void DlgSettingsLightSources::zoomIn() const
|
||||
|
||||
void DlgSettingsLightSources::zoomOut() const
|
||||
{
|
||||
if (camera == nullptr) {
|
||||
if (!camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -406,7 +406,7 @@ public:
|
||||
const auto PyW_uniqueName = QString::number(reinterpret_cast<quintptr>(pyobj));
|
||||
auto PyW_invalidator = findChild<QObject*>(PyW_uniqueName, Qt::FindDirectChildrenOnly);
|
||||
|
||||
if (PyW_invalidator == nullptr) {
|
||||
if (!PyW_invalidator) {
|
||||
PyW_invalidator = new QObject(this);
|
||||
PyW_invalidator->setObjectName(PyW_uniqueName);
|
||||
|
||||
@@ -420,7 +420,7 @@ public:
|
||||
auto destroyedFun = [pyobj]() {
|
||||
Base::PyGILStateLocker lock;
|
||||
|
||||
if (auto sbkPtr = reinterpret_cast<SbkObject*>(pyobj); sbkPtr != nullptr) {
|
||||
if (auto sbkPtr = reinterpret_cast<SbkObject*>(pyobj); sbkPtr) {
|
||||
Shiboken::Object::setValidCpp(sbkPtr, false);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -93,10 +93,5 @@ ActionGroup *ActionPanel::createGroup(const QPixmap &icon, const QString &title,
|
||||
return group;
|
||||
}
|
||||
|
||||
QSize ActionPanel::minimumSizeHint() const
|
||||
{
|
||||
return {200,150};
|
||||
}
|
||||
|
||||
} // namespace QSint
|
||||
|
||||
|
||||
@@ -89,12 +89,6 @@ public:
|
||||
*/
|
||||
void setScheme(ActionPanelScheme *scheme);
|
||||
|
||||
/**
|
||||
* @brief Returns the recommended minimum size for the panel.
|
||||
* @return The minimum size hint.
|
||||
*/
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
/** @brief The color scheme used by the panel. */
|
||||
ActionPanelScheme *myScheme;
|
||||
|
||||
+477
-355
File diff suppressed because it is too large
Load Diff
@@ -225,7 +225,7 @@ class GuiExport SelectionObserver
|
||||
public:
|
||||
/** Constructor
|
||||
*
|
||||
* @param attach: whether to attach this observer on construction
|
||||
* @param attach: whether to attach this observer on construction
|
||||
* @param resolve: sub-object resolving mode.
|
||||
*/
|
||||
explicit SelectionObserver(bool attach = true, ResolveMode resolve = ResolveMode::OldStyleElement);
|
||||
@@ -264,6 +264,7 @@ private:
|
||||
std::string filterDocName;
|
||||
std::string filterObjName;
|
||||
ResolveMode resolve;
|
||||
const char* pDocumentScopeName {nullptr};
|
||||
bool blockedSelection;
|
||||
};
|
||||
|
||||
@@ -286,20 +287,6 @@ public:
|
||||
std::string notAllowedReason;
|
||||
};
|
||||
|
||||
/** SelectionGateFilterExternal
|
||||
* The selection gate disallows any external object
|
||||
*/
|
||||
class GuiExport SelectionGateFilterExternal: public SelectionGate
|
||||
{
|
||||
public:
|
||||
explicit SelectionGateFilterExternal(const char* docName, const char* objName = nullptr);
|
||||
bool allow(App::Document*, App::DocumentObject*, const char*) override;
|
||||
|
||||
private:
|
||||
std::string DocName;
|
||||
std::string ObjName;
|
||||
};
|
||||
|
||||
/** The Selection class
|
||||
* The selection singleton keeps track of the selection state of
|
||||
* the whole application. It gets messages from all entities which can
|
||||
@@ -382,7 +369,7 @@ public:
|
||||
/// of the active document is cleared.
|
||||
void clearSelection(const char* pDocName = nullptr, bool clearPreSelect = true);
|
||||
/// Clear the selection of all documents
|
||||
void clearCompleteSelection(bool clearPreSelect = true);
|
||||
void clearCompleteSelection(const char* pDocName = nullptr, bool clearPreSelect = true);
|
||||
/// Check if selected
|
||||
bool isSelected(
|
||||
const char* pDocName,
|
||||
@@ -397,7 +384,7 @@ public:
|
||||
ResolveMode resolve = ResolveMode::OldStyleElement
|
||||
) const;
|
||||
|
||||
const char* getSelectedElement(App::DocumentObject*, const char* pSubName) const;
|
||||
std::string getSelectedElement(App::DocumentObject*, const char* pSubName) const;
|
||||
|
||||
/// set the preselected object (mostly by the 3D view)
|
||||
int setPreselect(
|
||||
@@ -415,10 +402,19 @@ public:
|
||||
void setPreselectCoord(float x, float y, float z);
|
||||
/// returns the present preselection
|
||||
const SelectionChanges& getPreselection() const;
|
||||
/// add a SelectionGate to control what is selectable
|
||||
void addSelectionGate(Gui::SelectionGate* gate, ResolveMode resolve = ResolveMode::OldStyleElement);
|
||||
/// remove the active SelectionGate
|
||||
void rmvSelectionGate();
|
||||
/// add a SelectionGate to control what is selectable in a document's scope, by default the
|
||||
/// active document is selected
|
||||
// which is usually the intended behavior
|
||||
void addSelectionGate(
|
||||
Gui::SelectionGate* gate,
|
||||
ResolveMode resolve = ResolveMode::OldStyleElement,
|
||||
const char* pDocName = nullptr
|
||||
);
|
||||
/// remove the document's SelectionGate, by default the active document is selected, which is
|
||||
/// usually the intended behavior
|
||||
void rmvSelectionGate(const char* pDocName = nullptr);
|
||||
/// remove the document's SelectionGate (assumes valid pointer)
|
||||
void rmvSelectionGate(App::Document* doc);
|
||||
|
||||
int disableCommandLog();
|
||||
int enableCommandLog(bool silent = false);
|
||||
@@ -495,8 +491,9 @@ public:
|
||||
/** Set selection object visibility
|
||||
*
|
||||
* @param visible: see VisibleState
|
||||
* @param pDocName: name of the document that scopes the request, defaults to active document
|
||||
*/
|
||||
void setVisible(VisibleState visible);
|
||||
void setVisible(VisibleState visible, const char* pDocName = nullptr);
|
||||
|
||||
bool isClarifySelectionActive();
|
||||
void setClarifySelectionActive(bool active);
|
||||
@@ -580,7 +577,7 @@ public:
|
||||
std::vector<SelObj> getCompleteSelection(ResolveMode resolve = ResolveMode::OldStyleElement) const;
|
||||
|
||||
/// Check if there is any selection
|
||||
bool hasSelection() const;
|
||||
bool hasSelection(const char* pDocName = nullptr) const;
|
||||
|
||||
/** Check if there is any selection within a given document
|
||||
*
|
||||
@@ -594,7 +591,7 @@ public:
|
||||
* If \c resolve is false, then the match is only done with the top
|
||||
* level parent object.
|
||||
*/
|
||||
bool hasSelection(const char* doc, ResolveMode resolve = ResolveMode::OldStyleElement) const;
|
||||
bool hasSelection(const char* doc, ResolveMode resolve) const;
|
||||
|
||||
/** Check if there is any sub-element selection
|
||||
*
|
||||
@@ -611,10 +608,7 @@ public:
|
||||
bool hasPreselection() const;
|
||||
|
||||
/// Size of selected entities for all documents
|
||||
unsigned int size() const
|
||||
{
|
||||
return static_cast<unsigned int>(_SelList.size());
|
||||
}
|
||||
unsigned int size(const char* pDocName = nullptr) const;
|
||||
|
||||
/** @name Selection stack functions
|
||||
*
|
||||
@@ -623,16 +617,10 @@ public:
|
||||
*/
|
||||
//@{
|
||||
/// Return the current selection stack size
|
||||
std::size_t selStackBackSize() const
|
||||
{
|
||||
return _SelStackBack.size();
|
||||
}
|
||||
std::size_t selStackBackSize(const char* pDocName = nullptr) const;
|
||||
|
||||
/// Return the current forward selection stack size
|
||||
std::size_t selStackForwardSize() const
|
||||
{
|
||||
return _SelStackForward.size();
|
||||
}
|
||||
std::size_t selStackForwardSize(const char* pDocName = nullptr) const;
|
||||
|
||||
/** Obtain selected objects from stack
|
||||
*
|
||||
@@ -650,28 +638,31 @@ public:
|
||||
/** Go back selection history
|
||||
*
|
||||
* @param count: optional number of steps to go back
|
||||
* @param pDocName: the name of the document to index the context, defaults to active document
|
||||
*
|
||||
* This function pops the selection stack, and populate the current
|
||||
* selection with the content of the last pop'd entry
|
||||
*/
|
||||
void selStackGoBack(int count = 1);
|
||||
void selStackGoBack(int count = 1, const char* pDocName = nullptr);
|
||||
|
||||
/** Go forward selection history
|
||||
*
|
||||
* @param count: optional number of steps to go back
|
||||
* @param pDocName: the name of the document to index the context, defaults to active document
|
||||
*
|
||||
* This function pops the selection stack, and populate the current
|
||||
* selection with the content of the last pop'd entry
|
||||
*/
|
||||
void selStackGoForward(int count = 1);
|
||||
void selStackGoForward(int count = 1, const char* pDocName = nullptr);
|
||||
|
||||
/** Save the current selection on to the stack
|
||||
*
|
||||
* @param clearForward: whether to clear forward selection stack
|
||||
* @param overwrite: whether to overwrite the current top entry of the
|
||||
* stack instead of pushing a new entry.
|
||||
* @param pDocName: the name of the document to index the context, defaults to active document
|
||||
*/
|
||||
void selStackPush(bool clearForward = true, bool overwrite = false);
|
||||
void selStackPush(bool clearForward = true, bool overwrite = false, const char* pDocName = nullptr);
|
||||
//@}
|
||||
|
||||
/** @name Picked list functions
|
||||
@@ -682,11 +673,11 @@ public:
|
||||
*/
|
||||
//@{
|
||||
/// Check whether picked list is enabled
|
||||
bool needPickedList() const;
|
||||
bool needPickedList(const char* pDocName = nullptr) const;
|
||||
/// Turn on or off picked list
|
||||
void enablePickedList(bool);
|
||||
void enablePickedList(bool, const char* pDocName = nullptr);
|
||||
/// Check if there is any selection inside picked list
|
||||
bool hasPickedList() const;
|
||||
bool hasPickedList(const char* pDocName = nullptr) const;
|
||||
/// Return select objects inside picked list
|
||||
std::vector<SelectionSingleton::SelObj> getPickedList(const char* pDocName) const;
|
||||
/// Return selected object inside picked list grouped by top level parents
|
||||
@@ -708,9 +699,9 @@ public:
|
||||
GreedySelection
|
||||
};
|
||||
/// Changes the style of selection between greedy and normal.
|
||||
void setSelectionStyle(SelectionStyle selStyle);
|
||||
void setSelectionStyle(SelectionStyle selStyle, const char* pDocName = nullptr);
|
||||
/// Get the style of selection.
|
||||
SelectionStyle getSelectionStyle();
|
||||
SelectionStyle getSelectionStyle(const char* pDocName = nullptr);
|
||||
//@}
|
||||
|
||||
static SelectionSingleton& instance();
|
||||
@@ -756,24 +747,20 @@ protected:
|
||||
|
||||
/// Observer message from the App doc
|
||||
void slotDeletedObject(const App::DocumentObject&);
|
||||
void slotClosedDocument(const App::Document&);
|
||||
|
||||
/// helper to retrieve document by name
|
||||
App::Document* getDocument(const char* pDocName = nullptr) const;
|
||||
|
||||
void slotSelectionChanged(const SelectionChanges& msg);
|
||||
|
||||
SelectionChanges CurrentPreselection;
|
||||
|
||||
std::deque<SelectionChanges> NotificationQueue;
|
||||
bool Notifying = false;
|
||||
|
||||
void notify(SelectionChanges&& Chng);
|
||||
void notify(const SelectionChanges& Chng)
|
||||
{
|
||||
notify(SelectionChanges(Chng));
|
||||
}
|
||||
|
||||
struct _SelObj
|
||||
struct SelectionDescription
|
||||
{
|
||||
std::string DocName;
|
||||
std::string FeatName;
|
||||
@@ -792,34 +779,26 @@ protected:
|
||||
void log(bool remove = false, bool clearPreselect = true);
|
||||
std::string getSubString() const;
|
||||
};
|
||||
mutable std::list<_SelObj> _SelList;
|
||||
|
||||
mutable std::list<_SelObj> _PickedList;
|
||||
bool _needPickedList {false};
|
||||
|
||||
using SelStackItem = std::set<App::SubObjectT>;
|
||||
std::deque<SelStackItem> _SelStackBack;
|
||||
std::deque<SelStackItem> _SelStackForward;
|
||||
|
||||
int checkSelection(
|
||||
const char* pDocName,
|
||||
const char* pObjectName,
|
||||
const char* pSubName,
|
||||
ResolveMode resolve,
|
||||
_SelObj& sel,
|
||||
const std::list<_SelObj>* selList = nullptr
|
||||
SelectionDescription& sel,
|
||||
const std::list<SelectionDescription>* selList = nullptr
|
||||
) const;
|
||||
|
||||
std::vector<Gui::SelectionObject> getObjectList(
|
||||
const char* pDocName,
|
||||
Base::Type typeId,
|
||||
std::list<_SelObj>& objs,
|
||||
const std::list<SelectionDescription>& objs,
|
||||
ResolveMode resolve,
|
||||
bool single = false
|
||||
) const;
|
||||
|
||||
static App::DocumentObject* getObjectOfType(
|
||||
_SelObj& sel,
|
||||
const SelectionDescription& sel,
|
||||
Base::Type type,
|
||||
ResolveMode resolve,
|
||||
const char** subelement = nullptr
|
||||
@@ -831,21 +810,56 @@ protected:
|
||||
ResolveMode resolve = ResolveMode::OldStyleElement
|
||||
) const;
|
||||
|
||||
using SelStackItem = std::set<App::SubObjectT>;
|
||||
// Each document has a description context
|
||||
struct SelectionInfo
|
||||
{
|
||||
Gui::SelectionGate* gate {nullptr};
|
||||
ResolveMode resolveMode {ResolveMode::OldStyleElement};
|
||||
|
||||
std::list<SelectionDescription> selList;
|
||||
std::list<SelectionDescription> pickedList;
|
||||
bool needPickedList {false};
|
||||
|
||||
std::deque<SelStackItem> selStackBack;
|
||||
std::deque<SelStackItem> selStackForward;
|
||||
|
||||
SelectionStyle selectionStyle {SelectionStyle::NormalSelection};
|
||||
};
|
||||
struct SelectionContext
|
||||
{
|
||||
SelectionInfo* info;
|
||||
std::string docName;
|
||||
};
|
||||
struct SelectionConstContext
|
||||
{
|
||||
const SelectionInfo* info;
|
||||
std::string docName;
|
||||
};
|
||||
|
||||
// Returns a selection context or nullptr if the document is not found
|
||||
SelectionContext getSelectionContext(const char* pDocName);
|
||||
SelectionConstContext getSelectionContext(const char* pDocName) const;
|
||||
|
||||
static SelectionSingleton* _pcSingleton;
|
||||
|
||||
|
||||
std::map<App::Document*, SelectionInfo> docSelectionContext;
|
||||
|
||||
// Preselection helpers, it's a mess, needs clarifying -theo-vt
|
||||
std::string DocName;
|
||||
std::string FeatName;
|
||||
std::string SubName;
|
||||
float hx, hy, hz;
|
||||
float hx {0.0f}, hy {0.0f}, hz {0.0f};
|
||||
SelectionChanges CurrentPreselection;
|
||||
|
||||
Gui::SelectionGate* ActiveGate;
|
||||
ResolveMode gateResolve;
|
||||
|
||||
int logDisabled = 0;
|
||||
bool logHasSelection = false;
|
||||
bool clarifySelectionActive = false;
|
||||
int logDisabled {0};
|
||||
bool logHasSelection {false};
|
||||
bool clarifySelectionActive {false};
|
||||
|
||||
SelectionStyle selectionStyle;
|
||||
std::deque<SelectionChanges> NotificationQueue;
|
||||
bool Notifying {false};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -791,7 +791,7 @@ bool SoFCUnifiedSelection::setSelection(const std::vector<PickedInfo>& infos, bo
|
||||
// Ex: Body.Pad.Face9 to Body.Pad.;g3;SKT;:H12dc,E;FAC;:H12dc:4,F;:G0;XTR;:H12dc:8,F.Face9
|
||||
getFullSubElementName(subName);
|
||||
const char* subSelected
|
||||
= Gui::Selection().getSelectedElement(vpd->getObject(), subName.c_str());
|
||||
= Gui::Selection().getSelectedElement(vpd->getObject(), subName.c_str()).c_str();
|
||||
|
||||
FC_TRACE(
|
||||
"select " << (subSelected ? subSelected : "'null'") << ", " << objectName << ", " << subName
|
||||
@@ -1315,7 +1315,7 @@ SoFCSelectionContextBasePtr SoFCSelectionRoot::getNodeContext(
|
||||
}
|
||||
|
||||
auto front = dynamic_cast<SoFCSelectionRoot*>(stack.front());
|
||||
if (front == nullptr) {
|
||||
if (!front) {
|
||||
return SoFCSelectionContextBasePtr();
|
||||
}
|
||||
|
||||
@@ -1341,7 +1341,7 @@ SoFCSelectionContextBasePtr SoFCSelectionRoot::getNodeContext2(
|
||||
}
|
||||
|
||||
auto* back = dynamic_cast<SoFCSelectionRoot*>(stack.back());
|
||||
if (back == nullptr || back->contextMap2.empty()) {
|
||||
if (!back || back->contextMap2.empty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1387,7 +1387,7 @@ std::pair<bool, SoFCSelectionContextBasePtr*> SoFCSelectionRoot::findActionConte
|
||||
|
||||
if (res.first) {
|
||||
auto back = dynamic_cast<SoFCSelectionRoot*>(stack.back());
|
||||
if (back != nullptr) {
|
||||
if (back) {
|
||||
stack.back() = _node;
|
||||
if (create) {
|
||||
res.second = &back->contextMap2[stack];
|
||||
@@ -1406,7 +1406,7 @@ std::pair<bool, SoFCSelectionContextBasePtr*> SoFCSelectionRoot::findActionConte
|
||||
}
|
||||
else {
|
||||
auto front = dynamic_cast<SoFCSelectionRoot*>(stack.front());
|
||||
if (front != nullptr) {
|
||||
if (front) {
|
||||
stack.front() = _node;
|
||||
if (create) {
|
||||
res.second = &front->contextMap[stack];
|
||||
@@ -2027,7 +2027,7 @@ void SoFCPathAnnotation::GLRenderBelowPath(SoGLRenderAction* action)
|
||||
continue;
|
||||
}
|
||||
auto node = dynamic_cast<SoFCSelectionRoot*>(path->getNode(i));
|
||||
if (node != nullptr && node->selectionStyle.getValue() == SoFCSelectionRoot::Box) {
|
||||
if (node && node->selectionStyle.getValue() == SoFCSelectionRoot::Box) {
|
||||
bbox = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "TaskCommandLink.h"
|
||||
|
||||
#include "ui_TaskCommandLink.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
#include "ViewProvider.h"
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/Document.h>
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
TaskCommandLink::TaskCommandLink()
|
||||
: ui(new Ui_TaskCommandLinkDialog())
|
||||
{
|
||||
proxy = new QWidget(this);
|
||||
ui->setupUi(proxy);
|
||||
ui->objectsList->header()->hide();
|
||||
ui->objectsList->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
|
||||
|
||||
this->groupLayout()->addWidget(proxy);
|
||||
|
||||
buildObjectsList();
|
||||
}
|
||||
TaskCommandLink::~TaskCommandLink()
|
||||
{
|
||||
delete proxy;
|
||||
delete ui;
|
||||
}
|
||||
std::vector<App::DocumentObject*> TaskCommandLink::selectedObjects()
|
||||
{
|
||||
auto selected = ui->objectsList->selectedItems();
|
||||
std::vector<App::DocumentObject*> dst;
|
||||
dst.reserve(selected.size());
|
||||
|
||||
for (auto sel : selected) {
|
||||
dst.push_back(sel->data(0, Qt::UserRole).value<App::DocumentObject*>());
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
void processObjectsHelper(std::vector<App::DocumentObject*> objs, QTreeWidgetItem* item)
|
||||
{
|
||||
for (auto obj : objs) {
|
||||
auto objItem = new QTreeWidgetItem(item);
|
||||
|
||||
objItem->setText(0, obj->Label.getValue());
|
||||
objItem->setData(0, Qt::UserRole, QVariant::fromValue(obj));
|
||||
|
||||
Gui::ViewProvider* vp = nullptr;
|
||||
if (auto doc = Application::Instance->getDocument(obj->getDocument())) {
|
||||
vp = doc->getViewProvider(obj);
|
||||
}
|
||||
if (vp) {
|
||||
objItem->setIcon(0, vp->getIcon());
|
||||
processObjectsHelper(vp->claimChildren(), objItem);
|
||||
}
|
||||
else {
|
||||
objItem->setIcon(0, QIcon());
|
||||
}
|
||||
}
|
||||
}
|
||||
void TaskCommandLink::buildObjectsList()
|
||||
{
|
||||
ui->objectsList->clear();
|
||||
|
||||
auto allDocuments = App::GetApplication().getDocuments();
|
||||
bool collapse = true;
|
||||
std::map<QTreeWidgetItem*, App::Document*> docItemMap;
|
||||
|
||||
for (auto doc : allDocuments) {
|
||||
auto docItem = new QTreeWidgetItem();
|
||||
std::string itemName = doc->Label.getValue();
|
||||
|
||||
docItem->setText(0, QString::fromStdString(itemName));
|
||||
docItem->setIcon(0, QIcon(QStringLiteral(":/icons/Document.svg")));
|
||||
docItem->setFlags(docItem->flags() & ~Qt::ItemIsSelectable); // Can't link a whole document
|
||||
|
||||
docItemMap[docItem] = doc;
|
||||
|
||||
ui->objectsList->addTopLevelItem(docItem);
|
||||
|
||||
processObjectsHelper(Application::Instance->getDocument(doc)->getTreeRootObjects(), docItem);
|
||||
|
||||
if (collapse) {
|
||||
ui->objectsList->collapseAll();
|
||||
}
|
||||
else {
|
||||
ui->objectsList->expandToDepth(0);
|
||||
}
|
||||
}
|
||||
ui->objectsList->selectedItems();
|
||||
}
|
||||
|
||||
// dialog
|
||||
|
||||
TaskCommandLinkDialog::TaskCommandLinkDialog(
|
||||
std::function<void(std::vector<App::DocumentObject*>)> executor_
|
||||
)
|
||||
: executor(executor_)
|
||||
{
|
||||
commandLink = new TaskCommandLink();
|
||||
Content.push_back(commandLink);
|
||||
}
|
||||
void TaskCommandLinkDialog::open()
|
||||
{}
|
||||
bool TaskCommandLinkDialog::accept()
|
||||
{
|
||||
executor(commandLink->selectedObjects());
|
||||
return true;
|
||||
}
|
||||
} // namespace Gui
|
||||
@@ -0,0 +1,80 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Library General Public *
|
||||
* License as published by the Free Software Foundation; either *
|
||||
* version 2 of the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this library; see the file COPYING.LIB. If not, *
|
||||
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
* Suite 330, Boston, MA 02111-1307, USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TaskView/TaskDialog.h"
|
||||
#include "TaskView/TaskView.h"
|
||||
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace App
|
||||
{
|
||||
class DocumentObject;
|
||||
}
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class Document;
|
||||
class Ui_TaskCommandLinkDialog;
|
||||
|
||||
class TaskCommandLink: public Gui::TaskView::TaskBox
|
||||
{
|
||||
public:
|
||||
TaskCommandLink();
|
||||
~TaskCommandLink();
|
||||
|
||||
std::vector<App::DocumentObject*> selectedObjects();
|
||||
|
||||
private:
|
||||
void buildObjectsList();
|
||||
|
||||
private:
|
||||
Ui_TaskCommandLinkDialog* ui {nullptr};
|
||||
QWidget* proxy {nullptr};
|
||||
};
|
||||
|
||||
class TaskCommandLinkDialog: public Gui::TaskView::TaskDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TaskCommandLinkDialog(std::function<void(std::vector<App::DocumentObject*>)> executor_);
|
||||
~TaskCommandLinkDialog() override = default;
|
||||
|
||||
QDialogButtonBox::StandardButtons getStandardButtons() const override
|
||||
{
|
||||
return QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
|
||||
}
|
||||
|
||||
void open() override;
|
||||
bool accept() override;
|
||||
|
||||
private:
|
||||
TaskCommandLink* commandLink {nullptr};
|
||||
Gui::Document* document {nullptr};
|
||||
std::function<void(std::vector<App::DocumentObject*>)> executor;
|
||||
};
|
||||
} // namespace Gui
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Gui::TaskCommandLinkDialog</class>
|
||||
<widget class="QWidget" name="Gui::TaskCommandLinkDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>340</width>
|
||||
<height>212</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Insert</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="objectsList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -25,8 +25,8 @@
|
||||
#include <QColorDialog>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
#include <App/ElementNamingUtils.h>
|
||||
#include <App/Transactions.h>
|
||||
#include <App/Document.h>
|
||||
|
||||
#include "TaskElementColors.h"
|
||||
@@ -64,6 +64,7 @@ public:
|
||||
bool busy;
|
||||
long onTopMode;
|
||||
bool touched;
|
||||
int tid {0}; // Transaction id
|
||||
|
||||
std::string editDoc;
|
||||
std::string editObj;
|
||||
@@ -77,15 +78,14 @@ public:
|
||||
, vpDoc(vp->getDocument())
|
||||
, editElement(element)
|
||||
{
|
||||
auto doc = Application::Instance->editDocument();
|
||||
if (doc) {
|
||||
auto editVp = doc->getInEdit(&vpParent, &editSub);
|
||||
if (editVp == vp) {
|
||||
auto obj = vpParent->getObject();
|
||||
editDoc = obj->getDocument()->getName();
|
||||
editObj = obj->getNameInDocument();
|
||||
editSub = Data::noElementName(editSub.c_str());
|
||||
}
|
||||
if (auto editDoc = Application::Instance->editDocument([this, &vp](Gui::Document* editDoc) {
|
||||
return editDoc->getInEdit(&vpParent, &editSub) == vp;
|
||||
})) {
|
||||
|
||||
auto obj = vpParent->getObject();
|
||||
this->editDoc = obj->getDocument()->getName();
|
||||
this->editObj = obj->getNameInDocument();
|
||||
this->editSub = Data::noElementName(editSub.c_str());
|
||||
}
|
||||
if (editDoc.empty()) {
|
||||
vpParent = vp;
|
||||
@@ -198,8 +198,8 @@ public:
|
||||
std::string sub = qPrintable(item->data(Qt::UserRole + 1).value<QString>());
|
||||
info.emplace(sub, Base::Color::fromValue<QColor>(col));
|
||||
}
|
||||
if (!App::GetApplication().getActiveTransaction()) {
|
||||
App::GetApplication().setActiveTransaction("Set colors");
|
||||
if (tid == App::NullTransaction) {
|
||||
tid = vpDoc->openCommand(QT_TRANSLATE_NOOP("Command", "Set colors"));
|
||||
}
|
||||
vp->setElementColors(info);
|
||||
touched = true;
|
||||
@@ -209,8 +209,12 @@ public:
|
||||
void reset()
|
||||
{
|
||||
touched = false;
|
||||
App::GetApplication().closeActiveTransaction(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
Selection().clearSelection();
|
||||
|
||||
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editdoc) {
|
||||
return editdoc->getEditViewProvider() == vp;
|
||||
});
|
||||
}
|
||||
|
||||
void accept()
|
||||
@@ -221,7 +225,11 @@ public:
|
||||
obj->getDocument()->recompute(obj->getInListRecursive());
|
||||
touched = false;
|
||||
}
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editdoc) {
|
||||
return editdoc->getEditViewProvider() == vp;
|
||||
});
|
||||
}
|
||||
|
||||
void removeAll()
|
||||
@@ -435,14 +443,14 @@ void ElementColors::onTopClicked(bool checked)
|
||||
void ElementColors::slotDeleteDocument(const Document& Doc)
|
||||
{
|
||||
if (d->vpDoc == &Doc || d->editDoc == Doc.getDocument()->getName()) {
|
||||
Control().closeDialog();
|
||||
Control().closeDialog(Doc.getDocument());
|
||||
}
|
||||
}
|
||||
|
||||
void ElementColors::slotDeleteObject(const ViewProvider& obj)
|
||||
{
|
||||
if (d->vp == &obj) {
|
||||
Control().closeDialog();
|
||||
Control().closeDialog(d->vpDoc->getDocument());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,14 +551,12 @@ void ElementColors::onRemoveAllClicked()
|
||||
bool ElementColors::accept()
|
||||
{
|
||||
d->accept();
|
||||
Application::Instance->setEditDocument(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ElementColors::reject()
|
||||
{
|
||||
d->reset();
|
||||
Application::Instance->setEditDocument(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,5 +158,11 @@ void TaskDialog::onUndo()
|
||||
void TaskDialog::onRedo()
|
||||
{}
|
||||
|
||||
void TaskDialog::activate()
|
||||
{}
|
||||
|
||||
void TaskDialog::deactivate()
|
||||
{}
|
||||
|
||||
|
||||
#include "moc_TaskDialog.cpp"
|
||||
|
||||
@@ -209,6 +209,13 @@ public:
|
||||
/// is called by the framework if the user press the redo button
|
||||
virtual void onRedo();
|
||||
|
||||
/// Called by the framework when it becomes the shown dialog
|
||||
/// of the stacked task panel (e.g. when it's document becomes active)
|
||||
virtual void activate();
|
||||
/// Called by the framework when it stops being the shown dialog
|
||||
/// of the stacked task panel (e.g. when it's document stops being active)
|
||||
virtual void deactivate();
|
||||
|
||||
void emitDestructionSignal()
|
||||
{
|
||||
Q_EMIT aboutToBeDestroyed();
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <Gui/Control.h>
|
||||
#include <Gui/UiLoader.h>
|
||||
#include <Gui/PythonWrapper.h>
|
||||
#include <Gui/DocumentPy.h>
|
||||
|
||||
#include "TaskDialogPython.h"
|
||||
#include "TaskView.h"
|
||||
@@ -142,43 +143,67 @@ Py::Object ControlPy::repr()
|
||||
|
||||
Py::Object ControlPy::showDialog(const Py::Tuple& args)
|
||||
{
|
||||
PyObject* arg0;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "O", &arg0)) {
|
||||
PyObject* arg0 = nullptr;
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "O|O!", &arg0, &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
Gui::TaskView::TaskDialog* act = Gui::Control().activeDialog();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
Gui::TaskView::TaskDialog* act = Gui::Control().activeDialog(doc);
|
||||
if (act) {
|
||||
throw Py::RuntimeError("Active task dialog found");
|
||||
}
|
||||
auto dlg = new TaskDialogPython(Py::Object(arg0));
|
||||
Gui::Control().showDialog(dlg);
|
||||
Gui::Control().showDialog(dlg, doc);
|
||||
return (Py::asObject(new TaskDialogPy(dlg)));
|
||||
}
|
||||
|
||||
Py::Object ControlPy::activeDialog(const Py::Tuple& args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(doc);
|
||||
return Py::Boolean(dlg != nullptr);
|
||||
}
|
||||
|
||||
Py::Object ControlPy::activeTaskDialog(const Py::Tuple& args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(doc);
|
||||
return (dlg ? Py::asObject(new TaskDialogPy(dlg)) : Py::None());
|
||||
}
|
||||
|
||||
Py::Object ControlPy::closeDialog(const Py::Tuple& args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
Gui::Control().closeDialog();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
Gui::Control().closeDialog(doc);
|
||||
return Py::None();
|
||||
}
|
||||
|
||||
@@ -217,28 +242,46 @@ Py::Object ControlPy::clearTaskWatcher(const Py::Tuple& args)
|
||||
|
||||
Py::Object ControlPy::isAllowedAlterDocument(const Py::Tuple& args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
bool ok = Gui::Control().isAllowedAlterDocument();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
bool ok = Gui::Control().isAllowedAlterDocument(doc);
|
||||
return Py::Boolean(ok);
|
||||
}
|
||||
|
||||
Py::Object ControlPy::isAllowedAlterView(const Py::Tuple& args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
bool ok = Gui::Control().isAllowedAlterView();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
bool ok = Gui::Control().isAllowedAlterView(doc);
|
||||
return Py::Boolean(ok);
|
||||
}
|
||||
|
||||
Py::Object ControlPy::isAllowedAlterSelection(const Py::Tuple& args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
||||
PyObject* docPy = nullptr;
|
||||
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
|
||||
throw Py::Exception();
|
||||
}
|
||||
bool ok = Gui::Control().isAllowedAlterSelection();
|
||||
|
||||
App::Document* doc = docPy
|
||||
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
|
||||
: nullptr;
|
||||
|
||||
bool ok = Gui::Control().isAllowedAlterSelection(doc);
|
||||
return Py::Boolean(ok);
|
||||
}
|
||||
|
||||
|
||||
+257
-190
@@ -234,37 +234,7 @@ void TaskBox::actionEvent(QActionEvent* e)
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TaskPanel::TaskPanel(QWidget* parent)
|
||||
: QSint::ActionPanel(parent)
|
||||
{}
|
||||
|
||||
TaskPanel::~TaskPanel() = default;
|
||||
|
||||
QSize TaskPanel::minimumSizeHint() const
|
||||
{
|
||||
// ActionPanel returns a size of 200x150 which leads to problems
|
||||
// when there are several task groups in the panel and the first
|
||||
// one is collapsed. In this case the task panel doesn't expand to
|
||||
// the actually required size and all the remaining groups are
|
||||
// squeezed into the available space and thus the widgets in there
|
||||
// often can't be used any more.
|
||||
// To fix this problem minimumSizeHint() is implemented to again
|
||||
// respect the layout's minimum size.
|
||||
QSize s1 = QSint::ActionPanel::minimumSizeHint();
|
||||
QSize s2 = QWidget::minimumSizeHint();
|
||||
return {qMax(s1.width(), s2.width()), qMax(s1.height(), s2.height())};
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
//**************************************************************************
|
||||
// TaskView
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TaskView::TaskView(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, ActiveDialog(nullptr)
|
||||
, ActiveCtrl(nullptr)
|
||||
, hGrp(Gui::WindowParameter::getDefaultParameter()->GetGroup("General"))
|
||||
{
|
||||
mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -280,19 +250,55 @@ TaskView::TaskView(QWidget* parent)
|
||||
dialogLayout->setSpacing(0);
|
||||
mainLayout->addLayout(dialogLayout, 1);
|
||||
|
||||
taskPanel = new TaskPanel(scrollArea);
|
||||
actionPanel = new QSint::ActionPanel(scrollArea);
|
||||
QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
sizePolicy.setHorizontalStretch(0);
|
||||
sizePolicy.setVerticalStretch(0);
|
||||
sizePolicy.setHeightForWidth(taskPanel->sizePolicy().hasHeightForWidth());
|
||||
taskPanel->setSizePolicy(sizePolicy);
|
||||
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
sizePolicy.setHeightForWidth(actionPanel->sizePolicy().hasHeightForWidth());
|
||||
actionPanel->setSizePolicy(sizePolicy);
|
||||
actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
|
||||
scrollArea->setWidget(taskPanel);
|
||||
scrollArea->setWidget(actionPanel);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scrollArea->setMinimumWidth(200);
|
||||
dialogLayout->addWidget(scrollArea, 1);
|
||||
}
|
||||
|
||||
TaskPanel::~TaskPanel()
|
||||
{
|
||||
for (QWidget* panel : contextualPanels) {
|
||||
delete panel;
|
||||
}
|
||||
}
|
||||
|
||||
QSize TaskPanel::minimumSizeHint() const
|
||||
{
|
||||
// ActionPanel returns a size of 200x150 which leads to problems
|
||||
// when there are several task groups in the panel and the first
|
||||
// one is collapsed. In this case the task panel doesn't expand to
|
||||
// the actually required size and all the remaining groups are
|
||||
// squeezed into the available space and thus the widgets in there
|
||||
// often can't be used any more.
|
||||
// To fix this problem minimumSizeHint() is implemented to again
|
||||
// respect the layout's minimum size.
|
||||
QSize s1 = actionPanel->minimumSizeHint();
|
||||
QSize s2 = QWidget::minimumSizeHint();
|
||||
return {qMax(s1.width(), s2.width()), qMax(s1.height(), s2.height())};
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
//**************************************************************************
|
||||
// TaskView
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TaskView::TaskView(QWidget* parent)
|
||||
: QStackedWidget(parent)
|
||||
, hGrp(Gui::WindowParameter::getDefaultParameter()->GetGroup("General"))
|
||||
{
|
||||
TaskWatcherPanel = new TaskPanel(this);
|
||||
addWidget(TaskWatcherPanel);
|
||||
|
||||
Gui::Selection().Attach(this);
|
||||
|
||||
@@ -329,7 +335,6 @@ TaskView::TaskView(QWidget* parent)
|
||||
|
||||
updateWatcher();
|
||||
}
|
||||
|
||||
TaskView::~TaskView()
|
||||
{
|
||||
connectApplicationActiveDocument.disconnect();
|
||||
@@ -341,17 +346,22 @@ TaskView::~TaskView()
|
||||
connectShowTaskWatcherSetting.disconnect();
|
||||
Gui::Selection().Detach(this);
|
||||
|
||||
for (QWidget* panel : contextualPanels) {
|
||||
delete panel;
|
||||
// if well behaved, we should not have nay taskInfo at this point
|
||||
for (auto& taskInfo : taskInfos) {
|
||||
delete taskInfo.ActiveCtrl;
|
||||
delete taskInfo.ActiveDialog;
|
||||
delete taskInfo.taskPanel;
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskView::isEmpty(bool includeWatcher) const
|
||||
{
|
||||
if (ActiveCtrl || ActiveDialog) {
|
||||
std::optional<TaskInfo> active = currentTaskInfo();
|
||||
if (active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no active task in the document
|
||||
if (includeWatcher) {
|
||||
for (auto* watcher : ActiveWatcher) {
|
||||
if (watcher->shouldShow()) {
|
||||
@@ -397,7 +407,8 @@ bool TaskView::event(QEvent* event)
|
||||
|
||||
void TaskView::keyPressEvent(QKeyEvent* ke)
|
||||
{
|
||||
if (ActiveCtrl && ActiveDialog) {
|
||||
std::optional<TaskInfo> active = currentTaskInfo();
|
||||
if (active) {
|
||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
// get all buttons of the complete task dialog
|
||||
QList<QPushButton*> list = this->findChildren<QPushButton*>();
|
||||
@@ -419,9 +430,9 @@ void TaskView::keyPressEvent(QKeyEvent* ke)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ke->key() == Qt::Key_Escape && ActiveDialog->isEscapeButtonEnabled()) {
|
||||
else if (ke->key() == Qt::Key_Escape && active->ActiveDialog->isEscapeButtonEnabled()) {
|
||||
// get only the buttons of the button box
|
||||
QDialogButtonBox* box = ActiveCtrl->standardButtons();
|
||||
QDialogButtonBox* box = active->ActiveCtrl->standardButtons();
|
||||
QList<QAbstractButton*> list = box->buttons();
|
||||
for (auto pb : list) {
|
||||
if (box->buttonRole(pb) == QDialogButtonBox::RejectRole) {
|
||||
@@ -447,7 +458,7 @@ void TaskView::keyPressEvent(QKeyEvent* ke)
|
||||
auto func = new Gui::TimerFunction();
|
||||
func->setAutoDelete(true);
|
||||
Gui::Document* doc = Gui::Application::Instance->getDocument(
|
||||
ActiveDialog->getDocumentName().c_str()
|
||||
active->ActiveDialog->getDocumentName().c_str()
|
||||
);
|
||||
if (doc) {
|
||||
func->setFunction([doc]() { doc->resetEdit(); });
|
||||
@@ -474,105 +485,96 @@ void TaskView::adjustMinimumSizeHint()
|
||||
|
||||
QSize TaskView::minimumSizeHint() const
|
||||
{
|
||||
QSize ms = QWidget::minimumSizeHint();
|
||||
QSize ms = currentWidget()->minimumSizeHint();
|
||||
int spacing = 0;
|
||||
if (QLayout* layout = taskPanel->layout()) {
|
||||
|
||||
if (QLayout* layout = currentWidget()->layout()) {
|
||||
spacing = 2 * layout->spacing();
|
||||
}
|
||||
|
||||
ms.setWidth(taskPanel->minimumSizeHint().width() + spacing);
|
||||
ms.setWidth(ms.width() + spacing);
|
||||
return ms;
|
||||
}
|
||||
|
||||
void TaskView::slotActiveDocument(const App::Document& doc)
|
||||
{
|
||||
Q_UNUSED(doc);
|
||||
if (!ActiveDialog) {
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document);
|
||||
if (foundTaskInfo != taskInfos.end()) {
|
||||
setShownTaskInfo((foundTaskInfo - taskInfos.begin()));
|
||||
}
|
||||
else {
|
||||
setShownTaskInfo(-1);
|
||||
}
|
||||
|
||||
if (foundTaskInfo == taskInfos.end()) {
|
||||
// at this point, active object of the active view returns None.
|
||||
// which is a problem if shouldShow of a watcher rely on the presence
|
||||
// of an active object (example Assembly).
|
||||
QTimer::singleShot(100, this, &TaskView::updateWatcher);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::slotInEdit(const Gui::ViewProviderDocumentObject& vp)
|
||||
{
|
||||
Q_UNUSED(vp);
|
||||
if (!ActiveDialog) {
|
||||
App::Document* doc = vp.getDocument()->getDocument();
|
||||
if (std::ranges::find(taskInfos, doc, &TaskInfo::Document) == taskInfos.end()) {
|
||||
updateWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::slotDeletedDocument(const App::Document& doc)
|
||||
{
|
||||
if (ActiveDialog) {
|
||||
if (ActiveDialog->isAutoCloseOnDeletedDocument()) {
|
||||
std::string name = ActiveDialog->getDocumentName();
|
||||
if (name.empty()) {
|
||||
Base::Console().warning(
|
||||
std::string("TaskView::slotDeletedDocument"),
|
||||
"No document name set\n"
|
||||
);
|
||||
}
|
||||
|
||||
if (name == doc.getName()) {
|
||||
ActiveDialog->autoClosedOnDeletedDocument();
|
||||
removeDialog();
|
||||
}
|
||||
}
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document);
|
||||
bool hasDialog = foundTaskInfo != taskInfos.end();
|
||||
if (hasDialog && foundTaskInfo->ActiveDialog->isAutoCloseOnDeletedDocument()) {
|
||||
foundTaskInfo->ActiveDialog->autoClosedOnDeletedDocument();
|
||||
removeDialog(foundTaskInfo);
|
||||
hasDialog = false;
|
||||
}
|
||||
|
||||
if (!ActiveDialog) {
|
||||
if (!hasDialog) {
|
||||
updateWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::slotViewClosed(const Gui::MDIView* view)
|
||||
{
|
||||
auto foundTaskInfo = std::ranges::find_if(taskInfos, [view](const TaskInfo& info) {
|
||||
return info.ActiveDialog->getAssociatedView() == view;
|
||||
});
|
||||
bool hasDialog = foundTaskInfo != taskInfos.end();
|
||||
// It can happen that only a view is closed an not the document
|
||||
if (ActiveDialog) {
|
||||
if (ActiveDialog->isAutoCloseOnClosedView()) {
|
||||
const Gui::MDIView* associatedView = ActiveDialog->getAssociatedView();
|
||||
if (!associatedView) {
|
||||
Base::Console().warning(std::string("TaskView::slotViewClosed"), "No view associated\n");
|
||||
}
|
||||
|
||||
if (associatedView == view) {
|
||||
ActiveDialog->autoClosedOnClosedView();
|
||||
removeDialog();
|
||||
}
|
||||
}
|
||||
if (hasDialog && foundTaskInfo->ActiveDialog->isAutoCloseOnClosedView()) {
|
||||
foundTaskInfo->ActiveDialog->autoClosedOnClosedView();
|
||||
removeDialog(foundTaskInfo);
|
||||
hasDialog = false;
|
||||
}
|
||||
|
||||
if (!ActiveDialog) {
|
||||
if (!hasDialog) {
|
||||
updateWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::transactionChangeOnDocument(const App::Document& doc, bool undo)
|
||||
{
|
||||
if (ActiveDialog) {
|
||||
std::string name = ActiveDialog->getDocumentName();
|
||||
if (name == doc.getName()) {
|
||||
undo ? ActiveDialog->onUndo() : ActiveDialog->onRedo();
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document);
|
||||
bool hasDialog = foundTaskInfo != taskInfos.end();
|
||||
|
||||
if (hasDialog) {
|
||||
if (undo) {
|
||||
foundTaskInfo->ActiveDialog->onUndo();
|
||||
}
|
||||
else {
|
||||
foundTaskInfo->ActiveDialog->onRedo();
|
||||
}
|
||||
|
||||
if (ActiveDialog->isAutoCloseOnTransactionChange()) {
|
||||
if (name.empty()) {
|
||||
Base::Console().warning(
|
||||
std::string("TaskView::transactionChangeOnDocument"),
|
||||
"No document name set\n"
|
||||
);
|
||||
}
|
||||
|
||||
if (name == doc.getName()) {
|
||||
ActiveDialog->autoClosedOnTransactionChange();
|
||||
removeDialog();
|
||||
}
|
||||
if (foundTaskInfo->ActiveDialog->isAutoCloseOnTransactionChange()) {
|
||||
foundTaskInfo->ActiveDialog->autoClosedOnTransactionChange();
|
||||
removeDialog(foundTaskInfo);
|
||||
hasDialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ActiveDialog) {
|
||||
if (!hasDialog) {
|
||||
updateWatcher();
|
||||
}
|
||||
}
|
||||
@@ -600,72 +602,75 @@ void TaskView::OnChange(
|
||||
|| Reason.Type == SelectionChanges::SetSelection
|
||||
|| Reason.Type == SelectionChanges::RmvSelection) {
|
||||
|
||||
if (!ActiveDialog) {
|
||||
if (!currentTaskInfo()) {
|
||||
updateWatcher();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// @endcond
|
||||
|
||||
void TaskView::showDialog(TaskDialog* dlg)
|
||||
bool TaskView::showDialog(TaskDialog* dlg, App::Document* doc)
|
||||
{
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
// if trying to open the same dialog twice nothing needs to be done
|
||||
if (ActiveDialog == dlg) {
|
||||
return;
|
||||
if (foundTaskInfo != taskInfos.end() && foundTaskInfo->ActiveDialog == dlg) {
|
||||
return false;
|
||||
}
|
||||
assert(!ActiveDialog);
|
||||
assert(!ActiveCtrl);
|
||||
|
||||
// remove the TaskWatcher as long as the Dialog is up
|
||||
removeTaskWatcher();
|
||||
assert(foundTaskInfo == taskInfos.end());
|
||||
|
||||
TaskInfo outInfo {.Document = doc};
|
||||
// first create the control element, set it up and wire it:
|
||||
ActiveCtrl = new TaskEditControl(this);
|
||||
ActiveCtrl->buttonBox->setStandardButtons(dlg->getStandardButtons());
|
||||
TaskDialogAttorney::setButtonBox(dlg, ActiveCtrl->buttonBox);
|
||||
|
||||
// clang-format off
|
||||
// make connection to the needed signals
|
||||
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::accepted,
|
||||
this, &TaskView::accept);
|
||||
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::rejected,
|
||||
this, &TaskView::reject);
|
||||
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::helpRequested,
|
||||
this, &TaskView::helpRequested);
|
||||
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &TaskView::clicked);
|
||||
// clang-format on
|
||||
outInfo.ActiveCtrl = new TaskEditControl(this);
|
||||
outInfo.ActiveCtrl->buttonBox->setStandardButtons(dlg->getStandardButtons());
|
||||
TaskDialogAttorney::setButtonBox(dlg, outInfo.ActiveCtrl->buttonBox);
|
||||
|
||||
const std::vector<QWidget*>& cont = dlg->getDialogContent();
|
||||
|
||||
// give to task dialog to customize the button box
|
||||
dlg->modifyStandardButtons(ActiveCtrl->buttonBox);
|
||||
dlg->modifyStandardButtons(outInfo.ActiveCtrl->buttonBox);
|
||||
|
||||
outInfo.taskPanel = new TaskPanel(this);
|
||||
if (dlg->buttonPosition() == TaskDialog::North) {
|
||||
// Add button box to the top of the main layout
|
||||
dialogLayout->insertWidget(0, ActiveCtrl);
|
||||
outInfo.taskPanel->dialogLayout->insertWidget(0, outInfo.ActiveCtrl);
|
||||
for (const auto& it : cont) {
|
||||
taskPanel->addWidget(it);
|
||||
outInfo.taskPanel->actionPanel->addWidget(it);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const auto& it : cont) {
|
||||
taskPanel->addWidget(it);
|
||||
outInfo.taskPanel->actionPanel->addWidget(it);
|
||||
}
|
||||
// Add button box to the bottom of the main layout
|
||||
dialogLayout->addWidget(ActiveCtrl);
|
||||
outInfo.taskPanel->dialogLayout->addWidget(outInfo.ActiveCtrl);
|
||||
}
|
||||
|
||||
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
outInfo.taskPanel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
|
||||
if (!dlg->needsFullSpace()) {
|
||||
taskPanel->addStretch();
|
||||
outInfo.taskPanel->actionPanel->addStretch();
|
||||
}
|
||||
|
||||
// set as active Dialog
|
||||
ActiveDialog = dlg;
|
||||
outInfo.ActiveDialog = dlg;
|
||||
outInfo.ActiveDialog->open();
|
||||
|
||||
ActiveDialog->open();
|
||||
// clang-format off
|
||||
// make connection to the needed signals
|
||||
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::accepted,
|
||||
this, [doc, this]{ accept(doc); });
|
||||
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::rejected,
|
||||
this, [doc, this]{ reject(doc); });
|
||||
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::helpRequested,
|
||||
this, [doc, this]{ helpRequested(doc); });
|
||||
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, [doc, this](QAbstractButton *button) { clicked(button, doc); });
|
||||
// clang-format on
|
||||
|
||||
// This will hide whatever was shown in the taskview
|
||||
taskInfos.push_back(outInfo);
|
||||
addWidget(outInfo.taskPanel);
|
||||
setShownTaskInfo(taskInfos.size() - 1);
|
||||
|
||||
saveCurrentWidth();
|
||||
getMainWindow()->updateActions();
|
||||
@@ -675,43 +680,51 @@ void TaskView::showDialog(TaskDialog* dlg)
|
||||
Q_EMIT taskUpdate();
|
||||
|
||||
OverlayManager::instance()->refresh();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TaskView::removeDialog()
|
||||
void TaskView::removeDialog(App::Document* doc)
|
||||
{
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (foundTaskInfo != taskInfos.end()) {
|
||||
removeDialog(foundTaskInfo);
|
||||
}
|
||||
}
|
||||
void TaskView::removeDialog(std::vector<TaskInfo>::iterator infoIt)
|
||||
{
|
||||
if (infoIt == taskInfos.end()) {
|
||||
return;
|
||||
}
|
||||
getMainWindow()->updateActions();
|
||||
|
||||
if (ActiveCtrl) {
|
||||
dialogLayout->removeWidget(ActiveCtrl);
|
||||
delete ActiveCtrl;
|
||||
ActiveCtrl = nullptr;
|
||||
}
|
||||
|
||||
TaskDialog* remove = nullptr;
|
||||
if (ActiveDialog) {
|
||||
std::optional<TaskInfo> remove = std::nullopt;
|
||||
if (infoIt->ActiveDialog) {
|
||||
// See 'accept' and 'reject'
|
||||
if (ActiveDialog->property("taskview_accept_or_reject").isNull()) {
|
||||
const std::vector<QWidget*>& cont = ActiveDialog->getDialogContent();
|
||||
if (infoIt->ActiveDialog->property("taskview_accept_or_reject").isNull()) {
|
||||
const std::vector<QWidget*>& cont = infoIt->ActiveDialog->getDialogContent();
|
||||
for (const auto& it : cont) {
|
||||
taskPanel->removeWidget(it);
|
||||
infoIt->taskPanel->actionPanel->removeWidget(it);
|
||||
}
|
||||
remove = ActiveDialog;
|
||||
ActiveDialog = nullptr;
|
||||
remove = *infoIt;
|
||||
taskInfos.erase(infoIt);
|
||||
removeWidget(remove->taskPanel);
|
||||
}
|
||||
else {
|
||||
ActiveDialog->setProperty("taskview_remove_dialog", true);
|
||||
infoIt->ActiveDialog->setProperty("taskview_remove_dialog", true);
|
||||
}
|
||||
}
|
||||
|
||||
taskPanel->removeStretch();
|
||||
|
||||
// put the watcher back in control
|
||||
removeTaskWatcher();
|
||||
addTaskWatcher();
|
||||
|
||||
if (remove) {
|
||||
remove->closed();
|
||||
remove->emitDestructionSignal();
|
||||
delete remove;
|
||||
remove->ActiveDialog->closed();
|
||||
remove->ActiveDialog->emitDestructionSignal();
|
||||
delete remove->ActiveCtrl;
|
||||
delete remove->ActiveDialog;
|
||||
delete remove->taskPanel;
|
||||
}
|
||||
|
||||
tryRestoreWidth();
|
||||
@@ -741,7 +754,7 @@ void TaskView::updateWatcher()
|
||||
|
||||
if (ActiveWatcher.empty()) {
|
||||
auto panel = Gui::Control().taskPanel();
|
||||
if (panel && panel->ActiveWatcher.size()) {
|
||||
if (panel && !panel->ActiveWatcher.empty()) {
|
||||
takeTaskWatcher(panel);
|
||||
}
|
||||
}
|
||||
@@ -797,9 +810,7 @@ void TaskView::addTaskWatcher(const std::vector<TaskWatcher*>& Watcher)
|
||||
}
|
||||
|
||||
ActiveWatcher = Watcher;
|
||||
if (!ActiveCtrl && !ActiveDialog) {
|
||||
addTaskWatcher();
|
||||
}
|
||||
addTaskWatcher();
|
||||
}
|
||||
|
||||
void TaskView::takeTaskWatcher(TaskView* other)
|
||||
@@ -823,33 +834,34 @@ void TaskView::clearTaskWatcher()
|
||||
void TaskView::addTaskWatcher()
|
||||
{
|
||||
if (!showTaskWatcher) {
|
||||
setShownTaskInfo(-1); // Switch to the empty taskwatcher panel
|
||||
return;
|
||||
}
|
||||
// add all widgets for all watcher to the task view
|
||||
for (TaskWatcher* tw : ActiveWatcher) {
|
||||
std::vector<QWidget*>& cont = tw->getWatcherContent();
|
||||
for (QWidget* w : cont) {
|
||||
taskPanel->addWidget(w);
|
||||
TaskWatcherPanel->actionPanel->addWidget(w);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ActiveWatcher.empty()) {
|
||||
taskPanel->addStretch();
|
||||
TaskWatcherPanel->actionPanel->addStretch();
|
||||
}
|
||||
updateWatcher();
|
||||
|
||||
// Workaround to avoid a crash in Qt. See also
|
||||
// https://forum.freecad.org/viewtopic.php?f=8&t=39187
|
||||
//
|
||||
// Notify the button box about a style change so that it can
|
||||
// safely delete the style animation of its push buttons.
|
||||
auto box = taskPanel->findChild<QDialogButtonBox*>();
|
||||
auto box = TaskWatcherPanel->mainLayout->findChild<QDialogButtonBox*>();
|
||||
if (box) {
|
||||
QEvent event(QEvent::StyleChange);
|
||||
QApplication::sendEvent(box, &event);
|
||||
}
|
||||
|
||||
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
TaskWatcherPanel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
setShownTaskInfo(-1);
|
||||
}
|
||||
|
||||
void TaskView::saveCurrentWidth()
|
||||
@@ -879,6 +891,44 @@ bool TaskView::shouldRestoreWidth() const
|
||||
{
|
||||
return restoreWidth;
|
||||
}
|
||||
std::optional<TaskInfo> TaskView::currentTaskInfo() const
|
||||
{
|
||||
// Index 0 is taskWatcher's panel
|
||||
if (currentIndex() <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return taskInfos[currentIndex() - 1];
|
||||
}
|
||||
TaskDialog* TaskView::dialog(App::Document* doc)
|
||||
{
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
return foundTaskInfo == taskInfos.end() ? nullptr : foundTaskInfo->ActiveDialog;
|
||||
}
|
||||
void TaskView::setShownTaskInfo(int index)
|
||||
{
|
||||
int stackedIndex = 0;
|
||||
int initIndex = currentIndex();
|
||||
if (index < 0 || index >= taskInfos.size()) {
|
||||
updateWatcher();
|
||||
stackedIndex = 0; // Show task watcher
|
||||
}
|
||||
else {
|
||||
stackedIndex = index + 1;
|
||||
}
|
||||
if (stackedIndex == initIndex) {
|
||||
return; // Nothing to be done
|
||||
}
|
||||
|
||||
if (initIndex > 0) {
|
||||
Gui::Selection().rmvSelectionGate();
|
||||
taskInfos[initIndex - 1].ActiveDialog->deactivate();
|
||||
}
|
||||
|
||||
if (stackedIndex > 0) {
|
||||
taskInfos[stackedIndex - 1].ActiveDialog->activate();
|
||||
}
|
||||
setCurrentIndex(stackedIndex);
|
||||
}
|
||||
|
||||
void TaskView::removeTaskWatcher()
|
||||
{
|
||||
@@ -904,92 +954,109 @@ void TaskView::removeTaskWatcher()
|
||||
std::vector<QWidget*>& cont = tw->getWatcherContent();
|
||||
for (QWidget* w : cont) {
|
||||
w->hide();
|
||||
taskPanel->removeWidget(w);
|
||||
TaskWatcherPanel->actionPanel->removeWidget(w);
|
||||
}
|
||||
}
|
||||
|
||||
taskPanel->removeStretch();
|
||||
TaskWatcherPanel->actionPanel->removeStretch();
|
||||
}
|
||||
|
||||
void TaskView::accept()
|
||||
void TaskView::accept(App::Document* doc)
|
||||
{
|
||||
if (!ActiveDialog) { // Protect against segfaults due to out-of-order deletions
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (foundTaskInfo == taskInfos.end()) { // Protect against segfaults due to out-of-order deletions
|
||||
Base::Console().warning("ActiveDialog was null in call to TaskView::accept()\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that if 'accept' calls 'closeDialog' the deletion is postponed until
|
||||
// the dialog leaves the 'accept' method
|
||||
ActiveDialog->setProperty("taskview_accept_or_reject", true);
|
||||
bool success = ActiveDialog->accept();
|
||||
ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
|
||||
if (success || ActiveDialog->property("taskview_remove_dialog").isValid()) {
|
||||
removeDialog();
|
||||
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", true);
|
||||
bool success = foundTaskInfo->ActiveDialog->accept();
|
||||
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
|
||||
if (success || foundTaskInfo->ActiveDialog->property("taskview_remove_dialog").isValid()) {
|
||||
removeDialog(doc);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::reject()
|
||||
void TaskView::reject(App::Document* doc)
|
||||
{
|
||||
if (!ActiveDialog) { // Protect against segfaults due to out-of-order deletions
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (foundTaskInfo == taskInfos.end()) { // Protect against segfaults due to out-of-order deletions
|
||||
Base::Console().warning("ActiveDialog was null in call to TaskView::reject()\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that if 'reject' calls 'closeDialog' the deletion is postponed until
|
||||
// the dialog leaves the 'reject' method
|
||||
ActiveDialog->setProperty("taskview_accept_or_reject", true);
|
||||
bool success = ActiveDialog->reject();
|
||||
ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
|
||||
if (success || ActiveDialog->property("taskview_remove_dialog").isValid()) {
|
||||
removeDialog();
|
||||
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", true);
|
||||
bool success = foundTaskInfo->ActiveDialog->reject();
|
||||
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
|
||||
if (success || foundTaskInfo->ActiveDialog->property("taskview_remove_dialog").isValid()) {
|
||||
removeDialog(doc);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::helpRequested()
|
||||
void TaskView::helpRequested(App::Document* doc)
|
||||
{
|
||||
ActiveDialog->helpRequested();
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (foundTaskInfo != taskInfos.end()) {
|
||||
foundTaskInfo->ActiveDialog->helpRequested();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::clicked(QAbstractButton* button)
|
||||
void TaskView::clicked(QAbstractButton* button, App::Document* doc)
|
||||
{
|
||||
int id = ActiveCtrl->buttonBox->standardButton(button);
|
||||
ActiveDialog->clicked(id);
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (foundTaskInfo != taskInfos.end()) {
|
||||
int id = foundTaskInfo->ActiveCtrl->buttonBox->standardButton(button);
|
||||
foundTaskInfo->ActiveDialog->clicked(id);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskView::clearActionStyle()
|
||||
{
|
||||
std::optional<TaskInfo> current = currentTaskInfo();
|
||||
TaskPanel* panel = current ? current->taskPanel : TaskWatcherPanel;
|
||||
static_cast<QSint::ActionPanelScheme*>(QSint::ActionPanelScheme::defaultScheme())->clearActionStyle();
|
||||
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
panel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
}
|
||||
|
||||
void TaskView::restoreActionStyle()
|
||||
{
|
||||
std::optional<TaskInfo> current = currentTaskInfo();
|
||||
TaskPanel* panel = current ? current->taskPanel : TaskWatcherPanel;
|
||||
static_cast<QSint::ActionPanelScheme*>(QSint::ActionPanelScheme::defaultScheme())
|
||||
->restoreActionStyle();
|
||||
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
panel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
|
||||
}
|
||||
|
||||
void TaskView::addContextualPanel(QWidget* panel)
|
||||
void TaskView::addContextualPanel(QWidget* panel, App::Document* doc)
|
||||
{
|
||||
if (!panel || contextualPanels.contains(panel)) {
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (!panel || foundTaskInfo == taskInfos.end()
|
||||
|| foundTaskInfo->taskPanel->contextualPanels.contains(panel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
contextualPanelsLayout->addWidget(panel);
|
||||
contextualPanels.append(panel);
|
||||
foundTaskInfo->taskPanel->contextualPanelsLayout->addWidget(panel);
|
||||
foundTaskInfo->taskPanel->contextualPanels.append(panel);
|
||||
panel->show();
|
||||
triggerMinimumSizeHint();
|
||||
Q_EMIT taskUpdate();
|
||||
}
|
||||
|
||||
void TaskView::removeContextualPanel(QWidget* panel)
|
||||
void TaskView::removeContextualPanel(QWidget* panel, App::Document* doc)
|
||||
{
|
||||
if (!panel || !contextualPanels.contains(panel)) {
|
||||
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
|
||||
if (!panel || foundTaskInfo == taskInfos.end()
|
||||
|| !foundTaskInfo->taskPanel->contextualPanels.contains(panel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
contextualPanelsLayout->removeWidget(panel);
|
||||
contextualPanels.removeOne(panel);
|
||||
|
||||
foundTaskInfo->taskPanel->contextualPanelsLayout->removeWidget(panel);
|
||||
foundTaskInfo->taskPanel->contextualPanels.removeOne(panel);
|
||||
panel->deleteLater();
|
||||
triggerMinimumSizeHint();
|
||||
Q_EMIT taskUpdate();
|
||||
|
||||
+43
-20
@@ -25,7 +25,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <QScrollArea>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include <Base/Parameter.h>
|
||||
#include <Gui/QSint/include/QSint>
|
||||
@@ -118,7 +120,7 @@ private:
|
||||
bool wasShown;
|
||||
};
|
||||
|
||||
class GuiExport TaskPanel: public QSint::ActionPanel
|
||||
class GuiExport TaskPanel: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -126,6 +128,14 @@ public:
|
||||
explicit TaskPanel(QWidget* parent = nullptr);
|
||||
~TaskPanel() override;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
public:
|
||||
QVBoxLayout* mainLayout;
|
||||
QScrollArea* scrollArea;
|
||||
QVBoxLayout* contextualPanelsLayout;
|
||||
QVBoxLayout* dialogLayout;
|
||||
QList<QWidget*> contextualPanels;
|
||||
QSint::ActionPanel* actionPanel;
|
||||
};
|
||||
|
||||
/// Father class of content of a Free widget (without header and Icon), shut be an exception!
|
||||
@@ -138,12 +148,20 @@ public:
|
||||
~TaskWidget() override;
|
||||
};
|
||||
|
||||
struct TaskInfo
|
||||
{
|
||||
TaskPanel* taskPanel {nullptr};
|
||||
TaskDialog* ActiveDialog {nullptr};
|
||||
TaskEditControl* ActiveCtrl {nullptr};
|
||||
App::Document* Document {nullptr};
|
||||
};
|
||||
|
||||
/** TaskView class
|
||||
* handles the FreeCAD task view panel. Keeps track of the inserted content elements.
|
||||
* This elements get injected mostly by the ViewProvider classes of the selected
|
||||
* DocumentObjects.
|
||||
*/
|
||||
class GuiExport TaskView: public QWidget, public Gui::SelectionSingleton::ObserverType
|
||||
class GuiExport TaskView: public QStackedWidget, public Gui::SelectionSingleton::ObserverType
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -160,6 +178,7 @@ public:
|
||||
friend class Gui::DockWnd::ComboView;
|
||||
friend class Gui::ControlSingleton;
|
||||
|
||||
/// sets the task watcher and shows it
|
||||
void addTaskWatcher(const std::vector<TaskWatcher*>& Watcher);
|
||||
void clearTaskWatcher();
|
||||
void takeTaskWatcher(TaskView* other);
|
||||
@@ -170,8 +189,8 @@ public:
|
||||
void restoreActionStyle();
|
||||
|
||||
/// Add a persistent panel at the top of the task view, independent of the active dialog.
|
||||
void addContextualPanel(QWidget* panel);
|
||||
void removeContextualPanel(QWidget* panel);
|
||||
void addContextualPanel(QWidget* panel, App::Document* doc);
|
||||
void removeContextualPanel(QWidget* panel, App::Document* doc);
|
||||
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
@@ -179,14 +198,22 @@ public:
|
||||
void setRestoreWidth(bool on);
|
||||
bool shouldRestoreWidth() const;
|
||||
|
||||
std::optional<TaskInfo> currentTaskInfo() const;
|
||||
|
||||
TaskDialog* dialog(App::Document* doc);
|
||||
|
||||
// Show the task info at the index
|
||||
// or taskwatcher if index = -1
|
||||
void setShownTaskInfo(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void taskUpdate();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void accept();
|
||||
void reject();
|
||||
void helpRequested();
|
||||
void clicked(QAbstractButton* button);
|
||||
protected:
|
||||
void accept(App::Document* doc);
|
||||
void reject(App::Document* doc);
|
||||
void helpRequested(App::Document* doc);
|
||||
void clicked(QAbstractButton* button, App::Document* doc);
|
||||
|
||||
private:
|
||||
void triggerMinimumSizeHint();
|
||||
@@ -200,11 +227,6 @@ private:
|
||||
void slotUndoDocument(const App::Document&);
|
||||
void slotRedoDocument(const App::Document&);
|
||||
void transactionChangeOnDocument(const App::Document&, bool undo);
|
||||
QVBoxLayout* mainLayout;
|
||||
QScrollArea* scrollArea;
|
||||
QVBoxLayout* contextualPanelsLayout;
|
||||
QVBoxLayout* dialogLayout;
|
||||
QList<QWidget*> contextualPanels;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
@@ -214,18 +236,19 @@ protected:
|
||||
void removeTaskWatcher();
|
||||
/// update the visibility of the TaskWatcher accordant to the selection
|
||||
void updateWatcher();
|
||||
/// used by Gui::Control to register Dialogs
|
||||
void showDialog(TaskDialog* dlg);
|
||||
/// used by Gui::Control to register Dialogs, returns true if the dialog was not already there
|
||||
bool showDialog(TaskDialog* dlg, App::Document* doc);
|
||||
// removes the running dialog after accept() or reject() from the TaskView
|
||||
void removeDialog();
|
||||
void removeDialog(App::Document* doc);
|
||||
void removeDialog(std::vector<TaskInfo>::iterator infoIt);
|
||||
|
||||
void setShowTaskWatcher(bool show);
|
||||
|
||||
std::vector<TaskWatcher*> ActiveWatcher;
|
||||
TaskPanel* TaskWatcherPanel;
|
||||
|
||||
QSint::ActionPanel* taskPanel;
|
||||
TaskDialog* ActiveDialog;
|
||||
TaskEditControl* ActiveCtrl;
|
||||
// First index of the stack is reserved to the active watcher
|
||||
std::vector<TaskInfo> taskInfos;
|
||||
bool restoreWidth = false;
|
||||
int currentWidth = 0;
|
||||
ParameterGrp::handle hGrp;
|
||||
|
||||
+35
-30
@@ -47,6 +47,7 @@
|
||||
#include <Base/Writer.h>
|
||||
|
||||
#include <Base/Color.h>
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObjectGroup.h>
|
||||
#include <App/AutoTransaction.h>
|
||||
@@ -1169,8 +1170,7 @@ void TreeWidget::contextMenuEvent(QContextMenuEvent* e)
|
||||
}
|
||||
}
|
||||
contextMenu.addAction(this->selectDependentsAction);
|
||||
if (doc == App::GetApplication().getActiveDocument()
|
||||
&& this->skipRecomputeCommand != nullptr) {
|
||||
if (doc == App::GetApplication().getActiveDocument() && this->skipRecomputeCommand) {
|
||||
// if active document is selected, use Command
|
||||
this->skipRecomputeCommand->addTo(&contextMenu);
|
||||
}
|
||||
@@ -1352,10 +1352,10 @@ void TreeWidget::showEvent(QShowEvent* ev)
|
||||
void TreeWidget::onCreateGroup()
|
||||
{
|
||||
QString name = tr("Group");
|
||||
App::AutoTransaction trans("Create group");
|
||||
if (this->contextItem->type() == DocumentType) {
|
||||
auto docitem = static_cast<DocumentItem*>(this->contextItem);
|
||||
App::Document* doc = docitem->document()->getDocument();
|
||||
App::AutoTransaction trans(doc, "Create group");
|
||||
QString cmd = QStringLiteral(
|
||||
"App.getDocument(\"%1\").addObject"
|
||||
"(\"App::DocumentObjectGroup\",\"Group\").Label=\"%2\""
|
||||
@@ -1367,6 +1367,7 @@ void TreeWidget::onCreateGroup()
|
||||
auto objitem = static_cast<DocumentObjectItem*>(this->contextItem);
|
||||
App::DocumentObject* obj = objitem->object()->getObject();
|
||||
App::Document* doc = obj->getDocument();
|
||||
App::AutoTransaction trans(doc, "Create group");
|
||||
QString cmd = QStringLiteral(
|
||||
"App.getDocument(\"%1\").getObject(\"%2\")"
|
||||
".newObject(\"App::DocumentObjectGroup\",\"Group\").Label=\"%3\""
|
||||
@@ -1558,7 +1559,8 @@ void TreeWidget::onRecomputeObject()
|
||||
if (objs.empty()) {
|
||||
return;
|
||||
}
|
||||
App::AutoTransaction committer("Recompute object");
|
||||
|
||||
App::AutoTransaction committer(objs.front()->getDocument()->openTransaction("Recompute object"));
|
||||
objs.front()->getDocument()->recompute(objs, true);
|
||||
}
|
||||
|
||||
@@ -2027,7 +2029,10 @@ void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
auto objitem = static_cast<DocumentObjectItem*>(item);
|
||||
ViewProviderDocumentObject* vp = objitem->object();
|
||||
|
||||
objitem->getOwnerDocument()->document()->setActiveView(vp);
|
||||
Gui::Document* guidoc = objitem->getOwnerDocument()->document();
|
||||
App::Document* appdoc = guidoc->getDocument();
|
||||
|
||||
guidoc->setActiveView(vp);
|
||||
auto manager = Application::Instance->macroManager();
|
||||
auto lines = manager->getLines();
|
||||
|
||||
@@ -2036,8 +2041,7 @@ void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
|
||||
const char* commandText = vp->getTransactionText();
|
||||
if (commandText) {
|
||||
auto editDoc = Application::Instance->editDocument();
|
||||
App::AutoTransaction committer(commandText, true);
|
||||
appdoc->openTransaction(commandText);
|
||||
|
||||
if (!vp->doubleClicked()) {
|
||||
QTreeWidget::mouseDoubleClickEvent(event);
|
||||
@@ -2045,11 +2049,6 @@ void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
else if (lines == manager->getLines()) {
|
||||
manager->addLine(MacroManager::Gui, ss.str().c_str());
|
||||
}
|
||||
|
||||
// If the double click starts an editing, let the transaction persist
|
||||
if (!editDoc && Application::Instance->editDocument()) {
|
||||
committer.setEnable(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!vp->doubleClicked()) {
|
||||
@@ -2423,23 +2422,27 @@ bool TreeWidget::dropInDocument(
|
||||
infos.reserve(items.size());
|
||||
bool syncPlacement = TreeParams::getSyncPlacement();
|
||||
|
||||
App::AutoTransaction committer(
|
||||
da == Qt::LinkAction ? "Link object"
|
||||
: da == Qt::CopyAction ? "Copy object"
|
||||
: "Move object"
|
||||
);
|
||||
int tid = 0;
|
||||
std::string transName = da == Qt::LinkAction ? "Link object"
|
||||
: da == Qt::CopyAction ? "Copy object"
|
||||
: "Move object";
|
||||
|
||||
// check if items can be dragged
|
||||
for (auto& v : items) {
|
||||
auto item = v.first;
|
||||
auto obj = item->object()->getObject();
|
||||
auto parentItem = item->getParentItem();
|
||||
|
||||
tid = obj->getDocument()->openTransaction(
|
||||
transName,
|
||||
tid
|
||||
); // If the same document already has this transaction opened, it is ignored
|
||||
if (parentItem) {
|
||||
bool allParentsOK = canDragFromParents(parentItem, obj, nullptr);
|
||||
|
||||
if (!allParentsOK || !parentItem->object()->canDragObjects()
|
||||
|| !parentItem->object()->canDragObject(obj)) {
|
||||
committer.close(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
TREE_ERR(
|
||||
"'" << obj->getFullName() << "' cannot be dragged out of '"
|
||||
<< parentItem->object()->getObject()->getFullName() << "'"
|
||||
@@ -2641,7 +2644,7 @@ bool TreeWidget::dropInDocument(
|
||||
errMsg = "Unknown exception";
|
||||
}
|
||||
if (!errMsg.empty()) {
|
||||
committer.close(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
QMessageBox::critical(
|
||||
getMainWindow(),
|
||||
QObject::tr("Drag & drop failed"),
|
||||
@@ -2649,6 +2652,8 @@ bool TreeWidget::dropInDocument(
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
return touched;
|
||||
}
|
||||
|
||||
@@ -2716,14 +2721,12 @@ bool TreeWidget::dropInObject(
|
||||
);
|
||||
}
|
||||
|
||||
// Open command
|
||||
App::AutoTransaction committer("Drop object");
|
||||
|
||||
bool syncPlacement = TreeParams::getSyncPlacement() && targetItemObj->isGroup();
|
||||
bool setSelection = true;
|
||||
std::vector<App::DocumentObject*> draggedObjects;
|
||||
std::vector<std::pair<App::DocumentObject*, std::string>> droppedObjects;
|
||||
std::vector<ItemInfo> infos;
|
||||
int tid = 0;
|
||||
// Only keep text names here, because you never know when doing drag
|
||||
// and drop some object may delete other objects.
|
||||
infos.reserve(items.size());
|
||||
@@ -2732,6 +2735,7 @@ bool TreeWidget::dropInObject(
|
||||
auto& info = infos.back();
|
||||
auto item = v.first;
|
||||
App::DocumentObject* obj = item->object()->getObject();
|
||||
tid = obj->getDocument()->openTransaction("Drop object");
|
||||
|
||||
std::ostringstream str;
|
||||
App::DocumentObject* topParent = nullptr;
|
||||
@@ -2768,7 +2772,7 @@ bool TreeWidget::dropInObject(
|
||||
info.parentDoc = vpp->getObject()->getDocument()->getName();
|
||||
}
|
||||
else {
|
||||
committer.close(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2778,13 +2782,13 @@ bool TreeWidget::dropInObject(
|
||||
&& !vp->canDropObjectEx(obj, owner, info.subname.c_str(), item->mySubs)) {
|
||||
if (event->possibleActions() & Qt::LinkAction) {
|
||||
if (items.size() > 1) {
|
||||
committer.close(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
TREE_TRACE("Cannot replace with more than one object");
|
||||
return false;
|
||||
}
|
||||
auto ext = vp->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
|
||||
if ((!ext || !ext->getLinkedObjectProperty()) && !targetItemObj->getParentItem()) {
|
||||
committer.close(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
TREE_TRACE("Cannot replace without parent");
|
||||
return false;
|
||||
}
|
||||
@@ -3037,7 +3041,7 @@ bool TreeWidget::dropInObject(
|
||||
errMsg = "Unknown exception";
|
||||
}
|
||||
if (!errMsg.empty()) {
|
||||
committer.close(true);
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
QMessageBox::critical(
|
||||
getMainWindow(),
|
||||
QObject::tr("Drag & drop failed"),
|
||||
@@ -3045,6 +3049,7 @@ bool TreeWidget::dropInObject(
|
||||
);
|
||||
return false;
|
||||
}
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
return touched;
|
||||
}
|
||||
|
||||
@@ -3716,7 +3721,7 @@ void TreeWidget::scrollItemToTop()
|
||||
continue;
|
||||
}
|
||||
auto doc = docItem->document()->getDocument();
|
||||
if (Gui::Selection().hasSelection(doc->getName())) {
|
||||
if (Gui::Selection().hasSelection(doc->getName(), ResolveMode::OldStyleElement)) {
|
||||
tree->currentDocItem = docItem;
|
||||
docItem->selectItems(DocumentItem::SR_FORCE_EXPAND);
|
||||
tree->currentDocItem = nullptr;
|
||||
@@ -4346,13 +4351,13 @@ void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
|
||||
QColor color(Base::Color::fromPackedRGB<QColor>(col));
|
||||
|
||||
if (!getTree()->editingItem) {
|
||||
auto doc = Application::Instance->editDocument();
|
||||
if (!doc) {
|
||||
// In which cases would this return? theo-vt
|
||||
if (!Application::Instance->isInEdit(document())) {
|
||||
return;
|
||||
}
|
||||
ViewProviderDocumentObject* parentVp = nullptr;
|
||||
std::string subname;
|
||||
auto vp = doc->getInEdit(&parentVp, &subname);
|
||||
auto vp = document()->getInEdit(&parentVp, &subname);
|
||||
if (!parentVp) {
|
||||
parentVp = freecad_cast<ViewProviderDocumentObject*>(vp);
|
||||
}
|
||||
|
||||
@@ -2678,16 +2678,16 @@ void View3DInventorViewer::setSeekMode(bool on)
|
||||
);
|
||||
}
|
||||
|
||||
SbVec3f View3DInventorViewer::getCenterPointOnFocalPlane() const
|
||||
SbVec3f View3DInventorViewer::getFocalPoint() const
|
||||
{
|
||||
SoCamera* cam = getSoRenderManager()->getCamera();
|
||||
if (!cam) {
|
||||
const SoCamera* camera = getCamera();
|
||||
if (!camera) {
|
||||
return {0., 0., 0.};
|
||||
}
|
||||
|
||||
SbVec3f direction;
|
||||
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
|
||||
return cam->position.getValue() + cam->focalDistance.getValue() * direction;
|
||||
camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
|
||||
return camera->position.getValue() + camera->focalDistance.getValue() * direction;
|
||||
}
|
||||
|
||||
float View3DInventorViewer::getMaxDimension() const
|
||||
|
||||
@@ -506,7 +506,7 @@ public:
|
||||
|
||||
void getDimensions(float& fHeight, float& fWidth) const;
|
||||
float getMaxDimension() const;
|
||||
SbVec3f getCenterPointOnFocalPlane() const;
|
||||
SbVec3f getFocalPoint() const;
|
||||
|
||||
NavigationStyle* navigationStyle() const;
|
||||
|
||||
|
||||
@@ -2341,7 +2341,7 @@ Py::Object View3DInventorPy::getSceneGraph()
|
||||
{
|
||||
try {
|
||||
SoNode* scene = getView3DInventorPtr()->getViewer()->getSceneGraph();
|
||||
if (scene == nullptr) {
|
||||
if (!scene) {
|
||||
return Py::None();
|
||||
}
|
||||
PyObject* proxy = nullptr;
|
||||
|
||||
@@ -593,7 +593,7 @@ void NaviCubeSettings::applySettings()
|
||||
|
||||
void NaviCubeSettings::parameterChanged(const char* Name)
|
||||
{
|
||||
if (Name == nullptr) {
|
||||
if (!Name) {
|
||||
return;
|
||||
}
|
||||
NaviCube* nc = _viewer->getNaviCube();
|
||||
|
||||
@@ -191,7 +191,10 @@ void ViewProvider::setEditViewer(View3DInventorViewer*, int ModNum)
|
||||
|
||||
void ViewProvider::unsetEditViewer(View3DInventorViewer*)
|
||||
{}
|
||||
|
||||
void ViewProvider::setActive(bool active)
|
||||
{
|
||||
Q_UNUSED(active);
|
||||
}
|
||||
bool ViewProvider::isUpdatesEnabled() const
|
||||
{
|
||||
return testStatus(UpdateData);
|
||||
|
||||
@@ -559,6 +559,8 @@ public:
|
||||
virtual ViewProvider* startEditing(int ModNum = 0);
|
||||
bool isEditing() const;
|
||||
void finishEditing();
|
||||
virtual void setActive(bool active);
|
||||
|
||||
/// adjust viewer settings when editing a view provider
|
||||
virtual void setEditViewer(View3DInventorViewer*, int ModNum);
|
||||
/// restores viewer settings when leaving editing mode
|
||||
|
||||
@@ -272,11 +272,11 @@ void ViewProviderDocumentObject::setShowable(bool enable)
|
||||
|
||||
void ViewProviderDocumentObject::startDefaultEditMode()
|
||||
{
|
||||
QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue()));
|
||||
Gui::Command::openCommand(text.toUtf8());
|
||||
|
||||
Gui::Document* document = this->getDocument();
|
||||
if (document) {
|
||||
QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue()));
|
||||
document->openCommand(text.toUtf8()); // Command is opened here and individual dialogs have
|
||||
// to close it
|
||||
document->setEdit(this, ViewProvider::Default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +152,10 @@ bool ViewProviderDragger::forwardToLink()
|
||||
ViewProviderDocumentObject* vpParent = nullptr;
|
||||
std::string subname;
|
||||
|
||||
auto doc = Application::Instance->editDocument();
|
||||
if (!doc) {
|
||||
// since we don't want to edit another document, only forward if the
|
||||
// current document is in edit
|
||||
auto doc = getDocument();
|
||||
if (!Application::Instance->isInEdit(doc)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -209,7 +211,7 @@ bool ViewProviderDragger::setEdit(int ModNum)
|
||||
transformDragger->addFinishCallback(dragFinishCallback, this);
|
||||
transformDragger->addMotionCallback(dragMotionCallback, this);
|
||||
|
||||
Gui::Control().showDialog(getTransformDialog());
|
||||
Gui::Control().showDialog(getTransformDialog(), getDocument()->getDocument());
|
||||
|
||||
updateDraggerPosition();
|
||||
|
||||
@@ -222,7 +224,7 @@ void ViewProviderDragger::unsetEdit(int ModNum)
|
||||
|
||||
transformDragger.reset();
|
||||
|
||||
Gui::Control().closeDialog();
|
||||
Gui::Control().closeDialog(getDocument()->getDocument());
|
||||
}
|
||||
|
||||
void ViewProviderDragger::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
|
||||
|
||||
@@ -441,7 +441,8 @@ public:
|
||||
/// Starts to drag the object
|
||||
void dragObject(App::DocumentObject* obj) override
|
||||
{
|
||||
App::AutoTransaction committer;
|
||||
// AutoTransaction does not work the way it used to, plus the called method should deal with
|
||||
// transaction itself - theo-vt
|
||||
switch (imp->dragObject(obj)) {
|
||||
case ViewProviderFeaturePythonImp::Accepted:
|
||||
case ViewProviderFeaturePythonImp::Rejected:
|
||||
@@ -477,7 +478,8 @@ public:
|
||||
/// If the dropped object type is accepted the object will be added as child
|
||||
void dropObject(App::DocumentObject* obj) override
|
||||
{
|
||||
App::AutoTransaction committer;
|
||||
// AutoTransaction does not work the way it used to, plus the called method should deal with
|
||||
// transaction itself - theo-vt
|
||||
switch (imp->dropObject(obj)) {
|
||||
case ViewProviderFeaturePythonImp::Accepted:
|
||||
case ViewProviderFeaturePythonImp::Rejected:
|
||||
@@ -522,7 +524,8 @@ public:
|
||||
const std::vector<std::string>& elements
|
||||
) override
|
||||
{
|
||||
App::AutoTransaction committer;
|
||||
// AutoTransaction does not work the way it used to, plus the called method should deal with
|
||||
// transaction itself - theo-vt
|
||||
std::string ret;
|
||||
if (!imp->dropObjectEx(obj, owner, subname, elements, ret)) {
|
||||
ret = ViewProviderT::dropObjectEx(obj, owner, subname, elements);
|
||||
@@ -676,7 +679,8 @@ protected:
|
||||
|
||||
int replaceObject(App::DocumentObject* oldObj, App::DocumentObject* newObj) override
|
||||
{
|
||||
App::AutoTransaction committer;
|
||||
// AutoTransaction does not work the way it used to, plus the called method should deal with
|
||||
// transaction itself - theo-vt
|
||||
switch (imp->replaceObject(oldObj, newObj)) {
|
||||
case ViewProviderFeaturePythonImp::Accepted:
|
||||
return 1;
|
||||
@@ -717,7 +721,8 @@ public:
|
||||
protected:
|
||||
bool doubleClicked() override
|
||||
{
|
||||
App::AutoTransaction committer;
|
||||
// AutoTransaction does not work the way it used to, plus the called method should deal with
|
||||
// transaction itself - theo-vt
|
||||
switch (imp->doubleClicked()) {
|
||||
case ViewProviderFeaturePythonImp::Accepted:
|
||||
return true;
|
||||
|
||||
@@ -39,11 +39,12 @@
|
||||
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/ImagePlane.h>
|
||||
#include <Gui/Document.h>
|
||||
#include <Gui/ActionFunction.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/Control.h>
|
||||
#include <Gui/TaskView/TaskImage.h>
|
||||
#include <App/ImagePlane.h>
|
||||
|
||||
#include "ViewProviderImagePlane.h"
|
||||
|
||||
@@ -184,7 +185,7 @@ void ViewProviderImagePlane::manipulateImage()
|
||||
{
|
||||
auto dialog = new TaskImageDialog(getObject<Image::ImagePlane>());
|
||||
|
||||
Gui::Control().showDialog(dialog);
|
||||
Gui::Control().showDialog(dialog, getDocument()->getDocument());
|
||||
}
|
||||
|
||||
void ViewProviderImagePlane::resizePlane(float xsize, float ysize)
|
||||
|
||||
@@ -2982,6 +2982,8 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
return;
|
||||
}
|
||||
|
||||
Gui::Document* doc = getDocument();
|
||||
|
||||
_setupContextMenu(ext, menu, receiver, member);
|
||||
Gui::ActionFunction* func = nullptr;
|
||||
|
||||
@@ -3033,8 +3035,14 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
options |= App::Link::OnChangeCopyOptions::ApplyAll;
|
||||
}
|
||||
|
||||
App::AutoTransaction guard("Setup configurable object");
|
||||
int tid = 0;
|
||||
auto sels = dlg.getSelections(DlgObjectSelection::SelectionOptions::InvertSort);
|
||||
|
||||
// Open transaction on all touched documents if there is more than one
|
||||
for (const auto& sel : sels) {
|
||||
tid = sel->getDocument()->openTransaction("Setup configurable object", tid);
|
||||
}
|
||||
|
||||
for (const auto& exclude : excludes) {
|
||||
auto iter = std::lower_bound(sels.begin(), sels.end(), exclude);
|
||||
if (iter == sels.end() || *iter != exclude) {
|
||||
@@ -3063,6 +3071,8 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
}
|
||||
}
|
||||
Command::updateActive();
|
||||
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
e.reportException();
|
||||
@@ -3080,9 +3090,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
if (!func) {
|
||||
func = new Gui::ActionFunction(menu);
|
||||
}
|
||||
func->trigger(act, [ext]() {
|
||||
func->trigger(act, [ext, doc]() {
|
||||
try {
|
||||
App::AutoTransaction guard("Enable Link copy on change");
|
||||
App::AutoTransaction guard(doc->openCommand("Enable Link copy on change"));
|
||||
ext->getLinkCopyOnChangeProperty()->setValue(1);
|
||||
Command::updateActive();
|
||||
}
|
||||
@@ -3098,9 +3108,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
)
|
||||
);
|
||||
act->setData(-1);
|
||||
func->trigger(act, [ext]() {
|
||||
func->trigger(act, [ext, doc]() {
|
||||
try {
|
||||
App::AutoTransaction guard("Enable Link tracking");
|
||||
App::AutoTransaction guard(doc->openCommand("Enable Link tracking"));
|
||||
ext->getLinkCopyOnChangeProperty()->setValue(3);
|
||||
Command::updateActive();
|
||||
}
|
||||
@@ -3117,9 +3127,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
if (!func) {
|
||||
func = new Gui::ActionFunction(menu);
|
||||
}
|
||||
func->trigger(act, [ext]() {
|
||||
func->trigger(act, [ext, doc]() {
|
||||
try {
|
||||
App::AutoTransaction guard("Disable copy on change");
|
||||
App::AutoTransaction guard(doc->openCommand("Disable copy on change"));
|
||||
ext->getLinkCopyOnChangeProperty()->setValue((long)0);
|
||||
Command::updateActive();
|
||||
}
|
||||
@@ -3142,9 +3152,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
if (!func) {
|
||||
func = new Gui::ActionFunction(menu);
|
||||
}
|
||||
func->trigger(act, [ext]() {
|
||||
func->trigger(act, [ext, doc]() {
|
||||
try {
|
||||
App::AutoTransaction guard("Link refresh");
|
||||
App::AutoTransaction guard(doc->openCommand("Link refresh"));
|
||||
ext->syncCopyOnChange();
|
||||
Command::updateActive();
|
||||
}
|
||||
@@ -3173,9 +3183,12 @@ void ViewProviderLink::_setupContextMenu(
|
||||
|
||||
if (ext->getLinkedObjectProperty() && ext->_getShowElementProperty()
|
||||
&& ext->_getElementCountValue() > 1) {
|
||||
auto action = menu->addAction(QObject::tr("Toggle Array Elements"), [ext] {
|
||||
Gui::Document* doc = getDocument();
|
||||
auto action = menu->addAction(QObject::tr("Toggle Array Elements"), [ext, doc] {
|
||||
try {
|
||||
App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
|
||||
App::AutoTransaction guard(
|
||||
doc->openCommand(QT_TRANSLATE_NOOP("Command", "Toggle array elements"))
|
||||
);
|
||||
ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
|
||||
Command::updateActive();
|
||||
}
|
||||
@@ -3283,15 +3296,18 @@ bool ViewProviderLink::initDraggingPlacement()
|
||||
FC_ERR("no placement");
|
||||
return false;
|
||||
}
|
||||
auto doc = Application::Instance->editDocument();
|
||||
if (!doc) {
|
||||
FC_ERR("no editing document");
|
||||
|
||||
// Used to check for the only document in edit
|
||||
// now we specifically ask for the document to be the vp's document
|
||||
// I think it makes sense but there may be cases I did not consider - theo-vt
|
||||
if (!Application::Instance->isInEdit(getDocument())) {
|
||||
FC_ERR("document is not in edit");
|
||||
return false;
|
||||
}
|
||||
|
||||
dragCtx = std::make_unique<DraggerContext>();
|
||||
|
||||
dragCtx->preTransform = doc->getEditingTransform();
|
||||
dragCtx->preTransform = getDocument()->getEditingTransform();
|
||||
const auto& pla = getObject()->getPlacementProperty()->getValue();
|
||||
|
||||
// Cancel out our own transformation from the editing transform, because
|
||||
@@ -3338,7 +3354,11 @@ ViewProvider* ViewProviderLink::startEditing(int mode)
|
||||
static thread_local bool _pendingTransform;
|
||||
static thread_local Matrix4D _editingTransform;
|
||||
|
||||
auto doc = Application::Instance->editDocument();
|
||||
// Used to take the document in edit when there could only be one at a time
|
||||
// here we take the current document the vp's document if it is in edit
|
||||
// I don't think there is a case where a this function is invoked in another document
|
||||
// with the intent of modifying that other document - theo-vt
|
||||
Gui::Document* doc = Application::Instance->isInEdit(getDocument()) ? getDocument() : nullptr;
|
||||
|
||||
if (mode == ViewProvider::Transform) {
|
||||
if (_pendingTransform && doc) {
|
||||
@@ -3410,9 +3430,9 @@ bool ViewProviderLink::setEdit(int ModNum)
|
||||
if (!ext || !ext->getColoredElementsProperty()) {
|
||||
return false;
|
||||
}
|
||||
TaskView::TaskDialog* dlg = Control().activeDialog();
|
||||
TaskView::TaskDialog* dlg = Control().activeDialog(getDocument()->getDocument());
|
||||
if (dlg) {
|
||||
Control().showDialog(dlg);
|
||||
Control().showDialog(dlg, getDocument()->getDocument());
|
||||
return false;
|
||||
}
|
||||
Selection().clearSelection();
|
||||
@@ -3425,7 +3445,7 @@ bool ViewProviderLink::setEdit(int ModNum)
|
||||
void ViewProviderLink::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
|
||||
{
|
||||
if (ModNum == ViewProvider::Color) {
|
||||
Gui::Control().showDialog(new TaskElementColors(this));
|
||||
Gui::Control().showDialog(new TaskElementColors(this), getDocument()->getDocument());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3944,7 +3964,7 @@ void ViewProviderLink::getPropertyMap(std::map<std::string, App::Property*>& Map
|
||||
void ViewProviderLink::visitProperties(const std::function<void(App::Property*)>& visitor) const
|
||||
{
|
||||
inherited::visitProperties(visitor);
|
||||
if (childVp != nullptr) {
|
||||
if (childVp) {
|
||||
childVp->visitProperties(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +95,7 @@ void ViewProviderTextureExtension::setCoinAppearance(
|
||||
const App::Material& source
|
||||
)
|
||||
{
|
||||
#if 0
|
||||
if (!source.image.empty()) {
|
||||
Base::Console().log("setCoinAppearance(Texture)\n");
|
||||
activateTexture2D();
|
||||
|
||||
QByteArray by = QByteArray::fromBase64(QString::fromStdString(source.image).toUtf8());
|
||||
@@ -106,12 +104,10 @@ void ViewProviderTextureExtension::setCoinAppearance(
|
||||
SoSFImage texture;
|
||||
Gui::BitmapFactory().convert(image, texture);
|
||||
pcShapeTexture2D->image = texture;
|
||||
} else {
|
||||
Base::Console().log("setCoinAppearance(Material)\n");
|
||||
}
|
||||
else {
|
||||
activateMaterial();
|
||||
}
|
||||
#endif
|
||||
activateMaterial();
|
||||
|
||||
// Always set the material for items such as lines that don't support textures
|
||||
pcShapeMaterial->ambientColor
|
||||
|
||||
@@ -324,7 +324,7 @@ void WorkbenchTabWidget::updateWorkbenchList()
|
||||
addWorkbenchTab(action);
|
||||
}
|
||||
|
||||
if (temporaryWorkbenchAction != nullptr) {
|
||||
if (temporaryWorkbenchAction) {
|
||||
setTemporaryWorkbenchTab(temporaryWorkbenchAction);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
#include <QActionGroup>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/AutoTransaction.h>
|
||||
#include <App/Document.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Gui/Document.h>
|
||||
|
||||
#include "Document.h"
|
||||
#include "Tree.h"
|
||||
@@ -358,11 +358,6 @@ void PropertyEditor::openEditor(const QModelIndex& index)
|
||||
return;
|
||||
}
|
||||
|
||||
auto& app = App::GetApplication();
|
||||
if (app.getActiveTransaction()) {
|
||||
FC_LOG("editor already transacting " << app.getActiveTransaction());
|
||||
return;
|
||||
}
|
||||
auto item = static_cast<PropertyItem*>(editingIndex.internalPointer());
|
||||
auto items = item->getPropertyData();
|
||||
for (auto propItem = item->parent(); items.empty() && propItem; propItem = propItem->parent()) {
|
||||
@@ -407,8 +402,8 @@ void PropertyEditor::openEditor(const QModelIndex& index)
|
||||
if (items.size() > 1) {
|
||||
str << "...";
|
||||
}
|
||||
transactionID = app.setActiveTransaction(str.str().c_str());
|
||||
FC_LOG("editor transaction " << app.getActiveTransaction());
|
||||
transactionID = obj->getDocument()->openTransaction(str.str().c_str());
|
||||
FC_LOG("editor transaction " << App::GetApplication().getTransactionName(transactionID));
|
||||
}
|
||||
|
||||
void PropertyEditor::onItemActivated(const QModelIndex& index)
|
||||
@@ -462,13 +457,15 @@ void PropertyEditor::recomputeDocument(App::Document* doc)
|
||||
|
||||
void PropertyEditor::closeTransaction()
|
||||
{
|
||||
int tid = 0;
|
||||
if (App::GetApplication().getActiveTransaction(&tid) && tid == transactionID) {
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
if (doc->getBookedTransactionID() == transactionID) {
|
||||
if (autoupdate) {
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
recomputeDocument(doc);
|
||||
}
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
doc->commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,7 +825,7 @@ void PropertyEditor::setFirstLevelExpanded(bool doExpand)
|
||||
}
|
||||
|
||||
auto* item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
if (item == nullptr || item->childCount() <= 0) {
|
||||
if (!item || item->childCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -941,15 +938,20 @@ std::unordered_set<App::Property*> PropertyEditor::acquireSelectedProperties() c
|
||||
|
||||
void PropertyEditor::removeProperties(const std::unordered_set<App::Property*>& props)
|
||||
{
|
||||
App::AutoTransaction committer("Remove property");
|
||||
int tid = 0;
|
||||
for (auto prop : props) {
|
||||
try {
|
||||
if (App::Document* doc = propertyDocument(prop->getContainer())) {
|
||||
tid = doc->openTransaction("Remove property");
|
||||
}
|
||||
prop->getContainer()->removeDynamicProperty(prop->getName());
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
e.reportException();
|
||||
}
|
||||
}
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
}
|
||||
|
||||
void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
|
||||
@@ -1157,12 +1159,17 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
|
||||
break;
|
||||
case MA_AddProp: {
|
||||
App::PropertyContainer* container = getSelectedPropertyContainer();
|
||||
if (container == nullptr) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
App::AutoTransaction committer("Add property");
|
||||
int tid = 0;
|
||||
if (App::Document* doc = propertyDocument(container)) {
|
||||
tid = doc->openTransaction("Add property");
|
||||
}
|
||||
Gui::Dialog::DlgAddProperty dlg(Gui::getMainWindow(), container);
|
||||
dlg.exec();
|
||||
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
return;
|
||||
}
|
||||
case MA_EditPropTooltip: {
|
||||
@@ -1203,8 +1210,10 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
|
||||
|| prop->testStatus(App::Property::LockDynamic)) {
|
||||
break;
|
||||
}
|
||||
|
||||
App::AutoTransaction committer("Rename property");
|
||||
int tid = 0;
|
||||
if (App::Document* doc = propertyDocument(prop->getContainer())) {
|
||||
tid = doc->openTransaction("Rename property");
|
||||
}
|
||||
const char* oldName = prop->getName();
|
||||
QString res = QInputDialog::getText(
|
||||
Gui::getMainWindow(),
|
||||
@@ -1222,9 +1231,11 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
|
||||
prop->getContainer()->renameDynamicProperty(prop, newName.c_str());
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
App::GetApplication().abortTransaction(tid);
|
||||
e.reportException();
|
||||
break;
|
||||
}
|
||||
App::GetApplication().commitTransaction(tid);
|
||||
break;
|
||||
}
|
||||
case MA_EditPropGroup: {
|
||||
@@ -1324,5 +1335,18 @@ QModelIndex PropertyEditor::indexResizable(QPoint mouse_pos)
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
App::Document* PropertyEditor::propertyDocument(App::PropertyContainer* cont) const
|
||||
{
|
||||
if (auto* doc = dynamic_cast<App::Document*>(cont)) {
|
||||
return doc;
|
||||
}
|
||||
if (auto* docObj = dynamic_cast<App::DocumentObject*>(cont)) {
|
||||
return docObj->getDocument();
|
||||
}
|
||||
if (auto* vp = dynamic_cast<ViewProviderDocumentObject*>(cont)) {
|
||||
return vp->getDocument()->getDocument();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#include "moc_PropertyEditor.cpp"
|
||||
|
||||
@@ -154,6 +154,8 @@ private:
|
||||
// and return the index of that cell if found
|
||||
QModelIndex indexResizable(QPoint mouse_pos);
|
||||
|
||||
App::Document* propertyDocument(App::PropertyContainer* cont) const;
|
||||
|
||||
private:
|
||||
PropertyItemDelegate* delegate;
|
||||
PropertyModel* propertyModel;
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#include <Gui/SpinBox.h>
|
||||
#include <Gui/VectorListEditor.h>
|
||||
#include <Gui/ViewProviderDocumentObject.h>
|
||||
#include <Gui/Document.h>
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-pro-*,cppcoreguidelines-prefer-member-initializer)
|
||||
using namespace Gui::PropertyEditor;
|
||||
@@ -2650,12 +2651,14 @@ PlacementEditor::~PlacementEditor() = default;
|
||||
|
||||
void PlacementEditor::browse()
|
||||
{
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
|
||||
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(
|
||||
Gui::Application::Instance->activeDocument()->getDocument()
|
||||
);
|
||||
Gui::Dialog::TaskPlacement* task {};
|
||||
task = qobject_cast<Gui::Dialog::TaskPlacement*>(dlg);
|
||||
if (dlg && !task) {
|
||||
// there is already another task dialog which must be closed first
|
||||
Gui::Control().showDialog(dlg);
|
||||
Gui::Control().showDialog(dlg, Gui::Application::Instance->activeDocument()->getDocument());
|
||||
return;
|
||||
}
|
||||
if (!task) {
|
||||
@@ -2669,7 +2672,7 @@ void PlacementEditor::browse()
|
||||
task->setPropertyName(propertyname);
|
||||
task->setSelection(Gui::Selection().getSelectionEx());
|
||||
task->bindObject();
|
||||
Gui::Control().showDialog(task);
|
||||
Gui::Control().showDialog(task, Gui::Application::Instance->activeDocument()->getDocument());
|
||||
}
|
||||
|
||||
void PlacementEditor::showValue(const QVariant& d)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user