diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1fe23f0a77b..6c7d8542328 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,15 @@ on: type: boolean required: false default: false + api_check: + description: | + Run the API tools check and generate the javadoc (one of true, false) + + Both only depend on the Java sources, so enable this for a single job + per operating system. + type: boolean + required: false + default: false gtk: description: | (Required on Linux only) GTK version to use (one of gtk3, gtk4) @@ -108,7 +117,7 @@ jobs: --threads 1C -DforkCount=1 '-Dnative=${{ inputs.native }}' - -Papi-check -Pjavadoc + ${{ inputs.api_check && '-Papi-check -Pjavadoc' || '' }} '-Dtycho.baseline.replace=none' --fail-at-end -DskipNativeTests=false diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 058f94f7570..7249d12cae3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Paths filter (PRs only) if: github.event.pull_request.base - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: filter with: predicate-quantifier: 'every' @@ -93,6 +93,8 @@ jobs: native: gtk.linux.x86_64 gtk: ${{ matrix.gtk }} gdk_backend: ${{ matrix.gdk_backend }} + # All Linux jobs build the same Java sources, so check the API and javadoc only once + api_check: ${{ matrix.gtk == 'gtk3' && matrix.gdk_backend == 'x11' }} performance: ${{ contains(github.event.pull_request.labels.*.name, 'performance') }} runtodotests: ${{ contains(github.event.pull_request.labels.*.name, 'runtodotests') }} @@ -110,6 +112,7 @@ jobs: runner: windows-latest java: ${{ matrix.java }} native: win32.win32.x86_64 + api_check: true performance: ${{ contains(github.event.pull_request.labels.*.name, 'performance') }} runtodotests: ${{ contains(github.event.pull_request.labels.*.name, 'runtodotests') }} @@ -128,5 +131,7 @@ jobs: runner: ${{ matrix.arch == 'x86_64' && 'macos-15-intel' || 'macos-latest' }} java: ${{ matrix.java }} native: cocoa.macosx.${{ matrix.arch }} + # Both macOS fragments compile the same Java sources, so check the API and javadoc only once + api_check: ${{ matrix.arch == 'aarch64' }} performance: ${{ contains(github.event.pull_request.labels.*.name, 'performance') }} runtodotests: ${{ contains(github.event.pull_request.labels.*.name, 'runtodotests') }} diff --git a/Jenkinsfile b/Jenkinsfile index 0e32bcfa3a4..02cc55870e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -21,10 +21,10 @@ def runOnNativeBuildAgent(String platform, Closure body) { def dockerImage = null switch (platform) { case 'gtk.linux.x86_64': - dockerImage = 'eclipse/platformreleng-debian-swtgtk3nativebuild:10' + dockerImage = 'ghcr.io/eclipse-platform/platformreleng-debian-swtgtk3nativebuild:11' break case 'gtk4.linux.x86_64': - dockerImage = 'eclipse/platformreleng-debian-swtnativebuild:12' + dockerImage = 'ghcr.io/eclipse-platform/platformreleng-debian-swtnativebuild:12' break } if (dockerImage != null) { diff --git a/NOTICE b/NOTICE index 4785673f8a9..a8ba871c77e 100644 --- a/NOTICE +++ b/NOTICE @@ -26,11 +26,7 @@ SPDX-License-Identifier: EPL-2.0 The project maintains the following source code repositories: -* https://github.com/eclipse-platform/eclipse.platform.debug.git * https://github.com/eclipse-platform/eclipse.platform.git * https://github.com/eclipse-platform/eclipse.platform.releng.aggregator.git -* https://github.com/eclipse-platform/eclipse.platform.releng.buildtools.git -* https://github.com/eclipse-platform/eclipse.platform.releng.git * https://github.com/eclipse-platform/eclipse.platform.swt.git -* https://github.com/eclipse-platform/eclipse.platform.ua.git * https://github.com/eclipse-platform/eclipse.platform.ui.git diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-awt-cocoa-4974r10.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-awt-cocoa-4974r10.jnilib new file mode 100755 index 00000000000..193936f7d75 --- /dev/null +++ b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-awt-cocoa-4974r10.jnilib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b91a45d91cda3adf3f5ed3b6649f2f7db10b26d82e444be48081e1d1dba80ff0 +size 51920 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-awt-cocoa-4974r7.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-awt-cocoa-4974r7.jnilib deleted file mode 100755 index e260a120925..00000000000 --- a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-awt-cocoa-4974r7.jnilib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c86c4e9806966f279b43bf0aaf1a05ee32084b989dbc93c07bac6ae6524f0b2 -size 51920 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-cocoa-4974r10.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-cocoa-4974r10.jnilib new file mode 100755 index 00000000000..d2cf553084a --- /dev/null +++ b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-cocoa-4974r10.jnilib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6d7d7f093d1b60b02db555814c1da2a85e4e2c590a6533697d9c1d4f0673645 +size 576448 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-cocoa-4974r7.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-cocoa-4974r7.jnilib deleted file mode 100755 index 109c95ad9ac..00000000000 --- a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-cocoa-4974r7.jnilib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d301ef8585d5cd866068833cda379455f2978e9dab1487ab6cc2d8d8a1268e43 -size 576448 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-pi-cocoa-4974r10.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-pi-cocoa-4974r10.jnilib new file mode 100755 index 00000000000..6c04d9d152d --- /dev/null +++ b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-pi-cocoa-4974r10.jnilib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3faad221ca39561f0eb501493fa2e93f4d3f388d668904ed2269067dfe0fc71a +size 299216 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-pi-cocoa-4974r7.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-pi-cocoa-4974r7.jnilib deleted file mode 100755 index b35950e3afd..00000000000 --- a/binaries/org.eclipse.swt.cocoa.macosx.aarch64/libswt-pi-cocoa-4974r7.jnilib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a42fe403f58fe11def681c83f4b2901fd69fffc7f241f5dea2d3e02f5a6c87e6 -size 299216 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-awt-cocoa-4974r10.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-awt-cocoa-4974r10.jnilib new file mode 100755 index 00000000000..076d7d9f0ea --- /dev/null +++ b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-awt-cocoa-4974r10.jnilib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc89f6dafcfd5726e26c1f761cf51992ce1624680d6a17052841b49579b9e0b9 +size 27184 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-awt-cocoa-4974r7.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-awt-cocoa-4974r7.jnilib deleted file mode 100755 index 3c47aa48318..00000000000 --- a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-awt-cocoa-4974r7.jnilib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:798b62759bbc4863e06ffdb6cb4e1f2dad4a25eb54969186c2e5bca427118404 -size 27184 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-cocoa-4974r10.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-cocoa-4974r10.jnilib new file mode 100755 index 00000000000..5af53cf85e1 --- /dev/null +++ b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-cocoa-4974r10.jnilib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d9dc5f0aa8cf0680e8884226c7f4f4b355845b48841cc733fe6235fd6f7d58f +size 510304 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-cocoa-4974r7.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-cocoa-4974r7.jnilib deleted file mode 100755 index 00af6b76c12..00000000000 --- a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-cocoa-4974r7.jnilib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42f500c1385e93afb3079b4d5ab0274cbfb90cd9d231bf7be332f557b563563c -size 510304 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-pi-cocoa-4974r10.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-pi-cocoa-4974r10.jnilib new file mode 100755 index 00000000000..30f122ad952 --- /dev/null +++ b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-pi-cocoa-4974r10.jnilib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:349229fe2d62df988537749c5e5458025093f99ea43cbe2d32e964542368a806 +size 258112 diff --git a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-pi-cocoa-4974r7.jnilib b/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-pi-cocoa-4974r7.jnilib deleted file mode 100755 index 959936fa351..00000000000 --- a/binaries/org.eclipse.swt.cocoa.macosx.x86_64/libswt-pi-cocoa-4974r7.jnilib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0e90169bfc31ead9a45414b2544309a472a37c69f36e6d134edfe81f38d8eff -size 258112 diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-atk-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-atk-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-atk-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-atk-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-awt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-awt-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-awt-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-awt-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-cairo-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-cairo-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-cairo-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-cairo-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-glx-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-glx-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-glx-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-glx-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-pi3-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-pi3-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-pi3-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-pi3-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-webkit-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-webkit-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-webkit-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.aarch64/libswt-webkit-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-atk-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-atk-gtk-4974r10.so new file mode 100755 index 00000000000..faec186a178 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-atk-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02edb508272d91454044ee5fc1cdd486e2dfc52608aef1bc0578b9de50fd0404 +size 67608 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-atk-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-atk-gtk-4974r7.so deleted file mode 100755 index 866f0196467..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-atk-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6105e985fddce365478b8aeba4ed07718f2abea577deaf74f119b7c3bab73793 -size 67608 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-awt-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-awt-gtk-4974r10.so new file mode 100755 index 00000000000..ed598da0a80 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-awt-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:149f6d78e1001cc2ab7babfccc1cc13f961ae81b6e8b0d45edd3e069570d2263 +size 67504 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-awt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-awt-gtk-4974r7.so deleted file mode 100755 index a411e5c705a..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-awt-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e88636ff78f3c9223f1a544b8a5ff362a6c4e097dab64868d46aeb22f62ba5d -size 67504 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-cairo-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-cairo-gtk-4974r10.so new file mode 100755 index 00000000000..e52ab540d36 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-cairo-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81267b1171b2cb056658b2d0ace25af65ef3f0d9eedbae40243c5ea64331c3a3 +size 67632 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-cairo-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-cairo-gtk-4974r7.so deleted file mode 100755 index 6fa478024e1..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-cairo-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d309cfa3c57b00ff95b458a1a582e35d295320d4c4c94232d5ad327507a61e64 -size 67632 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-glx-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-glx-gtk-4974r10.so new file mode 100755 index 00000000000..c7fab180bf5 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-glx-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e47dd49778bfd1ba83c5716f54a9d141de08c8f6ca1ccb5405e589da0a3003f +size 67520 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-glx-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-glx-gtk-4974r7.so deleted file mode 100755 index c045c6583aa..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-glx-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6dd11de26e50342856c5db2413630cc36b64a134aaea62b56d0ee1831904980a -size 67520 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-gtk-4974r10.so new file mode 100755 index 00000000000..083275529a1 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a797c983873bc4ec333d65b66361c8c4605027171f88be1dcf2459c6cd1dac07 +size 819648 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-gtk-4974r7.so deleted file mode 100755 index 0729bc3fea8..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04d777f0fc41b953a9ce0c28d1caa616c1a201e01ca111b9fa6dcff745d4d4ca -size 819648 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-pi3-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-pi3-gtk-4974r10.so new file mode 100755 index 00000000000..c237cab0b85 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-pi3-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bad4c356eff6fca4b5c3965c648383c6ae5c2873ebb06ca3580f64f7e0b98044 +size 668048 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-pi3-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-pi3-gtk-4974r7.so deleted file mode 100755 index fe122c7e411..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-pi3-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6e053a824e7f8bb66f22adbdd19e5e650bc87b6823356f879b321e9c2ebd209 -size 668048 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-webkit-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-webkit-gtk-4974r10.so new file mode 100755 index 00000000000..e61251cf672 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-webkit-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a864f82f00d18138f1c1fa9840b2a606831a22fb7c232d7234488d7dbce401fb +size 133160 diff --git a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-webkit-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-webkit-gtk-4974r7.so deleted file mode 100755 index aeeefc3dfc5..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.ppc64le/libswt-webkit-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:986bf233d8784f7a363ccbdc1861de2f43a0450711aec872297ae923465aba3b -size 133160 diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-atk-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-atk-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-atk-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-atk-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-awt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-awt-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-awt-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-awt-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-cairo-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-cairo-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-cairo-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-cairo-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-glx-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-glx-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-glx-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-glx-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-pi3-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-pi3-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-pi3-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-pi3-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-webkit-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-webkit-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-webkit-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.riscv64/libswt-webkit-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-atk-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-awt-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-cairo-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-glx-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi3-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4974r10.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4974r10.so new file mode 100755 index 00000000000..24076b08fe0 --- /dev/null +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4974r10.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:295115cdab2cb3953258fc7ee33abc103752931ec37412aa81d17a4241a9cea3 +size 425160 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4974r7.so deleted file mode 100755 index efb339e2204..00000000000 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-pi4-gtk-4974r7.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc32e044c9eb741659bcf8213e1850ee201fe834aaf14edcdbdb2f8d42f9a757 -size 425088 diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4974r7.so b/binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4974r10.so similarity index 100% rename from binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4974r7.so rename to binaries/org.eclipse.swt.gtk.linux.x86_64/libswt-webkit-gtk-4974r10.so diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/swt-awt-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.aarch64/swt-awt-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.aarch64/swt-awt-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.aarch64/swt-awt-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/swt-gdip-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.aarch64/swt-gdip-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.aarch64/swt-gdip-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.aarch64/swt-gdip-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/swt-osversion-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.aarch64/swt-osversion-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.aarch64/swt-osversion-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.aarch64/swt-osversion-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/swt-wgl-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.aarch64/swt-wgl-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.aarch64/swt-wgl-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.aarch64/swt-wgl-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.aarch64/swt-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.aarch64/swt-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.aarch64/swt-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.aarch64/swt-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/swt-awt-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.x86_64/swt-awt-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.x86_64/swt-awt-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.x86_64/swt-awt-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/swt-gdip-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.x86_64/swt-gdip-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.x86_64/swt-gdip-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.x86_64/swt-gdip-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/swt-osversion-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.x86_64/swt-osversion-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.x86_64/swt-osversion-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.x86_64/swt-osversion-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/swt-wgl-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.x86_64/swt-wgl-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.x86_64/swt-wgl-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.x86_64/swt-wgl-win32-4974r10.dll diff --git a/binaries/org.eclipse.swt.win32.win32.x86_64/swt-win32-4974r7.dll b/binaries/org.eclipse.swt.win32.win32.x86_64/swt-win32-4974r10.dll similarity index 100% rename from binaries/org.eclipse.swt.win32.win32.x86_64/swt-win32-4974r7.dll rename to binaries/org.eclipse.swt.win32.win32.x86_64/swt-win32-4974r10.dll diff --git a/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF b/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF index 6c5ef0ac6d0..b90424780d8 100644 --- a/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.swt.tools/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-SymbolicName: org.eclipse.swt.tools; singleton:=true -Bundle-Version: 3.112.0.qualifier +Bundle-Version: 3.112.100.qualifier Bundle-ManifestVersion: 2 Export-Package: org.eclipse.swt.tools.internal; x-internal:=true Bundle-ActivationPolicy: lazy diff --git a/bundles/org.eclipse.swt.tools/Mac Generation/org/eclipse/swt/tools/internal/MacGenerator.java b/bundles/org.eclipse.swt.tools/Mac Generation/org/eclipse/swt/tools/internal/MacGenerator.java index df6f5c332e1..7c666e300ff 100644 --- a/bundles/org.eclipse.swt.tools/Mac Generation/org/eclipse/swt/tools/internal/MacGenerator.java +++ b/bundles/org.eclipse.swt.tools/Mac Generation/org/eclipse/swt/tools/internal/MacGenerator.java @@ -982,6 +982,8 @@ public String[] getExtraAttributeNames(Node node) { } } else if (name.equals("class")) { return new String[]{"swt_superclass"}; + } else if (name.equals("enum")) { + return new String[]{"swt_value_aarch64"}; } else if (name.equals("struct")) { return new String[]{"swt_gen_memmove", "swt_gen_tostring"}; } else if (name.equals("retval")) { @@ -1147,10 +1149,10 @@ void generateEnums() { if (value.indexOf('.') != -1) { out("double "); } else { - if (value.equals("4294967295")) { + if (isUint32Max(value)) { out("int "); value = "-1"; - } else if (value.equals("18446744073709551615")) { + } else if (isUint64Max(value)) { out("long "); value = "-1L"; } else { @@ -1165,8 +1167,20 @@ void generateEnums() { } out(attributes.getNamedItem("name").getNodeValue()); out(" = "); - out(value); - if (isLong && !value.endsWith("L")) out("L"); + Node aarch64ValueNode = attributes.getNamedItem("swt_value_aarch64"); + if (aarch64ValueNode != null) { + out("IS_X86_64 ? "); + out(value); + if (isLong && !value.endsWith("L")) out("L"); + out(" : "); + String aarch64Value = aarch64ValueNode.getNodeValue(); + aarch64Value = isUint32Max(aarch64Value) ? "-1" : isUint64Max(aarch64Value) ? "-1L" : aarch64Value; + out(aarch64Value); + if (isLong && !aarch64Value.endsWith("L")) out("L"); + } else { + out(value); + if (isLong && !value.endsWith("L")) out("L"); + } out(";"); outln(); } else { @@ -1178,6 +1192,17 @@ void generateEnums() { } } +private static final String UINT32_MAX = "4294967295"; +private static final String UINT64_MAX = "18446744073709551615"; + +private boolean isUint32Max(String value) { + return value.equals(UINT32_MAX); +} + +private boolean isUint64Max(String value) { + return value.equals(UINT64_MAX); +} + boolean getGen(Node node) { NamedNodeMap attributes = node.getAttributes(); if (attributes == null) return false; diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java index 1e61652cef8..418da72a628 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java @@ -172,6 +172,7 @@ public class CTabFolder extends Composite { int[] priority = new int[0]; boolean mru = false; Listener listener; + Listener tabControlZoomListener; boolean ignoreTraverse; boolean useDefaultRenderer; @@ -345,6 +346,10 @@ void init(int style) { } }; + // A tab control is rescaled after the folder itself, so its size can only be + // measured reliably once it has processed the zoom change on its own. + tabControlZoomListener = event -> updateFolder(UPDATE_TAB_HEIGHT | REDRAW); + int[] folderEvents = new int[]{ SWT.Dispose, SWT.DragDetect, @@ -2057,6 +2062,8 @@ void onPageTraversal(Event event) { if (e.doit && !isDisposed()) { showList(chevronRect); } + } else { + index = visible [(current + offset + idx) % idx]; } } } @@ -4184,6 +4191,7 @@ void addTabControl(Control control, int flags, int index, boolean update) { int length = controls.length; control.addListener(SWT.Resize, listener); + control.addListener(SWT.ZoomChanged, tabControlZoomListener); //Grow all 4 arrays Control[] newControls = new Control [length + 1]; @@ -4245,6 +4253,7 @@ void removeTabControl (Control control, boolean update) { if (!control.isDisposed()) { control.removeListener(SWT.Resize, listener); + control.removeListener(SWT.ZoomChanged, tabControlZoomListener); control.setBackground (null); control.setBackgroundImage (null); if (control instanceof Composite) ((Composite) control).setBackgroundMode(SWT.INHERIT_NONE); diff --git a/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/cocoa/org/eclipse/swt/dnd/DropTarget.java b/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/cocoa/org/eclipse/swt/dnd/DropTarget.java index c9df3a4b9f5..d327f475414 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/cocoa/org/eclipse/swt/dnd/DropTarget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/cocoa/org/eclipse/swt/dnd/DropTarget.java @@ -575,7 +575,7 @@ int getOperationFromKeyState() { if (currEvent != null) { long modifiers = currEvent.modifierFlags(); boolean option = (modifiers & OS.NSAlternateKeyMask) == OS.NSAlternateKeyMask; - boolean control = (modifiers & OS.NSControlKeyMask) == OS.NSControlKeyMask; + boolean control = (modifiers & OS.NSEventModifierFlagControl) == OS.NSEventModifierFlagControl; if (control && option) return DND.DROP_DEFAULT; if (control) return DND.DROP_LINK; if (option) return DND.DROP_COPY; diff --git a/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DragSource.java b/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DragSource.java index 2918749b67e..ee334b146c2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DragSource.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DragSource.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -117,6 +117,9 @@ public class DragSource extends Widget { long targetList; + /* GTK4 GtkDragSource event controller added to the control */ + long dragSourceController; + //workaround - remember action performed for DragEnd boolean moveData = false; @@ -182,7 +185,7 @@ public DragSource(Control control, int style) { } control.setData(DND.DRAG_SOURCE_KEY, this); - long dragSourceController = GTK4.gtk_drag_source_new(); + dragSourceController = GTK4.gtk_drag_source_new(); GTK4.gtk_widget_add_controller(control.handle, dragSourceController); OS.g_signal_connect(dragSourceController, OS.prepare, dragPrepareProc.getAddress(), 0); @@ -207,21 +210,6 @@ public DragSource(Control control, int style) { OS.g_signal_connect(control.handle, OS.drag_end, DragEnd.getAddress(), 0); OS.g_signal_connect(control.handle, OS.drag_data_delete, DragDataDelete.getAddress(), 0); - controlListener = event -> { - if (event.type == SWT.Dispose) { - if (!DragSource.this.isDisposed()) { - DragSource.this.dispose(); - } - } - if (event.type == SWT.DragDetect) { - if (!DragSource.this.isDisposed()) { - DragSource.this.drag(event); - } - } - }; - control.addListener (SWT.Dispose, controlListener); - control.addListener (SWT.DragDetect, controlListener); - Object effect = control.getData(DEFAULT_DRAG_SOURCE_EFFECT); if (effect instanceof DragSourceEffect) { dragEffect = (DragSourceEffect) effect; @@ -232,9 +220,26 @@ public DragSource(Control control, int style) { } else if (control instanceof List) { dragEffect = new ListDragSourceEffect((List) control); } - - this.addListener(SWT.Dispose, e -> onDispose()); } + + // Dispose listeners + controlListener = event -> { + if (event.type == SWT.Dispose) { + if (!DragSource.this.isDisposed()) { + DragSource.this.dispose(); + } + } + if (event.type == SWT.DragDetect) { + if (!DragSource.this.isDisposed()) { + DragSource.this.drag(event); + } + } + }; + control.addListener (SWT.Dispose, controlListener); + // On GTK4 the drag is started by the GtkDragSource controller, not by SWT.DragDetect. + if (!GTK.GTK4) control.addListener (SWT.DragDetect, controlListener); + + this.addListener(SWT.Dispose, e -> onDispose()); } static int checkStyle (int style) { @@ -621,7 +626,12 @@ public Transfer[] getTransfer(){ void onDispose() { if (control == null) return; - if (targetList != 0) { + if (GTK.GTK4) { + if (dragSourceController != 0) { + GTK4.gtk_widget_remove_controller(control.handle, dragSourceController); + dragSourceController = 0; + } + } else if (targetList != 0) { GTK3.gtk_target_list_unref(targetList); } targetList = 0; diff --git a/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DropTarget.java b/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DropTarget.java index 6ca2c9a334a..8eafce3dcc6 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DropTarget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/DropTarget.java @@ -14,9 +14,12 @@ package org.eclipse.swt.dnd; +import java.lang.reflect.*; + import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.*; +import org.eclipse.swt.internal.GAsyncReadyCallbackHelper.*; import org.eclipse.swt.internal.gtk.*; import org.eclipse.swt.internal.gtk3.*; import org.eclipse.swt.internal.gtk4.*; @@ -111,11 +114,26 @@ public class DropTarget extends Widget { static Callback Drag_Data_Received; static Callback Drag_Drop; + /* GTK4 specific callbacks for GtkDropTargetAsync signals */ + static Callback DropAccept; + static Callback DropEnter; + static Callback DropMotion; + static Callback DropLeave; + static Callback Drop; + static { - Drag_Motion = new Callback(DropTarget.class, "Drag_Motion", 5); //$NON-NLS-1$ - Drag_Leave = new Callback(DropTarget.class, "Drag_Leave", 3); //$NON-NLS-1$ - Drag_Data_Received = new Callback(DropTarget.class, "Drag_Data_Received", 7); //$NON-NLS-1$ - Drag_Drop = new Callback(DropTarget.class, "Drag_Drop", 5); //$NON-NLS-1$ + if (GTK.GTK4) { + DropAccept = new Callback(DropTarget.class, "DropAccept", long.class, new Type[] {long.class, long.class}); //$NON-NLS-1$ + DropEnter = new Callback(DropTarget.class, "DropEnter", long.class, new Type[] {long.class, long.class, double.class, double.class}); //$NON-NLS-1$ + DropMotion = new Callback(DropTarget.class, "DropMotion", long.class, new Type[] {long.class, long.class, double.class, double.class}); //$NON-NLS-1$ + DropLeave = new Callback(DropTarget.class, "DropLeave", void.class, new Type[] {long.class, long.class}); //$NON-NLS-1$ + Drop = new Callback(DropTarget.class, "Drop", long.class, new Type[] {long.class, long.class, double.class, double.class}); //$NON-NLS-1$ + } else { + Drag_Motion = new Callback(DropTarget.class, "Drag_Motion", 5); //$NON-NLS-1$ + Drag_Leave = new Callback(DropTarget.class, "Drag_Leave", 3); //$NON-NLS-1$ + Drag_Data_Received = new Callback(DropTarget.class, "Drag_Data_Received", 7); //$NON-NLS-1$ + Drag_Drop = new Callback(DropTarget.class, "Drag_Drop", 5); //$NON-NLS-1$ + } } /* GTK4 specific */ @@ -157,10 +175,23 @@ public DropTarget(Control control, int style) { this.control = control; if (GTK.GTK4) { + if (DropAccept == null || DropEnter == null || DropMotion == null || DropLeave == null || Drop == null) { + DND.error(DND.ERROR_CANNOT_INIT_DROP); + } + if (control.getData(DND.DROP_TARGET_KEY) != null) { + DND.error(DND.ERROR_CANNOT_INIT_DROP); + } + control.setData(DND.DROP_TARGET_KEY, this); + int actions = opToOsOp(style); dropController = GTK4.gtk_drop_target_async_new(0, actions); - GTK4.gtk_widget_add_controller(control.handle, dropController); + + OS.g_signal_connect(dropController, OS.accept, DropAccept.getAddress(), 0); + OS.g_signal_connect(dropController, OS.drag_enter, DropEnter.getAddress(), 0); + OS.g_signal_connect(dropController, OS.drag_motion, DropMotion.getAddress(), 0); + OS.g_signal_connect(dropController, OS.drag_leave, DropLeave.getAddress(), 0); + OS.g_signal_connect(dropController, OS.drop, Drop.getAddress(), 0); } else { if (Drag_Motion == null || Drag_Leave == null || Drag_Data_Received == null || Drag_Drop == null) { DND.error(DND.ERROR_CANNOT_INIT_DROP); @@ -176,74 +207,74 @@ public DropTarget(Control control, int style) { drag_leave_handler = OS.g_signal_connect(control.handle, OS.drag_leave, Drag_Leave.getAddress(), 0); drag_data_received_handler = OS.g_signal_connect(control.handle, OS.drag_data_received, Drag_Data_Received.getAddress(), 0); drag_drop_handler = OS.g_signal_connect(control.handle, OS.drag_drop, Drag_Drop.getAddress(), 0); + } - // Dispose listeners - controlListener = event -> { - if (!DropTarget.this.isDisposed()){ - DropTarget.this.dispose(); - } - }; - control.addListener(SWT.Dispose, controlListener); - - this.addListener(SWT.Dispose, event -> onDispose()); - - Object effect = control.getData(DEFAULT_DROP_TARGET_EFFECT); - if (effect instanceof DropTargetEffect) { - dropEffect = (DropTargetEffect) effect; - } else if (control instanceof Table) { - dropEffect = new TableDropTargetEffect((Table) control); - } else if (control instanceof Tree) { - dropEffect = new TreeDropTargetEffect((Tree) control); + // Dispose listeners + controlListener = event -> { + if (!DropTarget.this.isDisposed()){ + DropTarget.this.dispose(); } + }; + control.addListener(SWT.Dispose, controlListener); + + this.addListener(SWT.Dispose, event -> onDispose()); + + Object effect = control.getData(DEFAULT_DROP_TARGET_EFFECT); + if (effect instanceof DropTargetEffect) { + dropEffect = (DropTargetEffect) effect; + } else if (control instanceof Table) { + dropEffect = new TableDropTargetEffect((Table) control); + } else if (control instanceof Tree) { + dropEffect = new TreeDropTargetEffect((Tree) control); + } - dragOverHeartbeat = () -> { - Control control1 = DropTarget.this.control; - if (control1 == null || control1.isDisposed() || dragOverStart == 0) return; - long time = System.currentTimeMillis(); - int delay = DRAGOVER_HYSTERESIS; - if (time < dragOverStart) { - delay = (int)(dragOverStart - time); - } else { - dragOverEvent.time += DRAGOVER_HYSTERESIS; - int allowedOperations = dragOverEvent.operations; - TransferData[] allowedTypes = dragOverEvent.dataTypes; - //pass a copy of data types in to listeners in case application modifies it - TransferData[] dataTypes = new TransferData[allowedTypes.length]; - System.arraycopy(allowedTypes, 0, dataTypes, 0, dataTypes.length); - - DNDEvent event = new DNDEvent(); - event.widget = dragOverEvent.widget; - event.x = dragOverEvent.x; - event.y = dragOverEvent.y; - event.time = dragOverEvent.time; - event.feedback = DND.FEEDBACK_SELECT; - event.dataTypes = dataTypes; - event.dataType = selectedDataType; - event.operations = dragOverEvent.operations; - event.detail = selectedOperation; - if (dropEffect != null) { - event.item = dropEffect.getItem(dragOverEvent.x, dragOverEvent.y); - } - selectedDataType = null; - selectedOperation = DND.DROP_NONE; - notifyListeners(DND.DragOver, event); - if (event.dataType != null) { - for (int i = 0; i < allowedTypes.length; i++) { - if (allowedTypes[i].type == event.dataType.type) { - selectedDataType = event.dataType; - break; - } + dragOverHeartbeat = () -> { + Control control1 = DropTarget.this.control; + if (control1 == null || control1.isDisposed() || dragOverStart == 0) return; + long time = System.currentTimeMillis(); + int delay = DRAGOVER_HYSTERESIS; + if (time < dragOverStart) { + delay = (int)(dragOverStart - time); + } else { + dragOverEvent.time += DRAGOVER_HYSTERESIS; + int allowedOperations = dragOverEvent.operations; + TransferData[] allowedTypes = dragOverEvent.dataTypes; + //pass a copy of data types in to listeners in case application modifies it + TransferData[] dataTypes = new TransferData[allowedTypes.length]; + System.arraycopy(allowedTypes, 0, dataTypes, 0, dataTypes.length); + + DNDEvent event = new DNDEvent(); + event.widget = dragOverEvent.widget; + event.x = dragOverEvent.x; + event.y = dragOverEvent.y; + event.time = dragOverEvent.time; + event.feedback = DND.FEEDBACK_SELECT; + event.dataTypes = dataTypes; + event.dataType = selectedDataType; + event.operations = dragOverEvent.operations; + event.detail = selectedOperation; + if (dropEffect != null) { + event.item = dropEffect.getItem(dragOverEvent.x, dragOverEvent.y); + } + selectedDataType = null; + selectedOperation = DND.DROP_NONE; + notifyListeners(DND.DragOver, event); + if (event.dataType != null) { + for (int i = 0; i < allowedTypes.length; i++) { + if (allowedTypes[i].type == event.dataType.type) { + selectedDataType = event.dataType; + break; } } - if (selectedDataType != null && (event.detail & allowedOperations) != 0) { - selectedOperation = event.detail; - } } - control1 = DropTarget.this.control; - if (control1 == null || control1.isDisposed()) return; - control1.getDisplay().timerExec(delay, dragOverHeartbeat); - }; - } + if (selectedDataType != null && (event.detail & allowedOperations) != 0) { + selectedOperation = event.detail; + } + } + control1 = DropTarget.this.control; + if (control1 == null || control1.isDisposed()) return; + control1.getDisplay().timerExec(delay, dragOverHeartbeat); + }; } static int checkStyle (int style) { @@ -285,6 +316,42 @@ static DropTarget FindDropTarget(long handle) { return (DropTarget)widget.getData(DND.DROP_TARGET_KEY); } +static DropTarget FindDropTargetGtk4(long controller) { + long widget = GTK.gtk_event_controller_get_widget(controller); + if (widget == 0) return null; + return FindDropTarget(widget); +} + +static long DropAccept(long controller, long drop) { + DropTarget target = FindDropTargetGtk4(controller); + if (target == null) return 0; + return target.dropAcceptGtk4(drop) ? 1 : 0; +} + +static long DropEnter(long controller, long drop, double x, double y) { + DropTarget target = FindDropTargetGtk4(controller); + if (target == null) return 0; + return target.dropMotionGtk4(drop, x, y, true); +} + +static long DropMotion(long controller, long drop, double x, double y) { + DropTarget target = FindDropTargetGtk4(controller); + if (target == null) return 0; + return target.dropMotionGtk4(drop, x, y, false); +} + +static void DropLeave(long controller, long drop) { + DropTarget target = FindDropTargetGtk4(controller); + if (target == null) return; + target.dropLeaveGtk4(drop); +} + +static long Drop(long controller, long drop, double x, double y) { + DropTarget target = FindDropTargetGtk4(controller); + if (target == null) return 0; + return target.dropGtk4(drop, x, y) ? 1 : 0; +} + /** * Adds the listener to the collection of listeners who will * be notified when a drag and drop operation is in progress, by sending @@ -593,12 +660,19 @@ public Transfer[] getTransfer() { void onDispose(){ if (control == null) return; - OS.g_signal_handler_disconnect(control.handle, drag_motion_handler); - OS.g_signal_handler_disconnect(control.handle, drag_leave_handler); - OS.g_signal_handler_disconnect(control.handle, drag_data_received_handler); - OS.g_signal_handler_disconnect(control.handle, drag_drop_handler); - if (transferAgents.length != 0) - GTK3.gtk_drag_dest_unset(control.handle); + if (GTK.GTK4) { + if (dropController != 0) { + GTK4.gtk_widget_remove_controller(control.handle, dropController); + dropController = 0; + } + } else { + OS.g_signal_handler_disconnect(control.handle, drag_motion_handler); + OS.g_signal_handler_disconnect(control.handle, drag_leave_handler); + OS.g_signal_handler_disconnect(control.handle, drag_data_received_handler); + OS.g_signal_handler_disconnect(control.handle, drag_drop_handler); + if (transferAgents.length != 0) + GTK3.gtk_drag_dest_unset(control.handle); + } transferAgents = null; if (controlListener != null) control.removeListener(SWT.Dispose, controlListener); @@ -609,22 +683,30 @@ void onDispose(){ int opToOsOp(int operation){ int osOperation = 0; + // GTK4 redefined the GdkDragAction values, so they differ from GTK3. + int copy = GTK.GTK4 ? GTK4.GDK_ACTION_COPY : GDK.GDK_ACTION_COPY; + int move = GTK.GTK4 ? GTK4.GDK_ACTION_MOVE : GDK.GDK_ACTION_MOVE; + int link = GTK.GTK4 ? GTK4.GDK_ACTION_LINK : GDK.GDK_ACTION_LINK; if ((operation & DND.DROP_COPY) == DND.DROP_COPY) - osOperation |= GDK.GDK_ACTION_COPY; + osOperation |= copy; if ((operation & DND.DROP_MOVE) == DND.DROP_MOVE) - osOperation |= GDK.GDK_ACTION_MOVE; + osOperation |= move; if ((operation & DND.DROP_LINK) == DND.DROP_LINK) - osOperation |= GDK.GDK_ACTION_LINK; + osOperation |= link; return osOperation; } int osOpToOp(int osOperation){ int operation = DND.DROP_NONE; - if ((osOperation & GDK.GDK_ACTION_COPY) == GDK.GDK_ACTION_COPY) + // GTK4 redefined the GdkDragAction values, so they differ from GTK3. + int copy = GTK.GTK4 ? GTK4.GDK_ACTION_COPY : GDK.GDK_ACTION_COPY; + int move = GTK.GTK4 ? GTK4.GDK_ACTION_MOVE : GDK.GDK_ACTION_MOVE; + int link = GTK.GTK4 ? GTK4.GDK_ACTION_LINK : GDK.GDK_ACTION_LINK; + if ((osOperation & copy) == copy) operation |= DND.DROP_COPY; - if ((osOperation & GDK.GDK_ACTION_MOVE) == GDK.GDK_ACTION_MOVE) + if ((osOperation & move) == move) operation |= DND.DROP_MOVE; - if ((osOperation & GDK.GDK_ACTION_LINK) == GDK.GDK_ACTION_LINK) + if ((osOperation & link) == link) operation |= DND.DROP_LINK; return operation; } @@ -671,19 +753,23 @@ public void removeDropListener(DropTargetListener listener) { * */ public void setTransfer(Transfer... transferAgents){ + if (transferAgents == null) DND.error(SWT.ERROR_NULL_ARGUMENT); if (GTK.GTK4) { this.transferAgents = transferAgents; long contentFormatsBuilder = GTK4.gdk_content_formats_builder_new(); for (Transfer agent : transferAgents) { + if (agent == null) continue; for (String typeName : agent.getTypeNames()) { GTK4.gdk_content_formats_builder_add_mime_type(contentFormatsBuilder, Converter.javaStringToCString(typeName)); } } - GTK4.gtk_drop_target_async_set_formats(dropController, contentFormatsBuilder); + // The builder is not a GdkContentFormats, it has to be converted first. + // gtk_drop_target_async_set_formats() then takes its own reference. + long formats = GTK4.gdk_content_formats_builder_free_to_formats(contentFormatsBuilder); + GTK4.gtk_drop_target_async_set_formats(dropController, formats); + GTK4.gdk_content_formats_unref(formats); } else { - if (transferAgents == null) DND.error(SWT.ERROR_NULL_ARGUMENT); - if (this.transferAgents.length != 0) { GTK3.gtk_drag_dest_unset(control.handle); } @@ -825,4 +911,228 @@ void updateDragOverHover(long delay, DNDEvent event) { dragOverEvent.operations = event.operations; dragOverEvent.time = event.time; } + +/* GTK4 drop target handlers */ + +boolean dropAcceptGtk4(long drop) { + if (control == null || control.isDisposed()) return false; + long formats = GTK4.gdk_drop_get_formats(drop); + if (formats == 0) return false; + for (Transfer transfer : transferAgents) { + if (transfer == null) continue; + long gtype = ContentProviders.getInstance().getGType(transfer); + if (gtype != 0 && GTK4.gdk_content_formats_contain_gtype(formats, gtype)) { + return true; + } + } + return false; +} + +long dropMotionGtk4(long drop, double x, double y, boolean isEnter) { + int oldKeyOperation = keyOperation; + + if (isEnter) { + selectedDataType = null; + selectedOperation = DND.DROP_NONE; + } + + DNDEvent event = new DNDEvent(); + if (!setEventDataGtk4(drop, x, y, event)) { + keyOperation = -1; + return 0; + } + + int allowedOperations = event.operations; + TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length]; + System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length); + + if (isEnter) { + event.type = DND.DragEnter; + } else if (keyOperation == oldKeyOperation) { + event.type = DND.DragOver; + event.dataType = selectedDataType; + event.detail = selectedOperation; + } else { + event.type = DND.DragOperationChanged; + event.dataType = selectedDataType; + } + updateDragOverHover(DRAGOVER_HYSTERESIS, event); + selectedDataType = null; + selectedOperation = DND.DROP_NONE; + notifyListeners(event.type, event); + if (event.detail == DND.DROP_DEFAULT) { + event.detail = (allowedOperations & DND.DROP_MOVE) != 0 ? DND.DROP_MOVE : DND.DROP_NONE; + } + if (event.dataType != null) { + for (TransferData allowedDataType : allowedDataTypes) { + if (allowedDataType.type == event.dataType.type) { + selectedDataType = event.dataType; + break; + } + } + } + if (selectedDataType != null && (allowedOperations & event.detail) != 0) { + selectedOperation = event.detail; + } + + if (isEnter) { + dragOverHeartbeat.run(); + } + return opToOsOp(selectedOperation); +} + +void dropLeaveGtk4(long drop) { + updateDragOverHover(0, null); + + if (keyOperation == -1) return; + keyOperation = -1; + + DNDEvent event = new DNDEvent(); + event.widget = this; + event.time = (int) System.currentTimeMillis(); + event.detail = DND.DROP_NONE; + notifyListeners(DND.DragLeave, event); +} + +boolean dropGtk4(long drop, double x, double y) { + // Stop the DragOver heartbeat; GTK4 does not reliably emit drag-leave after a drop. + updateDragOverHover(0, null); + + DNDEvent event = new DNDEvent(); + if (!setEventDataGtk4(drop, x, y, event)) { + keyOperation = -1; + return false; + } + keyOperation = -1; + + int allowedOperations = event.operations; + TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length]; + System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length); + + event.dataType = selectedDataType; + event.detail = selectedOperation; + selectedDataType = null; + selectedOperation = DND.DROP_NONE; + notifyListeners(DND.DropAccept, event); + if (event.dataType != null) { + for (TransferData allowedDataType : allowedDataTypes) { + if (allowedDataType.type == event.dataType.type) { + selectedDataType = allowedDataType; + break; + } + } + } + if (selectedDataType != null && ((event.detail & allowedOperations) == event.detail)) { + selectedOperation = event.detail; + } + if (selectedOperation == DND.DROP_NONE) { + GTK4.gdk_drop_finish(drop, 0); + return false; + } + + // Find the transfer agent that supports the selected data type and request its value + Transfer selectedTransfer = null; + for (Transfer transfer : transferAgents) { + if (transfer != null && transfer.isSupportedType(selectedDataType)) { + selectedTransfer = transfer; + break; + } + } + if (selectedTransfer == null) { + GTK4.gdk_drop_finish(drop, 0); + return false; + } + + long gtype = ContentProviders.getInstance().getGType(selectedTransfer); + final TransferData dropDataType = selectedDataType; + final int dropAllowedOperations = allowedOperations; + GAsyncReadyCallbackHelper.run(new Async() { + @Override + public void async(long callback) { + GTK4.gdk_drop_read_value_async(drop, gtype, OS.G_PRIORITY_DEFAULT, 0, callback, 0); + } + @Override + public void callback(long result) { + Object object = null; + long gvalue = GTK4.gdk_drop_read_value_finish(drop, result, null); + if (gvalue != 0) { + object = ContentProviders.getInstance().getObject(gvalue); + } + + int operation = selectedOperation; + if (object == null) { + operation = DND.DROP_NONE; + } + + DNDEvent dropEvent = new DNDEvent(); + dropEvent.widget = DropTarget.this; + dropEvent.time = (int) System.currentTimeMillis(); + dropEvent.detail = operation; + dropEvent.dataType = dropDataType; + dropEvent.data = object; + operation = DND.DROP_NONE; + notifyListeners(DND.Drop, dropEvent); + if ((dropAllowedOperations & dropEvent.detail) == dropEvent.detail) { + operation = dropEvent.detail; + } + + GTK4.gdk_drop_finish(drop, opToOsOp(operation)); + } + }); + return true; +} + +boolean setEventDataGtk4(long drop, double x, double y, DNDEvent event) { + if (drop == 0) return false; + long formats = GTK4.gdk_drop_get_formats(drop); + int actions = GTK4.gdk_drop_get_actions(drop); + if (formats == 0) return false; + + // get allowed operations + int style = getStyle(); + int operations = osOpToOp(actions) & style; + if (operations == DND.DROP_NONE) return false; + + // get current operation + int operation = getOperationFromKeyState(); + keyOperation = operation; + if (operation == DND.DROP_DEFAULT) { + if ((style & DND.DROP_DEFAULT) == 0) { + operation = (operations & DND.DROP_MOVE) != 0 ? DND.DROP_MOVE : DND.DROP_NONE; + } + } else { + if ((operation & operations) == 0) operation = DND.DROP_NONE; + } + + // Get allowed transfer types + TransferData[] dataTypes = new TransferData[0]; + for (Transfer transfer : transferAgents) { + if (transfer == null) continue; + long gtype = ContentProviders.getInstance().getGType(transfer); + if (gtype == 0 || !GTK4.gdk_content_formats_contain_gtype(formats, gtype)) continue; + TransferData[] supported = transfer.getSupportedTypes(); + TransferData[] newDataTypes = new TransferData[dataTypes.length + supported.length]; + System.arraycopy(dataTypes, 0, newDataTypes, 0, dataTypes.length); + System.arraycopy(supported, 0, newDataTypes, dataTypes.length, supported.length); + dataTypes = newDataTypes; + } + if (dataTypes.length == 0) return false; + + // x and y are relative to the widget; translate to display coordinates + Point coordinates = control.toDisplay((int) x, (int) y); + + event.widget = this; + event.x = coordinates.x; + event.y = coordinates.y; + event.time = (int) System.currentTimeMillis(); + event.feedback = DND.FEEDBACK_SELECT; + event.dataTypes = dataTypes; + event.dataType = dataTypes[0]; + event.operations = operations; + event.detail = operation; + if (dropEffect != null) { + event.item = dropEffect.getItem(coordinates.x, coordinates.y); + } + return true; +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c index 6a8d73e68c7..da6a5735fd7 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/library/os.c @@ -1605,7 +1605,7 @@ JNIEXPORT jlong JNICALL OS_NATIVE(CGPDFContextCreateWithURL) jlong rc = 0; OS_NATIVE_ENTER(env, that, CGPDFContextCreateWithURL_FUNC); if (arg1) if ((lparg1 = getCGRectFields(env, arg1, &_arg1)) == NULL) goto fail; - rc = (jlong)CGPDFContextCreateWithURL((CFURLRef)arg0, (const CGRect *)lparg1, (CFDictionaryRef)arg2); + rc = (jlong)CGPDFContextCreateWithURL((CFURLRef)arg0, (CGRect*)lparg1, (CFDictionaryRef)arg2); fail: if (arg1 && lparg1) setCGRectFields(env, arg1, lparg1); OS_NATIVE_EXIT(env, that, CGPDFContextCreateWithURL_FUNC); diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport index c0262de117e..5466b7020c6 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport @@ -1198,6 +1198,9 @@ + + + @@ -1421,7 +1424,6 @@ - @@ -1466,7 +1468,6 @@ - @@ -1477,7 +1478,6 @@ - @@ -1545,6 +1545,10 @@ + + + + @@ -1726,7 +1730,6 @@ - @@ -1783,7 +1786,6 @@ - @@ -1877,6 +1879,12 @@ + + + + + + @@ -2201,7 +2209,6 @@ - @@ -2516,7 +2523,6 @@ - @@ -32636,6 +32642,10 @@ + + + + @@ -32929,6 +32939,12 @@ + + + + + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport.extras b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport.extras index b43990cb1d0..8f5602cc4f8 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport.extras +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/AppKitFull.bridgesupport.extras @@ -2376,6 +2376,9 @@ + + + @@ -4154,6 +4157,14 @@ + + + + + + + + @@ -4192,6 +4203,12 @@ + + + + + + @@ -4356,6 +4373,9 @@ + + + @@ -4387,21 +4407,22 @@ - + - - + + + @@ -4412,6 +4433,10 @@ + + + + @@ -4421,13 +4446,13 @@ + - @@ -4443,7 +4468,6 @@ - @@ -4459,10 +4483,8 @@ - - @@ -4472,7 +4494,6 @@ - @@ -4491,9 +4512,6 @@ - - - @@ -4501,19 +4519,14 @@ - - - - - @@ -4529,13 +4542,10 @@ - + - - - - + @@ -4553,7 +4563,6 @@ - diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport index 147523b6afa..feaa3ca9fbb 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport @@ -5451,11 +5451,6 @@ - - - - - diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport.extras b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport.extras index 12e14c63f54..41fc30c2ce3 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport.extras +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreFoundationFull.bridgesupport.extras @@ -43,11 +43,6 @@ - - - - - diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreGraphicsFull.bridgesupport.extras b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreGraphicsFull.bridgesupport.extras index d694bac6a20..3702b5e3815 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreGraphicsFull.bridgesupport.extras +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/CoreGraphicsFull.bridgesupport.extras @@ -247,6 +247,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSProgressIndicator.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSProgressIndicator.java index 3397ada72d7..d430ad015e9 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSProgressIndicator.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSProgressIndicator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -7,9 +7,6 @@ * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.swt.internal.cocoa; @@ -63,6 +60,10 @@ public void setUsesThreadedAnimation(boolean usesThreadedAnimation) { OS.objc_msgSend(this.id, OS.sel_setUsesThreadedAnimation_, usesThreadedAnimation); } +public void sizeToFit() { + OS.objc_msgSend(this.id, OS.sel_sizeToFit); +} + public void startAnimation(id sender) { OS.objc_msgSend(this.id, OS.sel_startAnimation_, sender != null ? sender.id : 0); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java index 9bfa02eae83..16d5910854f 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSWorkspace.java @@ -7,9 +7,6 @@ * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.swt.internal.cocoa; @@ -27,6 +24,16 @@ public NSWorkspace(id id) { super(id); } +public NSURL URLForApplicationToOpenURL(NSURL url) { + long result = OS.objc_msgSend(this.id, OS.sel_URLForApplicationToOpenURL_, url != null ? url.id : 0); + return result != 0 ? new NSURL(result) : null; +} + +public NSURL URLForApplicationToOpenContentType(UTType contentType) { + long result = OS.objc_msgSend(this.id, OS.sel_URLForApplicationToOpenContentType_, contentType != null ? contentType.id : 0); + return result != 0 ? new NSURL(result) : null; +} + public NSString fullPathForApplication(NSString appName) { long result = OS.objc_msgSend(this.id, OS.sel_fullPathForApplication_, appName != null ? appName.id : 0); return result != 0 ? new NSString(result) : null; @@ -45,16 +52,6 @@ public boolean openURL(NSURL url) { return OS.objc_msgSend_bool(this.id, OS.sel_openURL_, url != null ? url.id : 0); } -public NSURL urlForApplicationToOpenURL(NSURL url) { - long result = OS.objc_msgSend(this.id, OS.sel_URLForApplicationToOpenURL_, url != null ? url.id : 0); - return result != 0 ? new NSURL(result) : null; -} - -public NSURL urlForApplicationToOpenContentType(long contentType) { - long result = OS.objc_msgSend(this.id, OS.sel_URLForApplicationToOpenContentType_, contentType); - return result != 0 ? new NSURL(result) : null; -} - public boolean openURLs(NSArray urls, NSString bundleIdentifier, long options, NSAppleEventDescriptor descriptor, long identifiers) { return OS.objc_msgSend_bool(this.id, OS.sel_openURLs_withAppBundleIdentifier_options_additionalEventParamDescriptor_launchIdentifiers_, urls != null ? urls.id : 0, bundleIdentifier != null ? bundleIdentifier.id : 0, options, descriptor != null ? descriptor.id : 0, identifiers); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java index 40c830f8464..ea852de5a13 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/OS.java @@ -767,6 +767,7 @@ public static boolean isSystemDarkAppearance() { public static final long class_NSWorkspace = objc_getClass("NSWorkspace"); public static final long class_SFCertificatePanel = objc_getClass("SFCertificatePanel"); public static final long class_SFCertificateTrustPanel = objc_getClass("SFCertificateTrustPanel"); +public static final long class_UTType = objc_getClass("UTType"); public static final long class_WebDataSource = objc_getClass("WebDataSource"); public static final long class_WebFrame = objc_getClass("WebFrame"); public static final long class_WebFrameView = objc_getClass("WebFrameView"); @@ -830,9 +831,9 @@ public static Selector getSelector (long value) { public static final long sel_PMPrintSettings = Selector.sel_PMPrintSettings.value; public static final long sel_TIFFRepresentation = Selector.sel_TIFFRepresentation.value; public static final long sel_URL = Selector.sel_URL.value; -public static final long sel_URLFromPasteboard_ = Selector.sel_URLFromPasteboard_.value; -public static final long sel_URLForApplicationToOpenURL_ = Selector.sel_URLForApplicationToOpenURL_.value; public static final long sel_URLForApplicationToOpenContentType_ = Selector.sel_URLForApplicationToOpenContentType_.value; +public static final long sel_URLForApplicationToOpenURL_ = Selector.sel_URLForApplicationToOpenURL_.value; +public static final long sel_URLFromPasteboard_ = Selector.sel_URLFromPasteboard_.value; public static final long sel_URLWithString_ = Selector.sel_URLWithString_.value; public static final long sel_UTF8String = Selector.sel_UTF8String.value; public static final long sel_abortEditing = Selector.sel_abortEditing.value; @@ -2143,6 +2144,9 @@ public static Selector getSelector (long value) { /** Constants */ public static final int NSAlertFirstButtonReturn = 1000; public static final int NSAlertSecondButtonReturn = 1001; +public static final int NSAlertStyleCritical = 2; +public static final int NSAlertStyleInformational = 1; +public static final int NSAlertStyleWarning = 0; public static final int NSAlertThirdButtonReturn = 1002; public static final int NSAlphaFirstBitmapFormat = 1; public static final int NSAlphaNonpremultipliedBitmapFormat = 2; @@ -2164,7 +2168,6 @@ public static Selector getSelector (long value) { public static final int NSBezelStylePushDisclosure = 14; public static final int NSBezelStyleSmallSquare = 6; public static final int NSBoldFontMask = 2; - public static final int NSBottomTabsBezelBorder = 2; public static final int NSBoxCustom = 4; public static final int NSBoxSeparator = 2; @@ -2174,21 +2177,19 @@ public static Selector getSelector (long value) { public static final int NSButtonTypeRadio = 4; public static final int NSButtonTypeSwitch = 3; public static final int NSCarriageReturnCharacter = 13; -public static final int NSDatePickerStyleClockAndCalendar = 1; - public static final int NSClosePathBezierPathElement = 3; -public static final int NSCommandKeyMask = 1048576; +public static final int NSColorSpaceModelRGB = 1; public static final int NSCompositingOperationClear = 0; public static final int NSCompositingOperationCopy = 1; public static final int NSCompositingOperationSourceAtop = 5; public static final int NSCompositingOperationSourceOver = 2; public static final int NSContentsCellMask = 1; -public static final int NSControlKeyMask = 262144; -public static final int NSCriticalAlertStyle = 2; public static final int NSCurveToBezierPathElement = 2; +public static final int NSDatePickerStyleClockAndCalendar = 1; +public static final int NSDatePickerStyleTextField = 2; +public static final int NSDatePickerStyleTextFieldAndStepper = 0; public static final int NSDeleteCharacter = 127; public static final long NSDeviceIndependentModifierFlagsMask = 4294901760L; - public static final int NSDragOperationCopy = 1; public static final int NSDragOperationDelete = 32; public static final long NSDragOperationEvery = -1L; @@ -2197,6 +2198,10 @@ public static Selector getSelector (long value) { public static final int NSDragOperationNone = 0; public static final int NSEnterCharacter = 3; public static final int NSEvenOddWindingRule = 1; +public static final int NSEventModifierFlagCommand = 1048576; +public static final int NSEventModifierFlagControl = 262144; +public static final int NSEventModifierFlagHelp = 4194304; +public static final int NSEventModifierFlagShift = 131072; public static final int NSEventPhaseBegan = 1; public static final int NSEventPhaseCancelled = 16; public static final int NSEventPhaseEnded = 8; @@ -2212,9 +2217,7 @@ public static Selector getSelector (long value) { public static final int NSFocusRingTypeNone = 1; public static final int NSFontPanelModeMaskAllEffects = 1048320; public static final int NSFontPanelModeMaskAllModes = -1; - public static final int NSHelpFunctionKey = 63302; -public static final int NSHelpKeyMask = 4194304; public static final int NSHourMinuteDatePickerElementFlag = 12; public static final int NSHourMinuteSecondDatePickerElementFlag = 14; public static final int NSImageAbove = 5; @@ -2229,7 +2232,7 @@ public static Selector getSelector (long value) { public static final int NSImageLeft = 2; public static final int NSImageOnly = 1; public static final int NSImageOverlaps = 6; -public static final int NSInformationalAlertStyle = 1; +public static final int NSImageScaleNone = 2; public static final int NSItalicFontMask = 1; public static final int NSKeyDown = 10; public static final int NSKeyUp = 11; @@ -2246,10 +2249,13 @@ public static Selector getSelector (long value) { public static final int NSLineBreakByWordWrapping = 0; public static final int NSLineToBezierPathElement = 1; public static final int NSControlSizeMini = 2; - +public static final int NSControlSizeRegular = 0; +public static final int NSControlSizeSmall = 1; +public static final int NSControlStateValueMixed = -1; +public static final int NSControlStateValueOff = 0; +public static final int NSControlStateValueOn = 1; public static final int NSMiterLineJoinStyle = 0; public static final int NSModalResponseCancel = 0; -public static final int NSControlStateValueMixed = -1; public static final int NSMouseEntered = 8; public static final int NSMouseExited = 9; public static final int NSMouseMoved = 5; @@ -2259,9 +2265,6 @@ public static Selector getSelector (long value) { public static final int NSNoImage = 0; public static final int NSNoTitle = 0; public static final int NSNonZeroWindingRule = 0; - -public static final int NSControlStateValueOff = 0; -public static final int NSControlStateValueOn = 1; public static final int NSOpenGLCPSurfaceOrder = 235; public static final int NSOpenGLPFAAccumSize = 14; public static final int NSOpenGLPFAAlphaSize = 11; @@ -2280,9 +2283,6 @@ public static Selector getSelector (long value) { public static final int NSPortraitOrientation = 0; public static final int NSPrintPanelShowsPageSetupAccessory = 256; public static final int NSPrintPanelShowsPrintSelection = 32; -public static final int NSProgressIndicatorPreferredThickness = 14; -public static final int NSColorSpaceModelRGB = 1; -public static final int NSControlSizeRegular = 0; public static final int NSRegularSquareBezelStyle = 2; public static final int NSResizableWindowMask = 8; public static final int NSRightMouseDown = 3; @@ -2290,19 +2290,14 @@ public static Selector getSelector (long value) { public static final int NSRightMouseUp = 4; public static final int NSRoundLineCapStyle = 1; public static final int NSRoundLineJoinStyle = 1; -public static final int NSImageScaleNone = 2; public static final int NSScrollElasticityNone = 1; public static final int NSScrollWheel = 22; -public static final int NSScrollerDecrementLine = 4; public static final int NSScrollerDecrementPage = 1; -public static final int NSScrollerIncrementLine = 5; public static final int NSScrollerIncrementPage = 3; public static final int NSScrollerKnob = 2; public static final int NSScrollerKnobSlot = 6; public static final int NSScrollerStyleLegacy = 0; public static final int NSScrollerStyleOverlay = 1; -public static final int NSShiftKeyMask = 131072; -public static final int NSControlSizeSmall = 1; public static final int NSSquareLineCapStyle = 2; public static final int NSStatusWindowLevel = 25; public static final int NSStringDrawingUsesLineFragmentOrigin = 1; @@ -2322,9 +2317,6 @@ public static Selector getSelector (long value) { public static final int NSTextAlignmentJustified = 3; public static final int NSTextAlignmentLeft = 0; public static final int NSTextAlignmentRight = IS_X86_64 ? 1 : 2; -public static final int NSDatePickerStyleTextFieldAndStepper = 0; -public static final int NSDatePickerStyleTextField = 2; - public static final int NSToolbarDisplayModeIconOnly = 2; public static final long NSTouchPhaseAny = -1L; public static final int NSTouchPhaseBegan = 1; @@ -2336,14 +2328,12 @@ public static Selector getSelector (long value) { public static final int NSUnderlineStyleNone = 0; public static final int NSUnderlineStyleSingle = 1; public static final int NSUnderlineStyleThick = 2; - public static final int NSViewHeightSizable = 16; public static final int NSViewMaxXMargin = 4; public static final int NSViewMaxYMargin = 32; public static final int NSViewMinXMargin = 1; public static final int NSViewMinYMargin = 8; public static final int NSViewWidthSizable = 2; -public static final int NSWarningAlertStyle = 0; public static final int NSWindowAbove = 1; public static final int NSWindowBelow = -1; public static final int NSWindowCollectionBehaviorFullScreenAuxiliary = 256; @@ -3183,12 +3173,6 @@ public static Selector getSelector (long value) { * @param image cast=(CGImageRef) */ public static final native void CGImageRelease(long image); -/** - * @param url cast=(CFURLRef) - * @param mediaBox cast=(const CGRect *) - * @param auxiliaryInfo cast=(CFDictionaryRef) - */ -public static final native long CGPDFContextCreateWithURL(long url, CGRect mediaBox, long auxiliaryInfo); /** * @param context cast=(CGContextRef) * @param pageInfo cast=(CFDictionaryRef) @@ -3197,11 +3181,17 @@ public static Selector getSelector (long value) { /** * @param context cast=(CGContextRef) */ -public static final native void CGPDFContextEndPage(long context); +public static final native void CGPDFContextClose(long context); +/** + * @param url cast=(CFURLRef) + * @param mediaBox cast=(CGRect*) + * @param auxiliaryInfo cast=(CFDictionaryRef) + */ +public static final native long CGPDFContextCreateWithURL(long url, CGRect mediaBox, long auxiliaryInfo); /** * @param context cast=(CGContextRef) */ -public static final native void CGPDFContextClose(long context); +public static final native void CGPDFContextEndPage(long context); /** * @param path cast=(CGMutablePathRef) * @param m cast=(CGAffineTransform*) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java index 589d736607e..09e0ae8c00c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/Selector.java @@ -96,9 +96,9 @@ public enum Selector { , sel_PMPrintSettings("PMPrintSettings") , sel_TIFFRepresentation("TIFFRepresentation") , sel_URL("URL") - , sel_URLFromPasteboard_("URLFromPasteboard:") - , sel_URLForApplicationToOpenURL_("URLForApplicationToOpenURL:") , sel_URLForApplicationToOpenContentType_("URLForApplicationToOpenContentType:") + , sel_URLForApplicationToOpenURL_("URLForApplicationToOpenURL:") + , sel_URLFromPasteboard_("URLFromPasteboard:") , sel_URLWithString_("URLWithString:") , sel_UTF8String("UTF8String") , sel_abortEditing("abortEditing") diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/UTType.java b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/UTType.java new file mode 100644 index 00000000000..b836c18703f --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/UTType.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.internal.cocoa; + +public class UTType extends NSObject { + +public UTType() { + super(); +} + +public UTType(long id) { + super(id); +} + +public UTType(id id) { + super(id); +} + +public static UTType typeWithFilenameExtension(NSString filenameExtension) { + long result = OS.objc_msgSend(OS.class_UTType, OS.sel_typeWithFilenameExtension_, filenameExtension != null ? filenameExtension.id : 0); + return result != 0 ? new UTType(result) : null; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/common/org/eclipse/swt/internal/Library.java b/bundles/org.eclipse.swt/Eclipse SWT PI/common/org/eclipse/swt/internal/Library.java index 62927e205b4..0b1069113f5 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/common/org/eclipse/swt/internal/Library.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/common/org/eclipse/swt/internal/Library.java @@ -35,7 +35,7 @@ public class Library { /** * SWT revision number (must be >= 0) */ - static int REVISION = 7; + static int REVISION = 10; /** * The JAVA and SWT versions diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c index d290dcf83fb..4f41025f6c9 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c @@ -359,6 +359,18 @@ JNIEXPORT jlong JNICALL GTK4_NATIVE(gdk_1content_1formats_1builder_1new) } #endif +#ifndef NO_gdk_1content_1formats_1contain_1gtype +JNIEXPORT jboolean JNICALL GTK4_NATIVE(gdk_1content_1formats_1contain_1gtype) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + jboolean rc = 0; + GTK4_NATIVE_ENTER(env, that, gdk_1content_1formats_1contain_1gtype_FUNC); + rc = (jboolean)gdk_content_formats_contain_gtype((GdkContentFormats *)arg0, (GType)arg1); + GTK4_NATIVE_EXIT(env, that, gdk_1content_1formats_1contain_1gtype_FUNC); + return rc; +} +#endif + #ifndef NO_gdk_1content_1formats_1get_1gtypes JNIEXPORT jlong JNICALL GTK4_NATIVE(gdk_1content_1formats_1get_1gtypes) (JNIEnv *env, jclass that, jlong arg0, jlongArray arg1) @@ -403,6 +415,16 @@ JNIEXPORT jlong JNICALL GTK4_NATIVE(gdk_1content_1formats_1to_1string) } #endif +#ifndef NO_gdk_1content_1formats_1unref +JNIEXPORT void JNICALL GTK4_NATIVE(gdk_1content_1formats_1unref) + (JNIEnv *env, jclass that, jlong arg0) +{ + GTK4_NATIVE_ENTER(env, that, gdk_1content_1formats_1unref_FUNC); + gdk_content_formats_unref((GdkContentFormats *)arg0); + GTK4_NATIVE_EXIT(env, that, gdk_1content_1formats_1unref_FUNC); +} +#endif + #ifndef NO_gdk_1content_1provider_1get_1value JNIEXPORT jboolean JNICALL GTK4_NATIVE(gdk_1content_1provider_1get_1value) (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlongArray arg2) @@ -613,6 +635,66 @@ JNIEXPORT void JNICALL GTK4_NATIVE(gdk_1content_1serializer_1set_1task_1data) } #endif +#ifndef NO_gdk_1drop_1finish +JNIEXPORT void JNICALL GTK4_NATIVE(gdk_1drop_1finish) + (JNIEnv *env, jclass that, jlong arg0, jint arg1) +{ + GTK4_NATIVE_ENTER(env, that, gdk_1drop_1finish_FUNC); + gdk_drop_finish((GdkDrop *)arg0, (GdkDragAction)arg1); + GTK4_NATIVE_EXIT(env, that, gdk_1drop_1finish_FUNC); +} +#endif + +#ifndef NO_gdk_1drop_1get_1actions +JNIEXPORT jint JNICALL GTK4_NATIVE(gdk_1drop_1get_1actions) + (JNIEnv *env, jclass that, jlong arg0) +{ + jint rc = 0; + GTK4_NATIVE_ENTER(env, that, gdk_1drop_1get_1actions_FUNC); + rc = (jint)gdk_drop_get_actions((GdkDrop *)arg0); + GTK4_NATIVE_EXIT(env, that, gdk_1drop_1get_1actions_FUNC); + return rc; +} +#endif + +#ifndef NO_gdk_1drop_1get_1formats +JNIEXPORT jlong JNICALL GTK4_NATIVE(gdk_1drop_1get_1formats) + (JNIEnv *env, jclass that, jlong arg0) +{ + jlong rc = 0; + GTK4_NATIVE_ENTER(env, that, gdk_1drop_1get_1formats_FUNC); + rc = (jlong)gdk_drop_get_formats((GdkDrop *)arg0); + GTK4_NATIVE_EXIT(env, that, gdk_1drop_1get_1formats_FUNC); + return rc; +} +#endif + +#ifndef NO_gdk_1drop_1read_1value_1async +JNIEXPORT void JNICALL GTK4_NATIVE(gdk_1drop_1read_1value_1async) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jint arg2, jlong arg3, jlong arg4, jlong arg5) +{ + GTK4_NATIVE_ENTER(env, that, gdk_1drop_1read_1value_1async_FUNC); + gdk_drop_read_value_async((GdkDrop *)arg0, (GType)arg1, arg2, (GCancellable *)arg3, (GAsyncReadyCallback)arg4, (gpointer)arg5); + GTK4_NATIVE_EXIT(env, that, gdk_1drop_1read_1value_1async_FUNC); +} +#endif + +#ifndef NO_gdk_1drop_1read_1value_1finish +JNIEXPORT jlong JNICALL GTK4_NATIVE(gdk_1drop_1read_1value_1finish) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jlongArray arg2) +{ + jlong *lparg2=NULL; + jlong rc = 0; + GTK4_NATIVE_ENTER(env, that, gdk_1drop_1read_1value_1finish_FUNC); + if (arg2) if ((lparg2 = (*env)->GetLongArrayElements(env, arg2, NULL)) == NULL) goto fail; + rc = (jlong)gdk_drop_read_value_finish((GdkDrop *)arg0, (GAsyncResult *)arg1, (GError **)lparg2); +fail: + if (arg2 && lparg2) (*env)->ReleaseLongArrayElements(env, arg2, lparg2, 0); + GTK4_NATIVE_EXIT(env, that, gdk_1drop_1read_1value_1finish_FUNC); + return rc; +} +#endif + #ifndef NO_gdk_1paintable_1snapshot JNIEXPORT void JNICALL GTK4_NATIVE(gdk_1paintable_1snapshot) (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jint arg2, jint arg3) @@ -2577,6 +2659,16 @@ JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1tree_1view_1column_1cell_1get_1size) } #endif +#ifndef NO_gtk_1tree_1view_1enable_1model_1drag_1dest +JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1tree_1view_1enable_1model_1drag_1dest) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jint arg2) +{ + GTK4_NATIVE_ENTER(env, that, gtk_1tree_1view_1enable_1model_1drag_1dest_FUNC); + gtk_tree_view_enable_model_drag_dest((GtkTreeView *)arg0, (GdkContentFormats *)arg1, (GdkDragAction)arg2); + GTK4_NATIVE_EXIT(env, that, gtk_1tree_1view_1enable_1model_1drag_1dest_FUNC); +} +#endif + #ifndef NO_gtk_1widget_1action_1set_1enabled JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1widget_1action_1set_1enabled) (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1, jboolean arg2) @@ -2841,6 +2933,16 @@ JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1widget_1pick) } #endif +#ifndef NO_gtk_1widget_1remove_1controller +JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1widget_1remove_1controller) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + GTK4_NATIVE_ENTER(env, that, gtk_1widget_1remove_1controller_FUNC); + gtk_widget_remove_controller((GtkWidget *)arg0, (GtkEventController *)arg1); + GTK4_NATIVE_EXIT(env, that, gtk_1widget_1remove_1controller_FUNC); +} +#endif + #ifndef NO_gtk_1widget_1set_1cursor JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1widget_1set_1cursor) (JNIEnv *env, jclass that, jlong arg0, jlong arg1) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h index 15d43724ca5..f240d03cae9 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h @@ -47,9 +47,11 @@ typedef enum { gdk_1content_1formats_1builder_1add_1mime_1type_FUNC, gdk_1content_1formats_1builder_1free_1to_1formats_FUNC, gdk_1content_1formats_1builder_1new_FUNC, + gdk_1content_1formats_1contain_1gtype_FUNC, gdk_1content_1formats_1get_1gtypes_FUNC, gdk_1content_1formats_1get_1mime_1types_FUNC, gdk_1content_1formats_1to_1string_FUNC, + gdk_1content_1formats_1unref_FUNC, gdk_1content_1provider_1get_1value_FUNC, gdk_1content_1provider_1new_1for_1value_FUNC, gdk_1content_1provider_1new_1typed_FUNC, @@ -67,6 +69,11 @@ typedef enum { gdk_1content_1serializer_1return_1error_FUNC, gdk_1content_1serializer_1return_1success_FUNC, gdk_1content_1serializer_1set_1task_1data_FUNC, + gdk_1drop_1finish_FUNC, + gdk_1drop_1get_1actions_FUNC, + gdk_1drop_1get_1formats_FUNC, + gdk_1drop_1read_1value_1async_FUNC, + gdk_1drop_1read_1value_1finish_FUNC, gdk_1paintable_1snapshot_FUNC, gdk_1toplevel_1focus_FUNC, gdk_1toplevel_1get_1state_FUNC, @@ -211,6 +218,7 @@ typedef enum { gtk_1text_1set_1tabs_FUNC, gtk_1text_1set_1visibility_FUNC, gtk_1tree_1view_1column_1cell_1get_1size_FUNC, + gtk_1tree_1view_1enable_1model_1drag_1dest_FUNC, gtk_1widget_1action_1set_1enabled_FUNC, gtk_1widget_1activate_1action_FUNC, gtk_1widget_1add_1controller_FUNC, @@ -231,6 +239,7 @@ typedef enum { gtk_1widget_1measure_FUNC, gtk_1widget_1paintable_1new_FUNC, gtk_1widget_1pick_FUNC, + gtk_1widget_1remove_1controller_FUNC, gtk_1widget_1set_1cursor_FUNC, gtk_1widget_1set_1focusable_FUNC, gtk_1widget_1set_1overflow_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java index 59edf503279..a070c4d1329 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java @@ -383,14 +383,17 @@ public static String getEnvironmentalVariable (String envVarName) { public static final byte[] delete_text = ascii("delete-text"); public static final byte[] direction_changed = ascii("direction-changed"); public static final byte[] dpi_changed = ascii("notify::scale-factor"); + public static final byte[] accept = ascii("accept"); public static final byte[] drag_begin = ascii("drag-begin"); public static final byte[] drag_data_delete = ascii("drag-data-delete"); public static final byte[] drag_data_get = ascii("drag-data-get"); public static final byte[] drag_data_received = ascii("drag-data-received"); public static final byte[] drag_drop = ascii("drag-drop"); public static final byte[] drag_end = ascii("drag-end"); + public static final byte[] drag_enter = ascii("drag-enter"); public static final byte[] drag_leave = ascii("drag-leave"); public static final byte[] drag_motion = ascii("drag-motion"); + public static final byte[] drop = ascii("drop"); public static final byte[] prepare = ascii("prepare"); public static final byte[] draw = ascii("draw"); public static final byte[] end = ascii("end"); @@ -408,6 +411,7 @@ public static String getEnvironmentalVariable (String envVarName) { public static final byte[] hide = ascii("hide"); public static final byte[] icon_release = ascii("icon-release"); public static final byte[] insert_text = ascii("insert-text"); + public static final byte[] items_changed = ascii("items-changed"); public static final byte[] key_press_event = ascii("key-press-event"); public static final byte[] key_release_event = ascii("key-release-event"); public static final byte[] key_pressed = ascii("key-pressed"); diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java index df933e27183..3c33dfcfb79 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java @@ -228,6 +228,38 @@ public class GTK4 { public static final native void gdk_content_formats_builder_add_mime_type(long builder, byte[] mime_type); /** @param builder cast=(GdkContentFormatsBuilder *) */ public static final native long gdk_content_formats_builder_free_to_formats(long builder); + /** + * @param formats cast=(GdkContentFormats *) + * @param type cast=(GType) + */ + public static final native boolean gdk_content_formats_contain_gtype(long formats, long type); + /** @param formats cast=(GdkContentFormats *) */ + public static final native void gdk_content_formats_unref(long formats); + + /* GdkDrop */ + /** + * @param drop cast=(GdkDrop *) + * @param action cast=(GdkDragAction) + */ + public static final native void gdk_drop_finish(long drop, int action); + /** @param drop cast=(GdkDrop *) */ + public static final native int gdk_drop_get_actions(long drop); + /** @param drop cast=(GdkDrop *) */ + public static final native long gdk_drop_get_formats(long drop); + /** + * @param drop cast=(GdkDrop *) + * @param type cast=(GType) + * @param cancellable cast=(GCancellable *) + * @param callback cast=(GAsyncReadyCallback) + * @param user_data cast=(gpointer) + */ + public static final native void gdk_drop_read_value_async(long drop, long type, int io_priority, long cancellable, long callback, long user_data); + /** + * @param drop cast=(GdkDrop *) + * @param result cast=(GAsyncResult *) + * @param error cast=(GError **) + */ + public static final native long gdk_drop_read_value_finish(long drop, long result, long[] error); /* GtkFileChooser */ /** @@ -705,6 +737,11 @@ public class GTK4 { * @param controller cast=(GtkEventController *) */ public static final native void gtk_widget_add_controller(long widget, long controller); + /** + * @param widget cast=(GtkWidget *) + * @param controller cast=(GtkEventController *) + */ + public static final native void gtk_widget_remove_controller(long widget, long controller); /** @param widget cast=(GtkWidget *) */ public static final native long gtk_widget_get_first_child(long widget); /** @param widget cast=(GtkWidget *) */ @@ -863,6 +900,14 @@ public class GTK4 { /** @param menu_button cast=(GtkMenuButton *) */ public static final native void gtk_menu_button_set_use_underline(long menu_button, boolean use_underline); + /* GtkTreeView */ + /** + * @param tree_view cast=(GtkTreeView *) + * @param formats cast=(GdkContentFormats *) + * @param actions cast=(GdkDragAction) + */ + public static final native void gtk_tree_view_enable_model_drag_dest(long tree_view, long formats, int actions); + /* GtkTreeViewColumn */ /** * @param tree_column cast=(GtkTreeViewColumn *) diff --git a/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java b/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java index 465abcc1180..786067fecee 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Program/cocoa/org/eclipse/swt/program/Program.java @@ -153,20 +153,17 @@ private static NSURL findAppURLForExtension(NSString ext) { // On macOS 12.0+, use the content type-based API which works reliably // for all file types including third-party ones. if (OS.VERSION >= OS.VERSION(12, 0, 0)) { - long UTTypeClass = OS.objc_getClass("UTType"); - if (UTTypeClass != 0) { - long utType = OS.objc_msgSend(UTTypeClass, OS.sel_typeWithFilenameExtension_, ext.id); - if (utType != 0) { - NSURL appURL = workspace.urlForApplicationToOpenContentType(utType); - if (appURL != null) { - return appURL; - } + UTType utType = UTType.typeWithFilenameExtension(ext); + if (utType != null) { + NSURL appURL = workspace.URLForApplicationToOpenContentType(utType); + if (appURL != null) { + return appURL; } } } // Fallback: URL-based lookup (available since macOS 10.6, deprecated in macOS 12.0) NSURL fileURL = NSURL.fileURLWithPath(NSString.stringWith("/tmp/dummy." + ext.getString())); - return workspace.urlForApplicationToOpenURL(fileURL); + return workspace.URLForApplicationToOpenURL(fileURL); } static Program getProgram(NSBundle bundle) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java index 6b4cb998c86..47b20458042 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java @@ -14,16 +14,19 @@ package org.eclipse.swt.graphics; import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import java.util.*; import java.util.concurrent.*; +import java.util.stream.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.widgets.*; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.params.*; +import org.junit.jupiter.params.provider.*; @ExtendWith(PlatformSpecificExecutionExtension.class) @ExtendWith(WithMonitorSpecificScalingExtension.class) @@ -142,4 +145,69 @@ private static int renderTextAndCountNonWhitePixels(Image target, Font font, Str } return count; } + + /** + * Regression test for the size calculation in scaling/cropping GC.drawImage() + * operations with asymmetric source dimensions (smaller height than width) at + * fractional zoom levels. + *

+ * At fractional zoom levels the effective X and Y scale factors diverge because + * each axis is rounded independently (e.g. at 125%: + * scaleFactorX = 625/500 = 1.25 but + * scaleFactorY = 24/19 ≈ 1.263). + */ + @ParameterizedTest + @MethodSource("zoomAndHeightArguments") + public void drawImage_asymmetricDimensionsAtFractionalZoom(int zoom, int height) { + Display display = Display.getDefault(); + + int logicalWidth = 500; + int logicalHeight = height; + + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData srcData = new ImageData(logicalWidth, logicalHeight, 32, palette); + for (int y = 0; y < logicalHeight; y++) { + for (int x = 0; x < logicalWidth; x++) { + // left half red, right half blue – makes wrong-rectangle errors visible + srcData.setPixel(x, y, x < logicalWidth / 2 ? 0xFF0000 : 0x0000FF); + } + } + Image srcImage = new Image(display, srcData); + + int previousZoom = DPIUtil.getDeviceZoom(); + try { + DPIUtil.setDeviceZoom(zoom); + + Image referenceImage = new Image(display, logicalWidth + 5, logicalHeight + 5); + GC referenceGC = new GC(referenceImage); + referenceGC.drawImage(srcImage, 0, 0); + referenceGC.dispose(); + + Image testImageScaled = new Image(display, logicalWidth + 5, logicalHeight + 5); + GC testGC = new GC(testImageScaled); + testGC.drawImage(srcImage, 0, 0, logicalWidth, logicalHeight); + testGC.dispose(); + assertArrayEquals(referenceImage.getImageData(zoom).data, testImageScaled.getImageData(zoom).data); + testImageScaled.dispose(); + + Image testImageScaledCropped = new Image(display, logicalWidth + 5, logicalHeight + 5); + testGC = new GC(testImageScaledCropped); + testGC.drawImage(srcImage, 0, 0, logicalWidth, logicalHeight, 0, 0, logicalWidth, logicalHeight); + testGC.dispose(); + assertArrayEquals(referenceImage.getImageData(zoom).data, testImageScaledCropped.getImageData(zoom).data); + testImageScaledCropped.dispose(); + + referenceImage.dispose(); + } finally { + DPIUtil.setDeviceZoom(previousZoom); + srcImage.dispose(); + } + } + + private static Stream zoomAndHeightArguments() { + int[] zooms = { 25, 50, 75, 100, 125, 150, 175, 200 }; + int[] heights = IntStream.rangeClosed(4, 20).toArray(); + return Arrays.stream(zooms).boxed() + .flatMap(zoom -> Arrays.stream(heights).mapToObj(height -> Arguments.of(zoom, height))); + } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java index 6b60c2a20df..33f7e3fac26 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/ImagesWin32Tests.java @@ -13,7 +13,8 @@ *******************************************************************************/ package org.eclipse.swt.graphics; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; @@ -30,9 +31,219 @@ public void testImageIconTypeShouldNotChangeAfterCallingGetHandleForDifferentZoo Image icon = Display.getDefault().getSystemImage(SWT.ICON_ERROR); try { Image.win32_getHandle(icon, 200); - assertEquals("Image type should stay to SWT.ICON", SWT.ICON, icon.type); + assertEquals(SWT.ICON, icon.type, "Image type should stay to SWT.ICON"); } finally { icon.dispose(); } } + + /** + * Tests that a GC.drawImage() handle is reused across consecutive calls at + * different pixel sizes when the image only provides 100% zoom data. Because + * every zoom request falls back to the same 100% data, the effective (nearest + * available) zoom is always 100%, and a freshly allocated handle should not be + * required for each different draw size. + *

+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testDrawingHandleIsReusedForSingleZoomImageAtDifferentSizes() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData = new ImageData(10, 10, 24, palette); + // Provider only has 100% data; returns null for every other zoom + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData : null); + long[] firstHandle = {0}; + long[] secondHandle = {0}; + try { + // 20x20 pixels → 200% zoom equivalent for a 10x10 base image + image.executeOnImageHandleAtBestFittingSize(h -> firstHandle[0] = h.handle(), 20, 20); + // 30x30 pixels → 300% zoom equivalent; provider still falls back to 100%, + // so the nearest available zoom is still 100% and the handle must be reused + image.executeOnImageHandleAtBestFittingSize(h -> secondHandle[0] = h.handle(), 30, 30); + assertNotEquals(0L, firstHandle[0], "First handle should be non-zero"); + assertEquals(firstHandle[0], secondHandle[0], + "Consecutive GC.drawImage() calls at different sizes should reuse the same " + + "handle when the nearest available zoom is the same (100% in this case)"); + } finally { + image.dispose(); + } + } + + /** + * Tests that a GC.drawImage() handle is reused across consecutive calls at + * different pixel sizes when the image provides data at 100% and 200% zoom. + * Sizes that both map to the 200% nearest available zoom (e.g. 200% and 250%) + * should share the same underlying handle without re-allocating it. + *

+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testDrawingHandleIsReusedForTwoZoomImageAtSizesWithSameNearestZoom() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData100 = new ImageData(10, 10, 24, palette); + ImageData imageData200 = new ImageData(20, 20, 24, palette); + // Provider has explicit data at 100% and 200%; returns null for anything else + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData100 : zoom == 200 ? imageData200 : null); + long[] firstHandle = {0}; + long[] secondHandle = {0}; + try { + // 20x20 pixels → exactly 200% zoom for the 10x10 base image; uses 200% data + image.executeOnImageHandleAtBestFittingSize(h -> firstHandle[0] = h.handle(), 20, 20); + // 25x25 pixels → 250% zoom equivalent; nearest available is 200%, so the + // previously cached 200% handle should be reused + image.executeOnImageHandleAtBestFittingSize(h -> secondHandle[0] = h.handle(), 25, 25); + assertNotEquals(0L, firstHandle[0], "First handle should be non-zero"); + assertEquals(firstHandle[0], secondHandle[0], + "Consecutive GC.drawImage() calls at different sizes should reuse the same " + + "handle when the nearest available zoom is the same (200% in this case)"); + } finally { + image.dispose(); + } + } + + /** + * Tests that GC.drawImage() handles differ when consecutive calls at different + * pixel sizes land in different nearest-available-zoom regions for an image + * that provides distinct data at 100% and 200%. A size mapping to 100% and a + * size mapping to 200% must not share the same native handle, as they would + * represent different pixel content. + *

+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testHandlesAreDifferentForTwoZoomImageAtDifferentNearestZooms() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData100 = new ImageData(10, 10, 24, palette); + ImageData imageData200 = new ImageData(20, 20, 24, palette); + // Provider has explicit data at 100% and 200%; returns null for anything else + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData100 : zoom == 200 ? imageData200 : null); + long[] handle100Zone = {0}; + long[] handle200Zone = {0}; + try { + // 10x10 pixels → 100% zoom for the 10x10 base image; nearest available is 100% + image.executeOnImageHandleAtBestFittingSize(h -> handle100Zone[0] = h.handle(), 10, 10); + // 20x20 pixels → 200% zoom; nearest available is 200% → must differ from the + // 100% handle since the underlying pixel data is different + image.executeOnImageHandleAtBestFittingSize(h -> handle200Zone[0] = h.handle(), 20, 20); + assertNotEquals(0L, handle100Zone[0], "First handle should be non-zero"); + assertNotEquals(handle100Zone[0], handle200Zone[0], + "GC.drawImage() calls where the nearest available zoom differs must not " + + "reuse the same handle (100% data vs 200% data)"); + } finally { + image.dispose(); + } + } + + /** + * Tests that a GC.drawImage() handle at the exact imageZoom is returned in + * preference to a handle at nearestAvailableZoom when both are present in the + * image's handle manager. + *

+ * Explicitly creating a persistent handle at 200% via + * {@link Image#win32_getHandle(Image, int)} places it in the handle manager. + * When GC.drawImage() then targets a pixel size that maps to imageZoom=200 + * (while nearestAvailableZoom stays 100% because the provider only has 100% + * data), the 200% handle must be found first via the imageZoom lookup and + * returned instead of the 100% one. + *

+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testDrawImagePrefersExistingHandleAtExactImageZoom() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData = new ImageData(10, 10, 24, palette); + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData : null); + try { + long handle100 = Image.win32_getHandle(image, 100); + long handle200 = Image.win32_getHandle(image, 200); + assertNotEquals(0L, handle100, "100% handle should be non-zero"); + assertNotEquals(0L, handle200, "200% handle should be non-zero"); + assertNotEquals(handle100, handle200, "Handles for different zooms should be distinct native objects"); + long[] drawHandle = {0}; + // 20x20 pixels → imageZoom=200 for a 10x10 base; nearestAvailableZoom=100 + // The 200% handle already in the handle manager must be found and preferred + image.executeOnImageHandleAtBestFittingSize(h -> drawHandle[0] = h.handle(), 20, 20); + assertEquals(handle200, drawHandle[0], + "GC.drawImage() should prefer the existing handle at imageZoom=200 " + + "over the nearestAvailableZoom=100 handle"); + } finally { + image.dispose(); + } + } + + /** + * Tests that when the pixel size requested by GC.drawImage() maps to a zoom + * equal to a current monitor's zoom, a persistent handle is created for that + * zoom level and is subsequently reusable via + * {@link Image#win32_getHandle(Image, int)}. + *

+ * A shell is created so that {@code Display.getShells()} is non-empty and + * the monitor zoom is visible to the handle-selection logic. Drawing at a + * pixel size whose imageZoom equals the shell's zoom triggers the + * monitor-zoom persistence path: the handle is stored in the image's handle + * manager and can be retrieved without allocating a second native object. + * On 100%-DPI machines the new {@code imageZoom == monitorZoom} branch + * overlaps with the existing {@code nearestAvailableZoom == 100} fallback; + * on HiDPI machines the new branch is exercised in isolation. + *

+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testDrawImageCreatesAndReusesPersistentHandleForMonitorZoomImageZoom() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData = new ImageData(10, 10, 24, palette); + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData : null); + Shell shell = new Shell(Display.getDefault()); + try { + int shellZoom = shell.getZoom(); + int pixelSize = 10 * shellZoom / 100; + long[] drawHandle = {0}; + // Drawing at shellZoom's pixel size → imageZoom == shellZoom == monitorZoom + // A persistent handle must be created and stored in the handle manager + image.executeOnImageHandleAtBestFittingSize(h -> drawHandle[0] = h.handle(), pixelSize, pixelSize); + // If persisted, win32_getHandle returns the same native handle without a new allocation + long persistedHandle = Image.win32_getHandle(image, shellZoom); + assertNotEquals(0L, drawHandle[0], "Draw handle should be non-zero"); + assertEquals(persistedHandle, drawHandle[0], + "GC.drawImage() at the monitor zoom should create a persistent handle " + + "so that win32_getHandle returns the same native object"); + } finally { + image.dispose(); + shell.dispose(); + } + } + + /** + * Tests that a persistent native handle already created via + * {@link Image#win32_getHandle(Image, int)} is found and reused by + * GC.drawImage() when the nearest available zoom for the requested draw size + * maps to the same zoom. This verifies that the + * {@code imageHandleManager.get(nearestAvailableZoom)} lookup is effective and + * avoids redundant handle allocation. + *

+ * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3419 + */ + @Test + public void testDrawImageReusesExistingPersistentHandleForNearestAvailableZoom() { + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData imageData = new ImageData(10, 10, 24, palette); + // Provider only has 100% data; every zoom falls back to 100% + Image image = new Image(Display.getDefault(), (ImageDataProvider) zoom -> zoom == 100 ? imageData : null); + try { + // Force creation of a persistent 100% handle (as would happen via GC.drawImage + // on a 100% zoom canvas or via Image.getImageData()) + long persistentHandle = Image.win32_getHandle(image, 100); + assertNotEquals(0L, persistentHandle, "Persistent handle should be non-zero"); + long[] drawHandle = {0}; + // 20x20 pixels → 200% zoom equivalent for the 10x10 base image; nearest + // available is still 100%, so the already-cached persistent handle must be + // returned without allocating a new one + image.executeOnImageHandleAtBestFittingSize(h -> drawHandle[0] = h.handle(), 20, 20); + assertEquals(persistentHandle, drawHandle[0], + "GC.drawImage() should reuse the existing persistent handle when the " + + "nearest available zoom matches the cached handle's zoom (100% here)"); + } finally { + image.dispose(); + } + } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ToolBarWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ToolBarWin32Tests.java new file mode 100644 index 00000000000..c730c22d488 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ToolBarWin32Tests.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2026 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.*; +import java.util.function.*; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; +import org.eclipse.swt.layout.*; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.*; + +/** + * Windows-specific tests for {@link ToolBar}'s image handling across monitor + * zoom changes. + */ +@ExtendWith(PlatformSpecificExecutionExtension.class) +class ToolBarWin32Tests { + + private static final int TIMEOUT_MILLIS = 5000; + + private record ToolItemWithExpectedColor(ToolItem toolItem, RGB expectedColor) { + } + + /** + * Regression test for a tool bar item rendering the wrong or a blank icon after + * a monitor zoom (DPI) change, as reported in issue + * #3466. + *

+ * The bug is triggered by clearing one item's image (which leaves the image + * list in a state that a later zoom change mishandles); neither disposing an + * image nor multiple monitors are required. The test observes the actual + * rendered result via public API only: each item gets a distinctly colored + * icon, and after the zoom change the dominant color under each item must still + * match that item's own icon. + */ + @Test + void testIconsRenderedCorrectlyAfterZoomChangeWithImageListHole() { + Display display = new Display(); + RGB[] colors = { new RGB(220, 40, 40), new RGB(40, 180, 40), new RGB(40, 40, 220), new RGB(230, 200, 30) }; + Image[] icons = new Image[colors.length]; + for (int i = 0; i < colors.length; i++) { + icons[i] = solidIcon(display, 16, colors[i]); + } + try { + Shell shell = new Shell(display); + shell.setLayout(new FillLayout()); + ToolBar bar = new ToolBar(shell, SWT.FLAT); + ToolItem[] items = new ToolItem[colors.length]; + for (int i = 0; i < colors.length; i++) { + items[i] = new ToolItem(bar, SWT.PUSH); + items[i].setImage(icons[i]); + } + shell.setSize(500, 90); + shell.open(); + + // Punch a hole below the other items: clear the first item's image. + items[0].setImage(null); + + int zoom = bar.getAutoscalingZoom(); + DPITestUtil.changeDPIZoom(bar.getShell(), zoom * 2); + + // Only the first item is expected to rendered blank + Set itemsToCheck = new HashSet<>(); + for (int i = 1; i < items.length; i++) { + itemsToCheck.add(new ToolItemWithExpectedColor(items[i], colors[i])); + } + // Dispatch events until every item renders its own icon color, or fail on + // timeout. With the bug an item permanently renders another item's icon or + // a blank one, so the condition is never met and the timeout triggers. + assertTrue(waitUntilIconsRenderOwnColor(display, () -> iconsRenderOwnColor(bar, itemsToCheck, colors), + TIMEOUT_MILLIS), "every tool item must render its own icon color after a zoom change"); + } finally { + for (Image icon : icons) + icon.dispose(); + display.dispose(); + } + } + + private static boolean waitUntilIconsRenderOwnColor(Display display, BooleanSupplier condition, + long timeoutMillis) { + long deadline = System.currentTimeMillis() + timeoutMillis; + while (System.currentTimeMillis() < deadline) { + if (condition.getAsBoolean()) { + return true; + } + if (!display.readAndDispatch()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + return condition.getAsBoolean(); + } + + private static boolean iconsRenderOwnColor(ToolBar bar, Set items, RGB[] candidateColors) { + Image snapshot = renderToolBar(bar); + try { + for (ToolItemWithExpectedColor item : items) { + if (!item.expectedColor().equals(dominantIconColor(snapshot, item.toolItem().getBounds(), candidateColors))) { + return false; + } + } + return true; + } finally { + snapshot.dispose(); + } + } + + private static Image solidIcon(Display display, int size, RGB rgb) { + Image image = new Image(display, size, size); + GC gc = new GC(image); + Color color = new Color(display, rgb); + gc.setBackground(color); + gc.fillRectangle(0, 0, size, size); + gc.dispose(); + return image; + } + + private static Image renderToolBar(ToolBar bar) { + Point size = bar.getSize(); + Image snapshot = new Image(bar.getDisplay(), Math.max(1, size.x), Math.max(1, size.y)); + GC gc = new GC(snapshot); + bar.print(gc); + gc.dispose(); + return snapshot; + } + + /** + * Returns which of the given candidate icon colors dominates the area of an + * item. Each sufficiently saturated pixel is classified to its nearest + * candidate color; the candidate matching the most pixels wins. Classifying to + * a fixed palette (rather than comparing exact RGB values) makes the result + * deterministic despite anti-aliasing, DPI interpolation and theming. + */ + private static RGB dominantIconColor(Image snapshot, Rectangle bounds, RGB[] candidates) { + ImageData data = snapshot.getImageData(); + PaletteData palette = data.palette; + int[] votes = new int[candidates.length]; + int x0 = Math.max(0, bounds.x), y0 = Math.max(0, bounds.y); + int x1 = Math.min(data.width, bounds.x + bounds.width); + int y1 = Math.min(data.height, bounds.y + bounds.height); + for (int y = y0; y < y1; y++) { + for (int x = x0; x < x1; x++) { + RGB rgb = palette.getRGB(data.getPixel(x, y)); + int max = Math.max(rgb.red, Math.max(rgb.green, rgb.blue)); + int min = Math.min(rgb.red, Math.min(rgb.green, rgb.blue)); + if (max - min < 60) { + continue; // ignore low-saturation background/borders + } + int best = -1, bestDist = Integer.MAX_VALUE; + for (int c = 0; c < candidates.length; c++) { + int dr = rgb.red - candidates[c].red; + int dg = rgb.green - candidates[c].green; + int db = rgb.blue - candidates[c].blue; + int dist = dr * dr + dg * dg + db * db; + if (dist < bestDist) { + bestDist = dist; + best = c; + } + } + if (best >= 0) + votes[best]++; + } + } + int winner = -1, most = 0; + for (int c = 0; c < candidates.length; c++) { + if (votes[c] > most) { + most = votes[c]; + winner = c; + } + } + return winner < 0 ? null : candidates[winner]; + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java index 0597becb231..76e216736d1 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java @@ -1445,9 +1445,9 @@ boolean sendKeyEvent (NSEvent nsEvent, int type) { int stateMask = 0; long modifierFlags = nsEvent.modifierFlags(); if ((modifierFlags & OS.NSAlternateKeyMask) != 0) stateMask |= SWT.ALT; - if ((modifierFlags & OS.NSShiftKeyMask) != 0) stateMask |= SWT.SHIFT; - if ((modifierFlags & OS.NSControlKeyMask) != 0) stateMask |= SWT.CONTROL; - if ((modifierFlags & OS.NSCommandKeyMask) != 0) stateMask |= SWT.COMMAND; + if ((modifierFlags & OS.NSEventModifierFlagShift) != 0) stateMask |= SWT.SHIFT; + if ((modifierFlags & OS.NSEventModifierFlagControl) != 0) stateMask |= SWT.CONTROL; + if ((modifierFlags & OS.NSEventModifierFlagCommand) != 0) stateMask |= SWT.COMMAND; if (type != SWT.KeyDown) return result; short keyCode = nsEvent.keyCode (); if (stateMask == SWT.COMMAND) { @@ -1484,7 +1484,7 @@ boolean sendTrackingKeyEvent (NSEvent nsEvent, int type) { * queue. */ long modifiers = nsEvent.modifierFlags(); - if ((modifiers & OS.NSShiftKeyMask) == 0) { + if ((modifiers & OS.NSEventModifierFlagShift) == 0) { short keyCode = nsEvent.keyCode (); switch (keyCode) { case 125: /* Arrow Down */ diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Control.java index 8c2238e83cf..f28f96b24da 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Control.java @@ -1044,7 +1044,7 @@ void doCommandBySelector (long id, long sel, long selector) { * is down, because we likely triggered the current key sequence via flagsChanged. */ long modifiers = nsEvent.modifierFlags(); - if (s.keyInputHappened == false || (modifiers & OS.NSCommandKeyMask) != 0) { + if (s.keyInputHappened == false || (modifiers & OS.NSEventModifierFlagCommand) != 0) { s.keyInputHappened = true; boolean [] consume = new boolean [1]; if (translateTraversal (nsEvent.keyCode (), nsEvent, consume)) return; @@ -1363,9 +1363,9 @@ void flagsChanged (long id, long sel, long theEvent) { int keyCode = Display.translateKey (nsEvent.keyCode ()); switch (keyCode) { case SWT.ALT: mask = OS.NSAlternateKeyMask; break; - case SWT.CONTROL: mask = OS.NSControlKeyMask; break; - case SWT.COMMAND: mask = OS.NSCommandKeyMask; break; - case SWT.SHIFT: mask = OS.NSShiftKeyMask; break; + case SWT.CONTROL: mask = OS.NSEventModifierFlagControl; break; + case SWT.COMMAND: mask = OS.NSEventModifierFlagCommand; break; + case SWT.SHIFT: mask = OS.NSEventModifierFlagShift; break; case SWT.CAPS_LOCK: Event event = new Event(); event.keyCode = keyCode; @@ -2558,7 +2558,7 @@ boolean mouseEvent (long id, long sel, long theEvent, int type) { switch (nsType) { case OS.NSLeftMouseDown: - if (nsEvent.clickCount() == 1 && (nsEvent.modifierFlags() & OS.NSControlKeyMask) == 0 && (state & DRAG_DETECT) != 0 && hooks (SWT.DragDetect)) { + if (nsEvent.clickCount() == 1 && (nsEvent.modifierFlags() & OS.NSEventModifierFlagControl) == 0 && (state & DRAG_DETECT) != 0 && hooks (SWT.DragDetect)) { consume = new boolean[1]; NSPoint location = view.convertPoint_fromView_(nsEvent.locationInWindow(), null); if (!view.isFlipped ()) { @@ -4783,7 +4783,7 @@ boolean translateTraversal (int key, NSEvent theEvent, boolean [] consume) { } case 48: /* Tab */ { long modifiers = theEvent.modifierFlags (); - boolean next = (modifiers & OS.NSShiftKeyMask) == 0; + boolean next = (modifiers & OS.NSEventModifierFlagShift) == 0; detail = next ? SWT.TRAVERSE_TAB_NEXT : SWT.TRAVERSE_TAB_PREVIOUS; break; } @@ -4799,7 +4799,7 @@ boolean translateTraversal (int key, NSEvent theEvent, boolean [] consume) { case 121: /* Page down */ { all = true; long modifiers = theEvent.modifierFlags (); - if ((modifiers & OS.NSControlKeyMask) == 0) return false; + if ((modifiers & OS.NSEventModifierFlagControl) == 0) return false; detail = key == 121 /* Page down */ ? SWT.TRAVERSE_PAGE_NEXT : SWT.TRAVERSE_PAGE_PREVIOUS; break; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java index 25f78cbc179..c2524f8e8c9 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java @@ -1103,7 +1103,7 @@ void createMainMenu () { title = NSString.stringWith(SWT.getMessage("SWT_HideOthers")); menuItem = appleMenu.addItemWithTitle(title, OS.sel_hideOtherApplications_, NSString.stringWith("h")); - menuItem.setKeyEquivalentModifierMask(OS.NSCommandKeyMask | OS.NSAlternateKeyMask); + menuItem.setKeyEquivalentModifierMask(OS.NSEventModifierFlagCommand | OS.NSAlternateKeyMask); menuItem.setTarget(applicationDelegate); title = NSString.stringWith(SWT.getMessage("SWT_ShowAll")); @@ -3924,9 +3924,9 @@ boolean performKeyEquivalent(NSWindow window, NSEvent nsEvent) { long selector = 0; long modifierFlags = nsEvent.modifierFlags(); if ((modifierFlags & OS.NSAlternateKeyMask) != 0) stateMask |= SWT.ALT; - if ((modifierFlags & OS.NSShiftKeyMask) != 0) stateMask |= SWT.SHIFT; - if ((modifierFlags & OS.NSControlKeyMask) != 0) stateMask |= SWT.CONTROL; - if ((modifierFlags & OS.NSCommandKeyMask) != 0) stateMask |= SWT.COMMAND; + if ((modifierFlags & OS.NSEventModifierFlagShift) != 0) stateMask |= SWT.SHIFT; + if ((modifierFlags & OS.NSEventModifierFlagControl) != 0) stateMask |= SWT.CONTROL; + if ((modifierFlags & OS.NSEventModifierFlagCommand) != 0) stateMask |= SWT.COMMAND; if (stateMask == SWT.COMMAND) { short keyCode = nsEvent.keyCode (); switch (keyCode) { @@ -5679,7 +5679,7 @@ void applicationSendEvent (long id, long sel, long event) { * Feature in Cocoa. The help key triggers context-sensitive help but doesn't get forwarded to the window as a key event. * If the event is destined for the key window, is the help key, and is an NSKeyDown, send it directly to the window first. */ - if (window != null && window.isKeyWindow() && nsEvent.type() == OS.NSKeyDown && (nsEvent.modifierFlags() & OS.NSHelpKeyMask) != 0) { + if (window != null && window.isKeyWindow() && nsEvent.type() == OS.NSKeyDown && (nsEvent.modifierFlags() & OS.NSEventModifierFlagHelp) != 0) { window.sendEvent(nsEvent); } @@ -5687,7 +5687,7 @@ void applicationSendEvent (long id, long sel, long event) { * Feature in Cocoa. NSKeyUp events are not delivered to the window if the command key is down. * If the event is destined for the key window, and it's a key up and the command key is down, send it directly to the window. */ - if (window != null && window.isKeyWindow() && nsEvent.type() == OS.NSKeyUp && (nsEvent.modifierFlags() & OS.NSCommandKeyMask) != 0) { + if (window != null && window.isKeyWindow() && nsEvent.type() == OS.NSKeyUp && (nsEvent.modifierFlags() & OS.NSEventModifierFlagCommand) != 0) { window.sendEvent(nsEvent); } else { objc_super super_struct = new objc_super (); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Link.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Link.java index 6d075f0136a..c1f1fd465a7 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Link.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Link.java @@ -824,7 +824,7 @@ int traversalCode (int key, NSEvent theEvent) { int bits = super.traversalCode (key, theEvent); if (key == 48 /* Tab */ && theEvent != null) { long modifierFlags = theEvent.modifierFlags(); - boolean next = (modifierFlags & OS.NSShiftKeyMask) == 0; + boolean next = (modifierFlags & OS.NSEventModifierFlagShift) == 0; if (next && focusIndex < offsets.length - 1) { return bits & ~ SWT.TRAVERSE_TAB_NEXT; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Menu.java index 2bdf5da08c8..9c5bbfab320 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Menu.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Menu.java @@ -389,7 +389,7 @@ void createItem (MenuItem item, int index) { long keyEquiv = 0; if (keyEquivString != null) { keyEquiv = keyEquivString.characterAtIndex(0); - if ((keyMask & OS.NSCommandKeyMask) != 0) keyEquiv |= SWT.COMMAND; + if ((keyMask & OS.NSEventModifierFlagCommand) != 0) keyEquiv |= SWT.COMMAND; if ((keyMask & OS.NSAlternateKeyMask) != 0) keyEquiv |= SWT.ALT; item.accelerator = (int) keyEquiv; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MenuItem.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MenuItem.java index 512ef0ab5ca..5ddd1191c6d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MenuItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MenuItem.java @@ -623,9 +623,9 @@ public void setAccelerator (int accelerator) { nsItem.setKeyEquivalent (nsstring.lowercaseString()); nsstring.release(); int mask = 0; - if ((accelerator & SWT.SHIFT) != 0) mask |= OS.NSShiftKeyMask; - if ((accelerator & SWT.CONTROL) != 0) mask |= OS.NSControlKeyMask; - if ((accelerator & SWT.COMMAND) != 0) mask |= OS.NSCommandKeyMask; + if ((accelerator & SWT.SHIFT) != 0) mask |= OS.NSEventModifierFlagShift; + if ((accelerator & SWT.CONTROL) != 0) mask |= OS.NSEventModifierFlagControl; + if ((accelerator & SWT.COMMAND) != 0) mask |= OS.NSEventModifierFlagCommand; if ((accelerator & SWT.ALT) != 0) mask |= OS.NSAlternateKeyMask; nsItem.setKeyEquivalentModifierMask (mask); } @@ -951,10 +951,10 @@ boolean updateAccelerator (boolean show) { if (i < buffer.length && buffer [i] == '\t') { for (j = i + 1; j < buffer.length; j++) { switch (buffer [j]) { - case '\u2303': mask |= OS.NSControlKeyMask; i++; break; + case '\u2303': mask |= OS.NSEventModifierFlagControl; i++; break; case '\u2325': mask |= OS.NSAlternateKeyMask; i++; break; - case '\u21E7': mask |= OS.NSShiftKeyMask; i++; break; - case '\u2318': mask |= OS.NSCommandKeyMask; i++; break; + case '\u21E7': mask |= OS.NSEventModifierFlagShift; i++; break; + case '\u2318': mask |= OS.NSEventModifierFlagCommand; i++; break; default: j = buffer.length; break; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MessageBox.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MessageBox.java index 53684bcb4d2..2358dc0186b 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MessageBox.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/MessageBox.java @@ -151,16 +151,16 @@ public String getMessage () { */ public int open () { NSAlert alert = (NSAlert) new NSAlert().alloc().init(); - int alertType = OS.NSInformationalAlertStyle; + int alertType = OS.NSAlertStyleInformational; if ((style & SWT.ICON_ERROR) != 0) { - alertType = OS.NSCriticalAlertStyle; + alertType = OS.NSAlertStyleCritical; } if (((style & SWT.ICON_INFORMATION) != 0) || ((style & SWT.ICON_WORKING) != 0) || ((style & SWT.ICON_QUESTION) != 0)) { - alertType = OS.NSInformationalAlertStyle; + alertType = OS.NSAlertStyleInformational; alert.setIcon(NSImage.imageNamed(OS.NSImageNameInfo)); } if ((style & SWT.ICON_WARNING) != 0) { - alertType = OS.NSWarningAlertStyle; + alertType = OS.NSAlertStyleWarning; alert.setIcon(NSImage.imageNamed(OS.NSImageNameCaution)); } alert.setAlertStyle(alertType); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ProgressBar.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ProgressBar.java index eac59b1a91f..a01615d5b55 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ProgressBar.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ProgressBar.java @@ -87,7 +87,11 @@ static int checkStyle (int style) { @Override public Point computeSize (int wHint, int hHint, boolean changed) { checkWidget(); - int size = OS.NSProgressIndicatorPreferredThickness; + NSProgressIndicator widget = (NSProgressIndicator)view; + NSRect oldFrame = widget.frame(); + widget.sizeToFit(); + int size = (int)widget.frame().height; + widget.setFrame(oldFrame); int width = 0, height = 0; if ((style & SWT.HORIZONTAL) != 0) { height = size; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Sash.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Sash.java index e9792688bc5..200a9ec1e98 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Sash.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Sash.java @@ -305,7 +305,7 @@ boolean sendKeyEvent(NSEvent nsEvent, int type) { int xChange = 0, yChange = 0; int stepSize = PAGE_INCREMENT; long modifiers = nsEvent.modifierFlags(); - if ((modifiers & OS.NSControlKeyMask) != 0) stepSize = INCREMENT; + if ((modifiers & OS.NSEventModifierFlagControl) != 0) stepSize = INCREMENT; if ((style & SWT.VERTICAL) != 0) { if (keyCode == 126 || keyCode == 125) break; xChange = keyCode == 123 ? -stepSize : stepSize; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ScrollBar.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ScrollBar.java index ea25420fec6..8e29470b678 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ScrollBar.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/ScrollBar.java @@ -481,18 +481,10 @@ void sendSelection () { int hitPart = (int)((NSScroller)view).testPart(point); Event event = new Event(); switch (hitPart) { - case OS.NSScrollerDecrementLine: - value -= increment; - event.detail = SWT.ARROW_UP; - break; case OS.NSScrollerDecrementPage: value -= pageIncrement; event.detail = SWT.PAGE_UP; break; - case OS.NSScrollerIncrementLine: - value += increment; - event.detail = SWT.ARROW_DOWN; - break; case OS.NSScrollerIncrementPage: value += pageIncrement; event.detail = SWT.PAGE_DOWN; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java index 9ef9aa678ea..0029ba4b3bb 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Shell.java @@ -2515,7 +2515,7 @@ void windowSendEvent (long id, long sel, long event) { * swallowed to handle native traversal. If we find that, force the key event to * the first responder. */ - if ((nsEvent.modifierFlags() & OS.NSControlKeyMask) != 0) { + if ((nsEvent.modifierFlags() & OS.NSEventModifierFlagControl) != 0) { NSString chars = nsEvent.characters(); if (chars != null && chars.length() == 1) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Slider.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Slider.java index 43e73d710fb..a6490a86622 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Slider.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Slider.java @@ -333,18 +333,10 @@ void sendSelection () { int hitPart = (int)((NSScroller)view).hitPart(); int value = getSelection (); switch (hitPart) { - case OS.NSScrollerDecrementLine: - event.detail = SWT.ARROW_UP; - value -= increment; - break; case OS.NSScrollerDecrementPage: value -= pageIncrement; event.detail = SWT.PAGE_UP; break; - case OS.NSScrollerIncrementLine: - value += increment; - event.detail = SWT.ARROW_DOWN; - break; case OS.NSScrollerIncrementPage: value += pageIncrement; event.detail = SWT.PAGE_DOWN; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java index 49549fcf9ca..5d6825ec32a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Table.java @@ -258,7 +258,7 @@ boolean canDragRowsWithIndexes_atPoint(long id, long sel, long rowIndexes, NSPoi boolean drag = (state & DRAG_DETECT) != 0 && hooks (SWT.DragDetect); if (drag) { - if (!widget.isRowSelected(row) && (modifiers & (OS.NSCommandKeyMask | OS.NSShiftKeyMask | OS.NSAlternateKeyMask)) == 0) { + if (!widget.isRowSelected(row) && (modifiers & (OS.NSEventModifierFlagCommand | OS.NSEventModifierFlagShift | OS.NSAlternateKeyMask)) == 0) { NSIndexSet set = (NSIndexSet)new NSIndexSet().alloc(); set = set.initWithIndex(row); widget.selectRowIndexes (set, false); @@ -2080,7 +2080,7 @@ void mouseDown (long id, long sel, long theEvent) { // which is interpreted as a single click that clears the selection. Fix is to ignore control-click if the // view has a context menu. NSEvent event = new NSEvent(theEvent); - if ((event.modifierFlags() & OS.NSControlKeyMask) != 0) return; + if ((event.modifierFlags() & OS.NSEventModifierFlagControl) != 0) return; } super.mouseDown(id, sel, theEvent); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Text.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Text.java index ee965adcccd..17fd2df1630 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Text.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Text.java @@ -1714,7 +1714,7 @@ boolean sendKeyEvent (NSEvent nsEvent, int type) { if (!result) return result; if (type != SWT.KeyDown) return result; long modifierFlags = nsEvent.modifierFlags(); - if ((modifierFlags & OS.NSCommandKeyMask) != 0) { + if ((modifierFlags & OS.NSEventModifierFlagCommand) != 0) { short keyCode = nsEvent.keyCode (); switch (keyCode) { case 7: /* X */ @@ -2460,8 +2460,8 @@ int traversalCode (int key, NSEvent theEvent) { bits &= ~SWT.TRAVERSE_RETURN; if (key == 48 /* Tab */ && theEvent != null) { long modifiers = theEvent.modifierFlags (); - boolean next = (modifiers & OS.NSShiftKeyMask) == 0; - if (next && (modifiers & OS.NSControlKeyMask) == 0) { + boolean next = (modifiers & OS.NSEventModifierFlagShift) == 0; + if (next && (modifiers & OS.NSEventModifierFlagControl) == 0) { bits &= ~(SWT.TRAVERSE_TAB_NEXT | SWT.TRAVERSE_TAB_PREVIOUS); } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tracker.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tracker.java index aa8e2507e93..374a5f3c93d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tracker.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tracker.java @@ -559,9 +559,9 @@ void key (NSEvent nsEvent) { int mask = 0; switch (keyCode) { case SWT.ALT: mask = OS.NSAlternateKeyMask; break; - case SWT.CONTROL: mask = OS.NSControlKeyMask; break; - case SWT.COMMAND: mask = OS.NSCommandKeyMask; break; - case SWT.SHIFT: mask = OS.NSShiftKeyMask; break; + case SWT.CONTROL: mask = OS.NSEventModifierFlagControl; break; + case SWT.COMMAND: mask = OS.NSEventModifierFlagCommand; break; + case SWT.SHIFT: mask = OS.NSEventModifierFlagShift; break; case SWT.CAPS_LOCK: Event event = new Event(); event.keyCode = keyCode; @@ -583,7 +583,7 @@ void key (NSEvent nsEvent) { } } - int stepSize = (modifierFlags & OS.NSControlKeyMask) != 0 ? STEPSIZE_SMALL : STEPSIZE_LARGE; + int stepSize = (modifierFlags & OS.NSEventModifierFlagControl) != 0 ? STEPSIZE_SMALL : STEPSIZE_LARGE; int xChange = 0, yChange = 0; switch (nsKeyCode) { case 53: /* Esc */ diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/TrayItem.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/TrayItem.java index 00b271bfdf7..328e6139996 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/TrayItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/TrayItem.java @@ -510,7 +510,7 @@ boolean shouldShowMenu (NSEvent event) { if (!(hooks(SWT.Selection) || hooks(SWT.DefaultSelection))) { return true; } - if ((event.modifierFlags() & OS.NSDeviceIndependentModifierFlagsMask) == OS.NSControlKeyMask) { + if ((event.modifierFlags() & OS.NSDeviceIndependentModifierFlagsMask) == OS.NSEventModifierFlagControl) { return true; } return false; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java index 0a92f0beb2f..4195ef69532 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Tree.java @@ -312,7 +312,7 @@ boolean canDragRowsWithIndexes_atPoint(long id, long sel, long rowIndexes, NSPoi boolean drag = (state & DRAG_DETECT) != 0 && hooks (SWT.DragDetect); if (drag) { - if (!widget.isRowSelected(row) && (modifiers & (OS.NSCommandKeyMask | OS.NSShiftKeyMask | OS.NSAlternateKeyMask | OS.NSControlKeyMask)) == 0) { + if (!widget.isRowSelected(row) && (modifiers & (OS.NSEventModifierFlagCommand | OS.NSEventModifierFlagShift | OS.NSAlternateKeyMask | OS.NSEventModifierFlagControl)) == 0) { NSIndexSet set = (NSIndexSet)new NSIndexSet().alloc(); set = set.initWithIndex(row); widget.selectRowIndexes (set, false); @@ -2133,7 +2133,7 @@ void mouseDown (long id, long sel, long theEvent) { // it from menuForEvent:. This has the side effect, however, of sending control-click to the NSTableView, // which is interpreted as a single click that clears the selection. Fix is to ignore control-click, NSEvent event = new NSEvent(theEvent); - if ((event.modifierFlags() & OS.NSControlKeyMask) != 0) return; + if ((event.modifierFlags() & OS.NSEventModifierFlagControl) != 0) return; } super.mouseDown(id, sel, theEvent); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Widget.java index 9e3b018b5f1..3804a64ecf6 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Widget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Widget.java @@ -1879,9 +1879,9 @@ boolean setInputState (Event event, NSEvent nsEvent, int type) { } long modifierFlags = nsEvent.modifierFlags(); if ((modifierFlags & OS.NSAlternateKeyMask) != 0) event.stateMask |= SWT.ALT; - if ((modifierFlags & OS.NSShiftKeyMask) != 0) event.stateMask |= SWT.SHIFT; - if ((modifierFlags & OS.NSControlKeyMask) != 0) event.stateMask |= SWT.CONTROL; - if ((modifierFlags & OS.NSCommandKeyMask) != 0) event.stateMask |= SWT.COMMAND; + if ((modifierFlags & OS.NSEventModifierFlagShift) != 0) event.stateMask |= SWT.SHIFT; + if ((modifierFlags & OS.NSEventModifierFlagControl) != 0) event.stateMask |= SWT.CONTROL; + if ((modifierFlags & OS.NSEventModifierFlagCommand) != 0) event.stateMask |= SWT.COMMAND; long state = NSEvent.pressedMouseButtons(); if ((state & 0x1) != 0) event.stateMask |= SWT.BUTTON1; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/library/make_common.mak b/bundles/org.eclipse.swt/Eclipse SWT/common/library/make_common.mak index d66419e442d..672f294dafe 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/library/make_common.mak +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/library/make_common.mak @@ -14,4 +14,4 @@ maj_ver=4 min_ver=974 -rev=7 +rev=10 diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Item.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Item.java index fd33f71ca76..c3e16f24878 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Item.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Item.java @@ -230,7 +230,6 @@ private void handleDPIChange(Event event) { // Refresh the image Image image = getImage(); if (image != null) { - setImage(null); setImage(image); } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java index e6b18fbacfb..23e74f61c62 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java @@ -1396,7 +1396,20 @@ void moveAbove (long child, long sibling) { long parentHandle = parentingHandle (); if (GTK.GTK4) { if (sibling == 0) { - GTK4.gtk_widget_insert_after(child, parentHandle, 0L); + /* + * True raise-to-top-of-paint-order, scoped to Shell-direct + * children (e.g. an overlay pane raised via moveAbove(null)). + * GTK4 has no per-widget GdkWindow, so this same native sibling + * list also backs Composite._getChildren(); other code already + * relies on today's insert_after(..., NULL) behaviour to keep + * getChildren() stable for non-Shell parents, so this is not + * changed generally. + */ + if (this instanceof Shell) { + GTK4.gtk_widget_insert_before(child, parentHandle, 0L); + } else { + GTK4.gtk_widget_insert_after(child, parentHandle, 0L); + } } else { GTK4.gtk_widget_insert_before(child, parentHandle, sibling); } @@ -1409,13 +1422,27 @@ void moveAbove (long child, long sibling) { void moveBelow (long child, long sibling) { if (child == sibling) return; long parentHandle = parentingHandle (); - if (sibling == 0 && parentHandle == fixedHandle) { + /* + * GTK3-only: when sibling == 0 (move to the bottom of the z-order) the + * child would be stacked behind the scrolled content's GdkWindow, hiding + * overlay controls such as Table/Tree editors. Re-place it just above the + * scrolled content instead. + * + * Not needed on GTK4: widgets have no per-widget GdkWindow, so paint order + * is simply the parent's child-list order (first = bottom, last = top). + */ + if (!GTK.GTK4 && sibling == 0 && parentHandle == fixedHandle) { moveAbove (child, scrolledHandle != 0 ? scrolledHandle : handle); return; } if (GTK.GTK4) { + /* Mirror image of moveAbove - see the comment there. */ if (sibling == 0) { - GTK4.gtk_widget_insert_before(child, parentHandle, 0L); + if (this instanceof Shell) { + GTK4.gtk_widget_insert_after(child, parentHandle, 0L); + } else { + GTK4.gtk_widget_insert_before(child, parentHandle, 0L); + } } else { GTK4.gtk_widget_insert_after(child, parentHandle, sibling); } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java index c78a643e29c..59ac7ed8968 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java @@ -815,7 +815,13 @@ void createWidget(int index) { checkBuffered(); showWidget(); setInitialBounds(); - setZOrder(null, false, false); + if (GTK.GTK4 && !(this instanceof Shell)) { + /* moveBelow(null) now sinks Shell-direct children to the back + * (see Composite.moveAbove) - guard against undoing that. */ + setZOrder(null, false, false, !(parent instanceof Shell)); + } else { + setZOrder(null, false, false); + } if (!GTK.GTK4) setRelations(); checkMirrored(); checkBorder(); @@ -2501,7 +2507,9 @@ public void removePaintListener(PaintListener listener) { void removeRelation () { if (!isDescribedByLabel ()) return; /* there will not be any */ if (labelRelation != null) { - _getAccessible().removeRelation (ACC.RELATION_LABELLED_BY, labelRelation._getAccessible()); + if (accessible != null && labelRelation.accessible != null) { + accessible.removeRelation (ACC.RELATION_LABELLED_BY, labelRelation.accessible); + } labelRelation = null; } } @@ -4831,10 +4839,10 @@ void destroyWidget() { // GTK windows don't have a parent, so destroy it now GTK4.gtk_window_destroy(currHandle); } else if (parent != null) { - if (fixedHandle != 0) { - // Remove widget from hierarchy by removing it from parent container - OS.swt_fixed_remove(parent.parentingHandle(), fixedHandle); - } + /* Use currHandle, not fixedHandle alone - widgets without a + * separate fixedHandle wrapper were otherwise never actually + * unparented here, leaving them alive and rendered natively. */ + OS.swt_fixed_remove(parent.parentingHandle(), currHandle); } else { assert false : "widgets must have a parent or be a GtkWindow"; } @@ -5923,7 +5931,12 @@ public boolean setParent (Composite parent) { allocation.height = height; gtk_widget_size_allocate(topHandle, allocation, -1); this.parent = parent; - setZOrder (null, false, true); + if (GTK.GTK4 && !(this instanceof Shell)) { + /* See createWidget() re: guarding fixChildren on GTK4. */ + setZOrder (null, false, true, !(parent instanceof Shell)); + } else { + setZOrder (null, false, true); + } reskin (SWT.ALL); // restore focus to the last Control that had it, if focus is now gone if (focusControlBeforeReparent != null && !focusControlBeforeReparent.isDisposed() && display.getFocusControl() == null) { @@ -6153,6 +6166,20 @@ public void setVisible (boolean visible) { if (enableWindow != 0) GDK.gdk_window_show_unraised(enableWindow); } gtk_widget_show (topHandle); + /* + * On GTK4, a Composite laid out while it (or an ancestor) is hidden can end + * up with its children sized against a stale 0x0 client area, because + * gtk_widget_hide() resets allocations to 0x0 (issue #3330) and setBounds on a + * hidden widget shows/allocates/re-hides it. The size given while hidden is + * still stored in the parent's swt_fixed child list, so re-running the parent's + * size allocation re-applies this control's real size (restoring its client + * area), after which a re-layout of its own subtree lets the children pick up + * the now-correct client area. See issue #3450. + */ + if (GTK.GTK4 && this instanceof Composite composite && composite.layout != null) { + parent.forceResize (); + composite.layout (true, true); + } } } else { /* diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index ab7d2e476e3..4a1bc89cce3 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -135,12 +135,14 @@ public class Display extends Device implements Executor { long changeValueProc; long snapshotDrawProc, keyPressReleaseProc, focusProc, windowActiveProc, enterMotionProc, leaveProc, scrollProc, resizeProc, layoutProc, activateProc, gesturePressReleaseProc; + long menuItemsChangedProc; long notifyProc; long computeSizeProc; Callback windowCallback2, windowCallback3, windowCallback4, windowCallback5, windowCallback6; Callback changeValue; Callback snapshotDraw, keyPressReleaseCallback, focusCallback, windowActiveCallback, enterMotionCallback, computeSizeCallback, scrollCallback, leaveCallback, resizeCallback, layoutCallback, activateCallback, gesturePressReleaseCallback; + Callback menuItemsChangedCallback; Callback notifyCallback; EventTable eventTable, filterTable; static String APP_NAME = "SWT"; //$NON-NLS-1$ @@ -3633,6 +3635,10 @@ void initializeCallbacks () { activateCallback = new Callback(this, "activateProc", void.class, new Type[] {long.class, long.class, long.class}); //$NON-NLS-1$ activateProc = activateCallback.getAddress(); + menuItemsChangedCallback = new Callback(this, "menuItemsChangedProc", void.class, new Type[] { + long.class, int.class, int.class, int.class, long.class}); //$NON-NLS-1$ + menuItemsChangedProc = menuItemsChangedCallback.getAddress(); + computeSizeCallback = new Callback(this, "computeSizeProc", void.class, new Type[] {long.class, long.class, long.class}); //$NON-NLS-1$ computeSizeProc = computeSizeCallback.getAddress(); } @@ -4671,6 +4677,10 @@ void releaseDisplay () { activateCallback = null; activateProc = 0; + menuItemsChangedCallback.dispose(); + menuItemsChangedCallback = null; + menuItemsChangedProc = 0; + computeSizeCallback.dispose(); computeSizeCallback = null; computeSizeProc = 0; @@ -6145,6 +6155,11 @@ void activateProc(long action, long parameter, long user_data) { widget.gtk_activate(user_data); } +void menuItemsChangedProc(long model, int position, int removed, int added, long user_data) { + Widget widget = getWidget(user_data); + if (widget instanceof Menu menu) menu.modelItemsChanged(); +} + void resizeProc(long handle, int width, int height) { Widget widget = getWidget(handle); if (widget != null) widget.gtk_size_allocate(handle, 0); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java index 8f5c2159541..19467e4b2d3 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java @@ -261,6 +261,88 @@ boolean ableToSetLocation() { return hasLocation; } +/* + * GtkPopoverMenu inserts section-separator widgets from a GLib idle callback + * (gtk_menu_section_box_handle_sync_separators). Calling gtk_popover_popup() + * synchronously would measure the menu before those widgets exist, undersizing + * it into a scrollbar. Deferring via asyncExec lets that idle run first. + */ +void popupGtk4Popover(boolean hasPointingTo, int pointX, int pointY) { + if (isDisposed()) return; + display.asyncExec(() -> { + if (isDisposed()) return; + if (hasPointingTo) { + GdkRectangle popoverPosition = new GdkRectangle(); + popoverPosition.x = pointX; + popoverPosition.y = pointY; + popoverPosition.width = popoverPosition.height = 1; + GTK.gtk_popover_set_pointing_to(handle, popoverPosition); + // Hide during the fit (scheduleGtk4PopoverFit) so the menu appears + // directly at its final location without a visible jump. + GTK.gtk_widget_set_opacity(handle, 0.0); + GTK.gtk_popover_popup(handle); + scheduleGtk4PopoverFit(pointX, pointY, 0); + } else { + GTK.gtk_popover_popup(handle); + } + }); +} + +/* + * Keep a bottom-of-screen popup menu from being shrunk into a scrolled view. + * + * GtkPopover's layout carries GDK_ANCHOR_RESIZE_Y (a GtkPopoverMenu can always + * shrink), so when the menu fits neither below the anchor nor flipped above it, + * the compositor shrinks it and shows a scrollbar. Wayland has no global window + * coordinates, so we cannot compute the overflow directly; instead we compare + * the popover's allocated height to its natural height and shift the anchor up + * by the shortfall, which yields exactly that much more room below. Repeated + * until it fits (usually 2-3 passes; the bound is just a safety cap). The + * popover stays hidden until settled. + */ +static final int GTK4_POPOVER_FIT_MAX_ITERATIONS = 6; + +void scheduleGtk4PopoverFit(int pointX, int pointY, int iteration) { + display.asyncExec(() -> { + if (isDisposed()) return; + if (!GTK.gtk_widget_get_mapped(handle)) { + // Menu was dismissed before it settled; make sure it is not left + // permanently transparent for a future show. + GTK.gtk_widget_set_opacity(handle, 1.0); + return; + } + if (iteration >= GTK4_POPOVER_FIT_MAX_ITERATIONS) { + revealGtk4Popover(); + return; + } + int[] natHeight = new int[1]; + GTK4.gtk_widget_measure(handle, GTK.GTK_ORIENTATION_VERTICAL, -1, null, natHeight, null, null); + int allocated = GTK4.gtk_widget_get_height(handle); + if (natHeight[0] <= 0 || allocated <= 0) { + // Not measured/allocated yet; wait another turn. + scheduleGtk4PopoverFit(pointX, pointY, iteration + 1); + return; + } + int shortfall = natHeight[0] - allocated; + if (shortfall <= 0) { + revealGtk4Popover(); + return; + } + int newPointY = pointY - shortfall; + GdkRectangle popoverPosition = new GdkRectangle(); + popoverPosition.x = pointX; + popoverPosition.y = newPointY; + popoverPosition.width = popoverPosition.height = 1; + GTK.gtk_popover_set_pointing_to(handle, popoverPosition); + scheduleGtk4PopoverFit(pointX, newPointY, iteration + 1); + }); +} + +void revealGtk4Popover() { + if (isDisposed()) return; + GTK.gtk_widget_set_opacity(handle, 1.0); +} + void _setVisible (boolean visible) { if (visible == GTK.gtk_widget_get_mapped (handle)) return; if (visible) { @@ -307,7 +389,6 @@ void _setVisible (boolean visible) { long eventPtr = 0; if (ableToSetLocation()) { if (GTK.GTK4) { - GdkRectangle popoverPosition = new GdkRectangle(); /* * gtk_popover_set_pointing_to expects coordinates in the coordinate * space of the popover's current parent widget. @@ -323,24 +404,16 @@ void _setVisible (boolean visible) { * relative to that control - do not apply the shell to * parent.handle translation. */ + int pointX = x, pointY = y; long currentParent = GTK.gtk_widget_get_parent(handle); if (currentParent == parent.handle) { double[] relX = new double[1], relY = new double[1]; if (GTK4.gtk_widget_translate_coordinates(parent.getShell().topHandle(), parent.handle, x, y, relX, relY)) { - popoverPosition.x = (int) relX[0]; - popoverPosition.y = (int) relY[0]; - } else { - popoverPosition.x = x; - popoverPosition.y = y; + pointX = (int) relX[0]; + pointY = (int) relY[0]; } - } else { - popoverPosition.x = x; - popoverPosition.y = y; } - popoverPosition.width = popoverPosition.height = 1; - GTK.gtk_popover_set_pointing_to(handle, popoverPosition); - - GTK.gtk_popover_popup(handle); + popupGtk4Popover(true, pointX, pointY); } else { // Create the GdkEvent manually as we need to control // certain fields like the event window @@ -384,7 +457,7 @@ void _setVisible (boolean visible) { } } else { if (GTK.GTK4) { - GTK.gtk_popover_popup(handle); + popupGtk4Popover(false, 0, 0); } else { /* * GTK Feature: gtk_menu_popup is deprecated as of GTK3.22 and the new method gtk_menu_popup_at_pointer @@ -871,6 +944,86 @@ long gtk_map (long widget) { return super.gtk_map(widget); } +/** + * Re-runs the CASCADE submenu SHOW/HIDE wiring after the menu structure changed + * while already mapped. The initial wiring at + * {@link #gtk_map}/{@link #gtk_show} only covers submenus that existed then; + * ones attached, replaced or rebuilt later would otherwise never get their + * {@link SWT#Show} event, making lazily-populated submenus appear empty. + */ +private void reconnectDropDownMenuSignalsIfMapped() { + if (!GTK.GTK4) return; + if ((style & SWT.BAR) != 0) { + if (handle != 0 && GTK.gtk_widget_get_mapped(handle)) { + connectDropDownMenuSignals(); + } + } else if ((style & SWT.POP_UP) != 0) { + if (handle != 0 && GTK.gtk_widget_get_mapped(handle)) { + connectCascadeSubMenuSignals(this, handle); + } + } else if ((style & SWT.DROP_DOWN) != 0) { + if (popoverHandle != 0 && GTK.gtk_widget_get_mapped(popoverHandle)) { + connectCascadeSubMenuSignals(this, popoverHandle); + } + } +} + +/** + * Hooks this menu's {@code items-changed} handler on the given {@code GMenu} + * model (no-op for {@code 0}), routing back via {@link #handle}. Connected in the + * signal's after phase so it runs once GTK's {@code GtkMenuTracker} has + * already (re)built the nested GtkPopoverMenu widgets, letting re-wiring run + * synchronously. + */ +void hookItemsChanged(long model) { + if (GTK.GTK4 && model != 0) { + long closure = OS.g_cclosure_new(display.menuItemsChangedProc, handle, 0); + OS.g_signal_connect_closure(model, OS.items_changed, closure, true); + } +} + +/** + * Called when this menu's {@code GMenuModel} emitted {@code items-changed} (its + * content was modified after creation). Re-runs the CASCADE submenu wiring from + * the nearest mapped ancestor so any nested GtkPopoverMenu GTK (re)built gets its + * {@link SWT#Show} routed to the SWT DROP_DOWN submenu. Runs synchronously (see + * {@link #hookItemsChanged(long)}); a cheap no-op if no ancestor is mapped. + */ +void modelItemsChanged() { + if (!GTK.GTK4 || isDisposed()) return; + Menu root = this; + while (root.cascade != null && root.cascade.parent != null && !root.cascade.parent.isDisposed()) { + root = root.cascade.parent; + } + root.reconnectDropDownMenuSignalsIfMapped(); +} + +/** + * Wires the SHOW/HIDE signals of the given DROP_DOWN {@code submenu} to the + * discovered {@code popover} GtkPopoverMenu widget, refreshing the SWT-side + * cache. If the submenu was previously wired to a now-stale popover (e.g. GTK + * rebuilt the widget after a model change), the stale handle is released first. + */ +private void wireSubMenuPopover(Menu submenu, long popover) { + if (submenu.popoverHandle == popover) return; + if (submenu.popoverHandle != 0) { + /* + * Release the stale popover we previously cached (mirrors deregister()). Its + * SHOW/HIDE closures are deliberately left connected: GTK normally destroys the + * widget along with the model change, and should it survive, removeWidget() above + * means its handlers no longer resolve to a widget and simply no-op. + */ + display.removeWidget(submenu.popoverHandle); + OS.g_object_unref(submenu.popoverHandle); + submenu.popoverHandle = 0; + } + OS.g_object_ref(popover); + submenu.popoverHandle = popover; + display.addWidget(popover, submenu); + OS.g_signal_connect_closure_by_id(popover, display.signalIds[SHOW], 0, display.getClosure(SHOW), false); + OS.g_signal_connect_closure_by_id(popover, display.signalIds[HIDE], 0, display.getClosure(HIDE), false); +} + private void connectDropDownMenuSignals() { if (items == null) return; long barItem = GTK4.gtk_widget_get_first_child(handle); @@ -878,17 +1031,13 @@ private void connectDropDownMenuSignals() { for (MenuItem menuItem : items) { if (barItem == 0) break; if ((menuItem.style & SWT.SEPARATOR) != 0) continue; - if (menuItem.menu != null && menuItem.menu.popoverHandle == 0) { + if (menuItem.menu != null) { long popover = findGtkPopoverMenuChild(barItem); - if (popover != 0) { - OS.g_object_ref(popover); - menuItem.menu.popoverHandle = popover; - display.addWidget(popover, menuItem.menu); - OS.g_signal_connect_closure_by_id(popover, display.signalIds[SHOW], 0, display.getClosure(SHOW), false); - OS.g_signal_connect_closure_by_id(popover, display.signalIds[HIDE], 0, display.getClosure(HIDE), false); - /* - * Also connect SHOW/HIDE signals for nested CASCADE submenus. - */ + /* Re-wire when the discovered popover differs from the cache (initial or GTK rebuilt it). */ + if (popover != 0 && menuItem.menu.popoverHandle != popover) { + wireSubMenuPopover(menuItem.menu, popover); + } + if (menuItem.menu.popoverHandle != 0) { connectCascadeSubMenuSignals(menuItem.menu); } } @@ -900,30 +1049,33 @@ private void connectCascadeSubMenuSignals(Menu menu) { connectCascadeSubMenuSignals(menu, menu.popoverHandle); } +/** + * Connects SHOW/HIDE signals for the CASCADE submenus of the given menu. A + * submenu whose GtkPopoverMenu is not (yet) present is simply skipped; the next + * pass triggered by MAP, SHOW or "items-changed" picks it up. + */ private void connectCascadeSubMenuSignals(Menu menu, long parentPopoverHandle) { if (menu == null || parentPopoverHandle == 0 || menu.items == null) return; for (MenuItem item : menu.items) { if ((item.style & SWT.CASCADE) != 0 && item.menu != null) { - /* - * item.menu is the CASCADE submenu (always SWT.DROP_DOWN style). - * Its popoverHandle is 0 until we find and register its GtkPopoverMenu. - * Skip if already connected (popoverHandle != 0). - */ - if (item.menu.popoverHandle != 0) continue; + /* Re-discover every pass: a rebuild can make GTK replace the widget, + * leaving a stale handle whose SHOW never fires (submenu appears empty). */ long nestedPopover = findNestedPopoverForModel(parentPopoverHandle, item.menu.modelHandle); if (nestedPopover != 0) { - OS.g_object_ref(nestedPopover); - item.menu.popoverHandle = nestedPopover; - display.addWidget(nestedPopover, item.menu); - OS.g_signal_connect_closure_by_id(nestedPopover, display.signalIds[SHOW], 0, display.getClosure(SHOW), false); - OS.g_signal_connect_closure_by_id(nestedPopover, display.signalIds[HIDE], 0, display.getClosure(HIDE), false); - // Recurse to handle further nested CASCADE submenus + if (item.menu.popoverHandle != nestedPopover) { + wireSubMenuPopover(item.menu, nestedPopover); + } connectCascadeSubMenuSignals(item.menu); } } } } +/** + * Recursively searches the widget subtree rooted at {@code parentWidget} for the + * nested GtkPopoverMenu whose GMenuModel is {@code targetModel}, returning its + * handle or {@code 0} if it is not (yet) present. + */ private long findNestedPopoverForModel(long parentWidget, long targetModel) { if (parentWidget == 0 || targetModel == 0) return 0; long child = GTK4.gtk_widget_get_first_child(parentWidget); @@ -983,6 +1135,10 @@ long gtk_show (long widget) { return 0; } sendEvent (SWT.Show); + /* Wire cascade submenu SHOW/HIDE signals once the DROP_DOWN popover is shown. */ + if (GTK.GTK4 && (style & SWT.DROP_DOWN) != 0 && popoverHandle != 0) { + connectCascadeSubMenuSignals(this, popoverHandle); + } if (OS.ubuntu_menu_proxy_get() != 0) { MenuItem[] items = getItems(); for (int i=0; idetailed action name: actionId qualified with the prefix the + * action group was inserted under (see Menu#createHandle), plus a target for + * SWT.RADIO. Use this where an action is referenced by name, such as + * g_menu_item_new() or gtk_named_action_new(), and never with the GActionMap + * API. + */ String actionName; /** @@ -264,38 +277,63 @@ void createHandle(int index) { case SWT.SEPARATOR: modelHandle = OS.g_menu_new(); handle = OS.g_menu_item_new_section(null, modelHandle); + /* + * A separator starts a new section GMenu; observe it for + * "items-changed" too so submenus added into this section later get + * wired (see Menu#hookItemsChanged and issue #3451). + */ + parent.hookItemsChanged(modelHandle); break; case SWT.RADIO: long stringVariantType = OS.g_variant_type_new(OS.G_VARIANT_TYPE_STRING); + actionId = String.valueOf(this.hashCode()); actionHandle = OS.g_simple_action_new_stateful( - Converter.javaStringToCString(String.valueOf(this.hashCode())), + Converter.javaStringToCString(actionId), stringVariantType, OS.g_variant_new_string(Converter.javaStringToCString("untoggled"))); OS.g_action_map_add_action(parent.actionGroup, actionHandle); - actionName = String.valueOf(parent.hashCode()) + "." + String.valueOf(this.hashCode()) + "::toggled"; + actionName = String.valueOf(parent.hashCode()) + "." + actionId + "::toggled"; handle = OS.g_menu_item_new(null, Converter.javaStringToCString(actionName)); OS.g_variant_type_free(stringVariantType); break; case SWT.CHECK: long boolVariantType = OS.g_variant_type_new(OS.G_VARIANT_TYPE_BOOLEAN); + actionId = String.valueOf(this.hashCode()); actionHandle = OS.g_simple_action_new_stateful( - Converter.javaStringToCString(String.valueOf(this.hashCode())), + Converter.javaStringToCString(actionId), 0, OS.g_variant_new_boolean(false)); OS.g_action_map_add_action(parent.actionGroup, actionHandle); - actionName = String.valueOf(parent.hashCode()) + "." + String.valueOf(this.hashCode()); + actionName = String.valueOf(parent.hashCode()) + "." + actionId; handle = OS.g_menu_item_new(null, Converter.javaStringToCString(actionName)); OS.g_variant_type_free(boolVariantType); break; case SWT.CASCADE: modelHandle = OS.g_menu_new(); - handle = OS.g_menu_item_new_submenu(Converter.javaStringToCString(""), modelHandle); + /* + * Give the CASCADE item an action so it can be enabled/disabled like + * on the other platforms and like GTK3. A plain submenu item created + * via g_menu_item_new_submenu has no action, so its GtkModelButton is + * always sensitive: setEnabled(false) would be a no-op and the (possibly + * empty) submenu could still be opened. Attaching a SimpleAction makes + * the item follow the action's enabled state. While the action is + * enabled, activating the item still navigates into the submenu (the + * action is not triggered); while disabled, the item is insensitive and + * the submenu cannot be opened. + */ + actionId = String.valueOf(this.hashCode()); + actionHandle = OS.g_simple_action_new(Converter.javaStringToCString(actionId), 0); + OS.g_action_map_add_action(parent.actionGroup, actionHandle); + actionName = String.valueOf(parent.hashCode()) + "." + actionId; + handle = OS.g_menu_item_new(Converter.javaStringToCString(""), Converter.javaStringToCString(actionName)); + OS.g_menu_item_set_submenu(handle, modelHandle); break; case SWT.PUSH: default: - actionHandle = OS.g_simple_action_new(Converter.javaStringToCString(String.valueOf(this.hashCode())), 0); + actionId = String.valueOf(this.hashCode()); + actionHandle = OS.g_simple_action_new(Converter.javaStringToCString(actionId), 0); OS.g_action_map_add_action(parent.actionGroup, actionHandle); - actionName = String.valueOf(parent.hashCode()) + "." + String.valueOf(this.hashCode()); + actionName = String.valueOf(parent.hashCode()) + "." + actionId; handle = OS.g_menu_item_new(null, Converter.javaStringToCString(actionName)); break; } @@ -517,10 +555,6 @@ public boolean getEnabled () { checkWidget(); if (GTK.GTK4) { - if ((style & SWT.CASCADE) != 0) { - return true; - } - return OS.g_action_get_enabled(actionHandle); } else { return GTK.gtk_widget_get_sensitive(handle); @@ -653,6 +687,10 @@ long gtk_activate (long widget) { } } + if (GTK.GTK4 && (style & SWT.CHECK) != 0) { + OS.g_simple_action_set_state(actionHandle, OS.g_variant_new_boolean(!getSelection())); + } + sendSelectionEvent (SWT.Selection); return 0; } @@ -684,7 +722,7 @@ void hookEvents() { super.hookEvents(); if (GTK.GTK4) { - if ((style & SWT.PUSH) != 0 || (style & SWT.RADIO) != 0) { + if ((style & SWT.PUSH) != 0 || (style & SWT.RADIO) != 0 || (style & SWT.CHECK) != 0) { OS.g_signal_connect(actionHandle, OS.activate, display.activateProc, handle); } } else { @@ -739,7 +777,12 @@ void releaseWidget() { super.releaseWidget(); if (GTK.GTK4) { - if (parent.actionGroup != 0 && actionName != null) OS.g_action_map_remove_action(parent.actionGroup, Converter.javaStringToCString(actionName)); + /* + * Remove by actionId, not actionName: GActionMap keys actions by their own + * name, so passing the prefixed detailed name silently removes nothing and + * leaks the action for the lifetime of the parent's action group. + */ + if (parent.actionGroup != 0 && actionId != null) OS.g_action_map_remove_action(parent.actionGroup, Converter.javaStringToCString(actionId)); } else { long accelGroup = getAccelGroup(); if (accelGroup != 0) removeAccelerator(accelGroup); @@ -1144,6 +1187,18 @@ public void setMenu (Menu menu) { OS.g_menu_remove(section.getSectionHandle(), section.getItemPosition(this)); OS.g_menu_insert_item(section.getSectionHandle(), section.getItemPosition(this), handle); + + /* + * If a DROP_DOWN is attached while its parent is already mapped (contributions + * added/rebuilt after the parent was shown, e.g. workspace restore), wire its + * SHOW/HIDE now; otherwise SWT.Show never fires and the submenu appears empty + * (issue #3451). The g_menu calls above already emit "items-changed" on the + * section model, so this is normally redundant; go through modelItemsChanged() + * anyway so both paths re-wire from the same (root) menu. + */ + if (menu != null) { + parent.modelItemsChanged(); + } } else { long accelGroup = getAccelGroup (); if (accelGroup != 0) removeAccelerators (accelGroup); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java index f750fa7b97e..f75676a3650 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java @@ -711,6 +711,13 @@ void createHandle (int index) { if (!searchEnabled ()) { GTK.gtk_tree_view_set_search_column (handle, -1); } + if (GTK.GTK4) { + // Required before TableDropTargetEffect may use + // gtk_tree_view_set_drag_dest_row(), see Tree.createHandle() for details. + long formats = GTK4.gdk_content_formats_builder_free_to_formats(GTK4.gdk_content_formats_builder_new()); + GTK4.gtk_tree_view_enable_model_drag_dest(handle, formats, 0); + GTK4.gdk_content_formats_unref(formats); + } } void createItem (TableColumn column, int index) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolBar.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolBar.java index 4a9782a213c..fb63abb0903 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolBar.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolBar.java @@ -309,8 +309,10 @@ public int getItemCount () { int itemCount = 0; if (GTK.GTK4) { + /* Must match _getItems(): a Menu's GtkPopover is also parented here + * as a native child and would otherwise count as a phantom item. */ for (long child = GTK4.gtk_widget_get_first_child(handle); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { - itemCount++; + if (display.getWidget(child) instanceof ToolItem) itemCount++; } } else { long list = GTK3.gtk_container_get_children (handle); @@ -348,8 +350,9 @@ ToolItem[] _getItems () { ArrayList childrenList = new ArrayList<>(); for (long child = GTK4.gtk_widget_get_first_child(handle); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { Widget childWidget = display.getWidget(child); - if (childWidget != null) { - childrenList.add((ToolItem)childWidget); + /* A Menu's GtkPopover is also a native child here; skip non-ToolItems. */ + if (childWidget instanceof ToolItem toolItem) { + childrenList.add(toolItem); } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolItem.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolItem.java index d02f04f91f5..a9cafd3febf 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/ToolItem.java @@ -910,6 +910,22 @@ int gtk_gesture_press_event(long gesture, int n_press, double x, double y, long sendSelectionEvent(SWT.Selection, e, false); return GTK4.GTK_EVENT_SEQUENCE_CLAIMED; } + /* + * GTK4: Handle right-click (button 3) to fire SWT.MenuDetect on the parent + * ToolBar, replicating the GTK3 gtk3_event_after behavior. The gesture + * coordinates are item-local; translate them to the ToolBar's coordinate + * space and then to screen coordinates before calling parent.showMenu(). + */ + if (n_press == 1 && GTK.gtk_gesture_single_get_current_button(gesture) == 3) { + double[] destX = new double[1]; + double[] destY = new double[1]; + boolean translated = GTK4.gtk_widget_translate_coordinates(handle, parent.handle, x, y, destX, destY); + int barX = translated ? (int) destX[0] : (int) x; + int barY = translated ? (int) destY[0] : (int) y; + Point screenPt = parent.toDisplay(barX, barY); + parent.showMenu(screenPt.x, screenPt.y); + return GTK4.GTK_EVENT_SEQUENCE_CLAIMED; + } return GTK4.GTK_EVENT_SEQUENCE_NONE; } @@ -949,8 +965,8 @@ void hookEvents () { OS.g_signal_connect(motionController, OS.enter, display.enterMotionProc, ENTER); OS.g_signal_connect(motionController, OS.leave, display.leaveProc, LEAVE); - //TODO: event-after long clickController = GTK4.gtk_gesture_click_new(); + GTK.gtk_gesture_single_set_button(clickController, 0); GTK4.gtk_widget_add_controller(handle, clickController); OS.g_signal_connect(clickController, OS.pressed, display.gesturePressReleaseProc, GESTURE_PRESSED); } else { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java index ec7d5ab9c09..7fd132ed41c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java @@ -881,7 +881,23 @@ void createHandle (int index) { GTK.gtk_tree_view_set_search_column (handle, -1); } - if (GTK.GTK4) bindArrowKeyBindings(); + if (GTK.GTK4) { + bindArrowKeyBindings(); + /* + * GTK renders the drop highlight requested through + * gtk_tree_view_set_drag_dest_row() from the private TreeViewDragInfo struct, + * but only gtk_tree_view_enable_model_drag_dest() ever allocates it and the + * snapshot code dereferences it without a NULL check. Driving the highlight + * ourselves, as setInsertMark() and TreeDropTargetEffect do, would therefore + * crash on the next repaint, so allocate the struct up front. The drop target + * GTK installs alongside it gets an empty format list and no actions, which + * makes it reject every drag so that GTK's own tree view drag handlers never + * compete with SWT's DropTarget. + */ + long formats = GTK4.gdk_content_formats_builder_free_to_formats(GTK4.gdk_content_formats_builder_new()); + GTK4.gtk_tree_view_enable_model_drag_dest(handle, formats, 0); + GTK4.gdk_content_formats_unref(formats); + } } /** @@ -2977,7 +2993,15 @@ public void removeAll () { long selection = GTK.gtk_tree_view_get_selection (handle); OS.g_signal_handlers_block_matched (selection, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED); - GTK.gtk_tree_store_clear (modelHandle); + // Disconnect the model from the view before clearing it. + // gtk_tree_store_clear fires cell-data / row-changed callbacks for every + // row it removes. Those callbacks re-enter SWT (cellDataProc -> checkData + // -> getParentItem -> gtk_tree_model_get_path) with iterators that are + // already being freed, causing a SIGSEGV. With no model attached the view + // has nothing to render, so no callbacks are fired during the clear. + GTK.gtk_tree_view_set_model (handle, 0); + GTK.gtk_tree_store_clear (modelHandle); + GTK.gtk_tree_view_set_model (handle, modelHandle); OS.g_signal_handlers_unblock_matched (selection, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index e6b99131a7b..525cf88296d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -1250,11 +1250,15 @@ private Rectangle computeSourceRectangle(ImageHandle imageHandle, Rectangle full * computed to pixels depending on the factor of the full image bounds to the * actual OS handle size that will be used. */ - float scaleFactor = Math.min(1f * imageHandle.width() / fullImageBounds.width, 1f * imageHandle.height() / fullImageBounds.height); - int closestZoomOfHandle = Math.round(scaleFactor * 100); - Rectangle srcPixels = Win32DPIUtils.pointToPixel(drawable, src, closestZoomOfHandle); - - if (closestZoomOfHandle != 100) { + float scaleFactorX = (1f * imageHandle.width()) / fullImageBounds.width; + float scaleFactorY = (1f * imageHandle.height()) / fullImageBounds.height; + int srcXPixels = Math.round(scaleFactorX * src.x); + int srcWidthPixels = Math.round(scaleFactorX * (src.x + src.width)) - srcXPixels; + int srcYPixels = Math.round(scaleFactorY * src.y); + int srcHeightPixels = Math.round(scaleFactorY * (src.y + src.height)) - srcYPixels; + Rectangle srcPixels = new Rectangle(srcXPixels, srcYPixels, srcWidthPixels, srcHeightPixels); + + if (Math.abs(scaleFactorX - 1f) >= 0.01f || Math.abs(scaleFactorY - 1f) >= 0.01f) { /* * This is a HACK! Due to rounding errors at fractional scale factors, * the coordinates may be slightly off. The workaround is to restrict @@ -1263,7 +1267,7 @@ private Rectangle computeSourceRectangle(ImageHandle imageHandle, Rectangle full int errX = srcPixels.x + srcPixels.width - imageHandle.width(); int errY = srcPixels.y + srcPixels.height - imageHandle.height(); if (errX != 0 || errY != 0) { - if (errX <= closestZoomOfHandle / 100 && errY <= closestZoomOfHandle / 100) { + if (errX <= Math.max(1, scaleFactorX) && errY <= Math.max(1, scaleFactorY)) { srcPixels.intersect(new Rectangle(0, 0, imageHandle.width(), imageHandle.height())); } else { SWT.error (SWT.ERROR_INVALID_ARGUMENT); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java index 12a78ed8464..098efadcb9d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java @@ -18,6 +18,7 @@ import java.io.*; import java.util.*; +import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.function.*; @@ -29,6 +30,7 @@ import org.eclipse.swt.internal.gdip.*; import org.eclipse.swt.internal.image.*; import org.eclipse.swt.internal.win32.*; +import org.eclipse.swt.widgets.*; /** * Instances of this class are graphics which have been prepared @@ -173,6 +175,15 @@ Set getAllZooms() { return zoomLevelToImageHandle.keySet(); } + Integer getNearestAvailableZoom(int zoom) { + TreeSet availableZooms = new TreeSet<>(getAllZooms()); + if (availableZooms.contains(zoom)) { + return zoom; + } + Integer higher = availableZooms.higher(zoom); + return higher != null ? higher : availableZooms.lower(zoom); + } + void destroyHandles(Predicate filter) { zoomLevelToImageHandle.entrySet().removeIf(entry -> { if (filter.test(entry.getKey())) { @@ -201,28 +212,39 @@ public String toString() { } private class HandleAtSize { + record TemporaryHandleForZoom(DestroyableImageHandle handle, int zoom) {} + private InternalImageHandle handleContainer = null; - private DestroyableImageHandle temporaryHandleContainer = null; + private TemporaryHandleForZoom temporaryHandleContainer = null; private int requestedWidth = -1; private int requestedHeight = -1; public void destroy() { - if (temporaryHandleContainer != null) { - temporaryHandleContainer.destroy(); - temporaryHandleContainer = null; + TemporaryHandleForZoom previousHandle = reset(); + if (previousHandle != null) { + previousHandle.handle().destroy(); } + } + + private TemporaryHandleForZoom reset() { + TemporaryHandleForZoom previousHandle = temporaryHandleContainer; + temporaryHandleContainer = null; handleContainer = null; requestedWidth = -1; requestedHeight = -1; + return previousHandle; } public ImageHandle refresh(int width, int height) { if (!isReusable(width, height)) { - destroy(); + TemporaryHandleForZoom previousHandle = reset(); requestedWidth = width; requestedHeight = height; handleContainer = createHandleAtExactSize(width, height) - .orElseGet(() -> getOrCreateImageHandleAtClosestSize(width, height)); + .orElseGet(() -> getOrCreateImageHandleAtClosestSize(width, height, previousHandle)); + if (previousHandle != null && previousHandle.handle() != handleContainer) { + previousHandle.handle().destroy(); + } } return handleContainer; } @@ -238,23 +260,58 @@ private boolean isReusable(int width, int height) { private Optional createHandleAtExactSize(int width, int height) { Optional imageData = imageProvider.loadImageDataAtExactSize(width, height); if (imageData.isPresent()) { - temporaryHandleContainer = init(imageData.get(), -1); - return Optional.of(temporaryHandleContainer); + temporaryHandleContainer = new TemporaryHandleForZoom(init(imageData.get(), -1), 0); + return Optional.of(temporaryHandleContainer.handle()); } return Optional.empty(); } - private InternalImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, int heightHint) { + private InternalImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, int heightHint, TemporaryHandleForZoom previousHandle) { Rectangle bounds = getBounds(100); int imageZoomForWidth = 100 * widthHint / bounds.width; int imageZoomForHeight = 100 * heightHint / bounds.height; int imageZoom = DPIUtil.getZoomForAutoscaleProperty(Math.max(imageZoomForWidth, imageZoomForHeight)); + int nearestAvailableZoom = imageProvider.nearestAvailableZoom(imageZoom); + InternalImageHandle bestFittingHandle = getPersistentHandle(imageZoom, nearestAvailableZoom); + if (bestFittingHandle != null) { + return bestFittingHandle; + } + if (previousHandle != null && previousHandle.zoom() == nearestAvailableZoom) { + temporaryHandleContainer = previousHandle; + return previousHandle.handle(); + } + ElementAtZoom imageData = imageProvider.loadImageData(imageZoom); + temporaryHandleContainer = new TemporaryHandleForZoom(init(imageData.element(), -1), imageData.zoom()); + return temporaryHandleContainer.handle(); + } + + private InternalImageHandle getPersistentHandle(int imageZoom, int nearestAvailableZoom) { InternalImageHandle bestFittingHandle = imageHandleManager.get(imageZoom); - if (bestFittingHandle == null) { - ImageData bestFittingImageData = imageProvider.loadImageData(imageZoom).element(); - bestFittingHandle = temporaryHandleContainer = init(bestFittingImageData, -1); + if (bestFittingHandle != null) { + return bestFittingHandle; + } + if (getShellZooms().contains(imageZoom)) { + return getHandleInternal(imageZoom, imageZoom); + } + bestFittingHandle = imageHandleManager.get(nearestAvailableZoom); + if (bestFittingHandle != null) { + return bestFittingHandle; + } + if (nearestAvailableZoom == 100) { + return getHandleInternal(100, 100); } - return bestFittingHandle; + return null; + } + + private Set getShellZooms() { + if (getDevice() instanceof Display display) { + try { + return Arrays.stream(display.getShells()).map(Shell::getZoom).collect(Collectors.toSet()); + } catch (SWTException e) { + return Collections.emptySet(); + } + } + return Collections.emptySet(); } } @@ -957,6 +1014,10 @@ public static long win32_getHandle (Image image, int zoom) { } ImageHandle getHandle (int targetZoom, int nativeZoom) { + return getHandleInternal(targetZoom, nativeZoom); +} + +InternalImageHandle getHandleInternal (int targetZoom, int nativeZoom) { if (isDisposed()) { return null; } @@ -2090,6 +2151,8 @@ protected boolean isPersistentImageHandleRequriedForImageData() { return false; } + abstract int nearestAvailableZoom(int zoom); + /** * Returns image data at the best-fitting available zoom for the given zoom. * The returned data will have a potential gray/disable style applied. @@ -2101,8 +2164,7 @@ protected boolean isPersistentImageHandleRequriedForImageData() { abstract AbstractImageProviderWrapper createCopy(Image image); ElementAtZoom getClosestAvailableImageData(int zoom) { - TreeSet availableZooms = new TreeSet<>(imageHandleManager.getAllZooms()); - int closestZoom = Optional.ofNullable(availableZooms.higher(zoom)).orElse(availableZooms.lower(zoom)); + int closestZoom = imageHandleManager.getNearestAvailableZoom(zoom); ImageData imageData = imageHandleManager.get(closestZoom).getImageData(); return new ElementAtZoom<>(imageData, closestZoom); } @@ -2179,6 +2241,11 @@ protected DestroyableImageHandle newImageHandle(ZoomContext zoomContext) { ImageData resizedData = newImageData (zoomContext.targetZoom()); return newImageHandle(resizedData, zoomContext); } + + @Override + int nearestAvailableZoom(int zoom) { + return zoomForHandle; + } } private abstract class ImageFromImageDataProviderWrapper extends AbstractImageProviderWrapper { @@ -2249,6 +2316,11 @@ protected ElementAtZoom loadImageData(int zoom) { AbstractImageProviderWrapper createCopy(Image image) { return image.new PlainImageDataProviderWrapper(this.imageDataAtBaseZoom); } + + @Override + int nearestAvailableZoom(int zoom) { + return baseZoom; + } } private class MaskedImageDataProviderWrapper extends ImageFromImageDataProviderWrapper { @@ -2281,6 +2353,11 @@ protected ElementAtZoom loadImageData(int zoom) { AbstractImageProviderWrapper createCopy(Image image) { return image.new MaskedImageDataProviderWrapper(this.srcAt100, this.maskAt100); } + + @Override + int nearestAvailableZoom(int zoom) { + return 100; + } } private class ImageDataLoaderStreamProviderWrapper extends ImageFromImageDataProviderWrapper { @@ -2325,6 +2402,14 @@ protected Optional loadImageDataAtExactSize(int targetWidth, int targ } return Optional.empty(); } + + @Override + int nearestAvailableZoom(int zoom) { + if (ImageDataLoader.isDynamicallySizable(new ByteArrayInputStream(this.inputStreamData))) { + return zoom; + } + return FileFormat.DEFAULT_ZOOM; + } } private class PlainImageProviderWrapper extends AbstractImageProviderWrapper { @@ -2388,6 +2473,14 @@ protected ElementAtZoom loadImageData(int zoom) { return getClosestAvailableImageData(zoom); } + @Override + int nearestAvailableZoom(int zoom) { + if (imageHandleManager.isEmpty()) { + return 100; + } + return imageHandleManager.getNearestAvailableZoom(zoom); + } + @Override protected DestroyableImageHandle newImageHandle(ZoomContext zoomContext) { int targetZoom = zoomContext.targetZoom(); @@ -2533,6 +2626,7 @@ private class ImageFileNameProviderWrapper extends BaseImageProviderWrapper loadImageData(int zoom) { ElementAtZoom fileForZoom = DPIUtil.validateAndGetImagePathAtZoom(provider, zoom); @@ -2564,6 +2658,14 @@ protected ElementAtZoom loadImageData(int zoom) { return adaptImageDataIfDisabledOrGray(imageDataAtZoom); } + @Override + int nearestAvailableZoom(int zoom) { + if (provider instanceof ImageDataAtSizeProvider) { + return zoom; + } + return DPIUtil.validateAndGetImagePathAtZoom(provider, zoom).zoom(); + } + @Override public int hashCode() { return Objects.hash(provider, styleFlag); @@ -2807,6 +2909,14 @@ protected Optional loadImageDataAtExactSize(int targetWidth, int targ } return Optional.empty(); } + + @Override + int nearestAvailableZoom(int zoom) { + if (provider instanceof ImageDataAtSizeProvider) { + return zoom; + } + return DPIUtil.validateAndGetImageDataAtZoom (provider, zoom).zoom(); + } } private class ImageGcDrawerWrapper extends DynamicImageProviderWrapper { @@ -2848,6 +2958,11 @@ protected ElementAtZoom loadImageData(int zoom) { return new ElementAtZoom<>(loadImageData(new ZoomContext(zoom)), zoom); } + @Override + int nearestAvailableZoom(int zoom) { + return zoom; + } + private ImageData loadImageData(ZoomContext zoomContext) { currentZoom = zoomContext; int targetZoom = zoomContext.targetZoom(); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java index 05658442854..c32726acb1b 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java @@ -394,14 +394,6 @@ public void put (int index, Image image) { images [index] = image; } -public void remove (int index) { - int count = OS.ImageList_GetImageCount (handle); - if (!(0 <= index && index < count)) return; - zoomToHandle.values().forEach(handle -> OS.ImageList_Remove (handle, index)); - System.arraycopy (images, index + 1, images, index, --count - index); - images [index] = null; -} - public int removeRef() { return --refCount; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java index 75b22c3c684..a9ade374d1d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java @@ -7161,9 +7161,11 @@ private LRESULT positionTooltip(NMHDR hdr, long lParam) { long hwndToolTip = OS.SendMessage(handle, OS.LVM_GETTOOLTIPS, 0, 0); int flags = OS.SWP_NOACTIVATE | OS.SWP_NOZORDER; Rectangle adjustedTooltipBounds = getDisplay().fitRectangleBoundsIntoMonitorWithCursor(toolRect); - OS.SetWindowPos(hwndToolTip, 0, adjustedTooltipBounds.x, adjustedTooltipBounds.y, - adjustedTooltipBounds.width, adjustedTooltipBounds.height, flags); - result = LRESULT.ONE; + if (adjustedTooltipBounds != null) { + OS.SetWindowPos(hwndToolTip, 0, adjustedTooltipBounds.x, adjustedTooltipBounds.y, + adjustedTooltipBounds.width, adjustedTooltipBounds.height, flags); + result = LRESULT.ONE; + } } else if (isCustomToolTip()) { RECT itemRect = getItemBounds(pinfo, item, hDC); NMTTDISPINFO lpnmtdi = new NMTTDISPINFO(); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java index 6e523eef5b3..68e839fcefe 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java @@ -1712,8 +1712,6 @@ record ToolItemData(ToolItem toolItem, TBBUTTON button) { // Remove and re-add all button the let Windows resize the tool bar Stack buttondata = new Stack<>(); for (int i = itemCount - 1; i >= 0; i--) { - TBBUTTON lpButton = new TBBUTTON (); - OS.SendMessage (handle, OS.TB_GETBUTTON, i, lpButton); ToolItem item = toolItems[i]; if ((item.style & SWT.SEPARATOR) != 0 && item.getControl() != null) { // Take note of widths of separators with control, so they can be resized @@ -1721,6 +1719,13 @@ record ToolItemData(ToolItem toolItem, TBBUTTON button) { seperatorWidth[i] = item.getWidth(); } item.notifyListeners(SWT.ZoomChanged, event); + // Capture the button data AFTER handling the zoom change. The zoom refresh + // may update the item's image-list slot (iBitmap), so capturing the button + // beforehand could re-add it with a stale image index, resulting in the + // wrong (or a blank) icon being shown. Reading the button here ensures the + // current, post-refresh image index is preserved. + TBBUTTON lpButton = new TBBUTTON (); + OS.SendMessage (handle, OS.TB_GETBUTTON, i, lpButton); buttondata.push(new ToolItemData(item, lpButton)); OS.SendMessage(handle, OS.TB_DELETEBUTTON, i, 0); } @@ -1738,6 +1743,11 @@ record ToolItemData(ToolItem toolItem, TBBUTTON button) { } } } + // Refresh the image lists so the image list for the correct zoom is used + setImageList(getImageList()); + setDisabledImageList(getDisabledImageList()); + setHotImageList(getHotImageList()); + boolean toolBarEnabled = getEnabled(); for (int i = 0; i < itemCount; i++) { ToolItem item = toolItems[i]; // If the separator is used with a control, we must reset the size to the cached value, @@ -1745,12 +1755,9 @@ record ToolItemData(ToolItem toolItem, TBBUTTON button) { if ((item.style & SWT.SEPARATOR) != 0 && item.getControl() != null) { item.setWidth(seperatorWidth[i]); } + // Make sure the tool item is resized with the new image and font size + toolItems[i].updateImages(toolItems[i].getEnabled() && toolBarEnabled); } - - // Refresh the image lists so the image list for the correct zoom is used - setImageList(getImageList()); - setDisabledImageList(getDisabledImageList()); - setHotImageList(getHotImageList()); OS.SendMessage(handle, OS.TB_AUTOSIZE, 0, 0); clearSizeCache(true); } diff --git a/examples/org.eclipse.swt.examples/META-INF/MANIFEST.MF b/examples/org.eclipse.swt.examples/META-INF/MANIFEST.MF index b7b9968fa3f..c078217c52e 100644 --- a/examples/org.eclipse.swt.examples/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.swt.examples/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin.SWTStandaloneExampleSet.name Bundle-SymbolicName: org.eclipse.swt.examples; singleton:=true -Bundle-Version: 3.109.100.qualifier +Bundle-Version: 3.109.200.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-21 diff --git a/examples/org.eclipse.swt.examples/src/examples_control.properties b/examples/org.eclipse.swt.examples/src/examples_control.properties index 54bc35f9c0d..ad7f2ef4442 100644 --- a/examples/org.eclipse.swt.examples/src/examples_control.properties +++ b/examples/org.eclipse.swt.examples/src/examples_control.properties @@ -232,6 +232,7 @@ Set_Min_Visible = Minimize Set_Max_Visible = Maximize Set_Unselected_Close_Visible = Close on Unselected Tabs Set_Unselected_Image_Visible = Image on Unselected Tabs +Set_MRU_Active = Activate MRU Selection_Foreground_Color = Selection Foreground Color Selection_Background_Color = Selection Background Color Item_Foreground_Color = Item Foreground Color diff --git a/examples/org.eclipse.swt.examples/src/org/eclipse/swt/examples/controlexample/CTabFolderTab.java b/examples/org.eclipse.swt.examples/src/org/eclipse/swt/examples/controlexample/CTabFolderTab.java index 728f988c7d4..74b7450e906 100644 --- a/examples/org.eclipse.swt.examples/src/org/eclipse/swt/examples/controlexample/CTabFolderTab.java +++ b/examples/org.eclipse.swt.examples/src/org/eclipse/swt/examples/controlexample/CTabFolderTab.java @@ -63,7 +63,7 @@ class CTabFolderTab extends Tab { /* Other widgets added to the "Other" group */ Button singleTabButton, imageButton, showMinButton, showMaxButton, - topRightButton, unselectedCloseButton, unselectedImageButton; + topRightButton, unselectedCloseButton, unselectedImageButton, activateMRUButton; ToolBar topRightControl; @@ -233,6 +233,12 @@ void createOtherGroup () { unselectedCloseButton.setText (ControlExample.getResourceString("Set_Unselected_Close_Visible")); unselectedCloseButton.setSelection(true); unselectedCloseButton.addSelectionListener (widgetSelectedAdapter(event -> setUnselectedCloseVisible())); + + activateMRUButton = new Button (otherGroup, SWT.CHECK); + activateMRUButton.setText (ControlExample.getResourceString("Set_MRU_Active")); + activateMRUButton.setSelection(false); + activateMRUButton.addSelectionListener (widgetSelectedAdapter(event -> setMRUActive())); + } /** @@ -412,6 +418,7 @@ void setExampleWidgetState () { setImages(); setMinimizeVisible(); setMaximizeVisible(); + setMRUActive(); setUnselectedCloseVisible(); setUnselectedImageVisible(); setSelectionBackground (); @@ -458,6 +465,13 @@ void setMaximizeVisible () { tabFolder1.setMaximizeVisible(showMaxButton.getSelection ()); setExampleWidgetSize(); } + /** + * Activates/deactivates the MRU setting + */ + void setMRUActive () { + tabFolder1.setMRUVisible(activateMRUButton.getSelection ()); + setExampleWidgetSize(); + } /** * Sets the top right control to a toolbar */ diff --git a/features/org.eclipse.swt.tools.feature/feature.xml b/features/org.eclipse.swt.tools.feature/feature.xml index e931cf31d75..bbd29d9849f 100644 --- a/features/org.eclipse.swt.tools.feature/feature.xml +++ b/features/org.eclipse.swt.tools.feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Issue3329_TreeCrashBug.java b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Issue3329_TreeCrashBug.java new file mode 100644 index 00000000000..4668816fec8 --- /dev/null +++ b/tests/org.eclipse.swt.tests.gtk/ManualTests/org/eclipse/swt/tests/gtk/snippets/Issue3329_TreeCrashBug.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2026 Andrey Loskutov and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrey Loskutov - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.tests.gtk.snippets; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +/** + * Manual reproducer for the Tree.removeAll() crash on Linux GTK with + * SWT.VIRTUAL trees. + * + * STEPS TO REPRODUCE: 1. Run this snippet. 2. Expand any root item so its + * children become visible (this creates Java TreeItem objects with GTK iters + * backed by live GNodes). 3. Click GO. + * + * EXPECTED: Tree content refreshes cleanly. ACTUAL: SIGSEGV inside + * gtk_tree_model_get_path (libgtk-3.so.0). + * + * ROOT CAUSE (mirrors the original JFace crash test provided in the ticket): + * + * Tree.removeAll() calls gtk_tree_store_clear(). GTK fires cellDataProc + * synchronously for rows that become visible as the cursor moves during + * deletion. For an UNCACHED root item (never rendered, so no ID column value in + * the model), Tree._getItem() calls getId() which calls gtk_tree_store_set() to + * assign the ID. That gtk_tree_store_set fires a synchronous "row-changed" + * signal which re-enters cellDataProc a second time for the same row. The inner + * cellDataProc finds the item still uncached and calls checkData() -> + * sendEvent(SWT.SetData). + * + * At this point gtk_tree_store_clear has already removed (and freed the GNodes + * of) the earlier root items and their children. The Java TreeItem objects for + * those freed rows are still alive (release() is called after + * gtk_tree_store_clear returns), but their handle field contains a GtkTreeIter + * whose user_data pointer is now dangling. + * + * JFace's SetData handler calls viewer.replace() which calls + * internalFindItems() -> getTreePathFromItem() -> getParentItem() on every + * existing TreeItem widget. Calling getParentItem() on any item whose GTK row + * has been freed passes the stale GtkTreeIter to gtk_tree_model_get_path(), + * which dereferences the freed GNode -> SIGSEGV at si_addr=0x17 (null-like + * offset into freed memory). + * + * This reproducer replicates that by explicitly calling getParentItem() on the + * previously-collected child items (same as getTreePathFromItem does) inside + * the SetData listener that fires during gtk_tree_store_clear. + */ +public class Issue3329_TreeCrashBug { + + private static final int ROOT_COUNT = 40; + private static final int CHILD_COUNT = 60; + + public static void main(String[] args) { + var display = new Display(); + var shell = new Shell(display); + shell.setLayout(new GridLayout()); + + var button = new Button(shell, SWT.PUSH); + button.setText("GO"); //$NON-NLS-1$ + + var tree = new Tree(shell, SWT.VIRTUAL | SWT.BORDER); + tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + // Collect every child TreeItem as it is rendered during v1 population. + // These objects hold GTK iters (GtkTreeIter.user_data -> GNode). + // After removeAll() frees those GNodes the handles become dangling. + List prevChildren = new ArrayList<>(); + + tree.addListener(SWT.SetData, e -> { + TreeItem item = (TreeItem) e.item; + TreeItem parent = item.getParentItem(); + if (parent == null) { + item.setText("v1_" + e.index); //$NON-NLS-1$ + item.setItemCount(CHILD_COUNT); + } else { + item.setText(parent.getText() + "_" + e.index); //$NON-NLS-1$ + prevChildren.add(item); // remember this child — its GNode will be freed on removeAll() + } + }); + + tree.setItemCount(ROOT_COUNT); + + // GO button: replace the tree content with a new version. + // Crashes if at least one root was expanded (prevChildren is non-empty) + // AND there are uncached roots below the visible viewport. + button.addListener(SWT.Selection, e -> { + for (var l : tree.getListeners(SWT.SetData)) { + tree.removeListener(SWT.SetData, l); + } + + // New SetData listener registered BEFORE removeAll(). + // It fires re-entrantly from inside gtk_tree_store_clear (via the + // cellDataProc -> getId -> gtk_tree_store_set -> row-changed -> cellDataProc + // -> checkData -> SetData chain described in the root-cause comment above). + // + // At the time it fires, the earlier roots and their children have + // already been removed; prevChildren[i].handle contains a freed GNode. + // Calling getParentItem() on those items passes the stale GtkTreeIter + // to gtk_tree_model_get_path() -> SIGSEGV. + // + // This mirrors AbstractTreeViewer.getTreePathFromItem() which walks all + // existing TreeItem widgets via getParentItem() inside internalFindItems(). + tree.addListener(SWT.SetData, ev -> { + for (TreeItem child : prevChildren) { + // child is NOT disposed yet (release() happens after gtk_tree_store_clear), + // but child.handle is a stale GtkTreeIter with a freed GNode pointer. + if (!child.isDisposed()) { + child.getParentItem(); // <-- SIGSEGV on production GTK3 + // G_DISABLE_CHECKS) + } + } + TreeItem item = (TreeItem) ev.item; + TreeItem parent = item.getParentItem(); + if (parent == null) { + item.setText("v2_" + ev.index); //$NON-NLS-1$ + item.setItemCount(CHILD_COUNT); + } else { + item.setText(parent.getText() + "_" + ev.index); //$NON-NLS-1$ + } + }); + + tree.removeAll(); // triggers the crash + tree.setItemCount(ROOT_COUNT); + }); + + shell.setSize(300, 250); // small window -> more uncached (out-of-viewport) roots + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java index ffef5ae21e7..651f6d0fd2d 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java @@ -3045,6 +3045,79 @@ public void test_BrowserFunction_availableOnLoad_concurrentInstances_issue20() { assertTrue(browser2FuncAvailable.get(), "BrowserFunction for second browser missing when page load completed"); } +/** + * Regression test: a disposed BrowserFunction must no longer be available (re-injected) after a + * subsequent navigation. This verifies that deregistration removes the persistent document-created + * script (whose ID is captured asynchronously on the Edge backend). + */ +@Test +public void test_BrowserFunction_disposedFunctionRemovedAfterNavigation() { + BrowserFunction function = new BrowserFunction(browser, "disposableFunc") { + @Override + public Object function(Object[] arguments) { + return "alive"; + } + }; + + AtomicBoolean firstPageLoaded = new AtomicBoolean(false); + ProgressListener firstPageListener = completedAdapter(e -> firstPageLoaded.set(true)); + browser.addProgressListener(firstPageListener); + browser.setText("first page"); + shell.open(); + assertTrue(waitForPassCondition(firstPageLoaded::get), "First page did not load"); + // The function is registered and usable now (this also ensures its registration has settled). + assertEquals("alive", browser.evaluate("return disposableFunc();")); + browser.removeProgressListener(firstPageListener); + + // Dispose the function, then navigate: it must be gone on the new page. + function.dispose(); + AtomicBoolean secondPageLoaded = new AtomicBoolean(false); + browser.addProgressListener(completedAdapter(e -> secondPageLoaded.set(true))); + browser.setText("second page"); + assertTrue(waitForPassCondition(secondPageLoaded::get), "Second page did not load"); + + Object stillDefined = browser.evaluate("return typeof disposableFunc === 'function';"); + assertEquals(Boolean.FALSE, stillDefined, + "A disposed BrowserFunction must not be re-injected on a subsequently loaded page"); +} + +/** + * Regression test: redefining a BrowserFunction with the same name (which deregisters the previous + * one and registers the new one) must take effect and survive navigations - the newest definition + * wins and the previous one is not resurrected. + */ +@Test +public void test_BrowserFunction_redefineSameNameSurvivesNavigation() { + new BrowserFunction(browser, "f") { + @Override + public Object function(Object[] arguments) { + return "v1"; + } + }; + new BrowserFunction(browser, "f") { + @Override + public Object function(Object[] arguments) { + return "v2"; + } + }; + + AtomicBoolean firstPageLoaded = new AtomicBoolean(false); + ProgressListener firstPageListener = completedAdapter(e -> firstPageLoaded.set(true)); + browser.addProgressListener(firstPageListener); + browser.setText("first page"); + shell.open(); + assertTrue(waitForPassCondition(firstPageLoaded::get), "First page did not load"); + assertEquals("v2", browser.evaluate("return f();"), "The most recent definition of 'f' must win"); + browser.removeProgressListener(firstPageListener); + + AtomicBoolean secondPageLoaded = new AtomicBoolean(false); + browser.addProgressListener(completedAdapter(e -> secondPageLoaded.set(true))); + browser.setText("second page"); + assertTrue(waitForPassCondition(secondPageLoaded::get), "Second page did not load"); + assertEquals("v2", browser.evaluate("return f();"), + "The redefined BrowserFunction must survive navigation and the previous definition must not be resurrected"); +} + @Test @Disabled("Too fragile on CI, Display.getDefault().post(event) does not work reliably") public void test_TabTraversalOutOfBrowser() { diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java index ac61c9e2984..66ca7167ca3 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_CTabFolder.java @@ -45,7 +45,9 @@ import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.internal.DPIUtil; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -54,6 +56,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; @@ -69,6 +72,7 @@ * * @see org.eclipse.swt.custom.CTabFolder */ +@SuppressWarnings("restriction") public class Test_org_eclipse_swt_custom_CTabFolder extends Test_org_eclipse_swt_widgets_Composite { @Override @@ -522,6 +526,51 @@ public void test_topRightWrapOverflow() { + "topRight.y=" + topRightBounds.y + " tab.bottom=" + (tabBounds.y + tabBounds.height)); } +/** + * A tab control is rescaled after the folder itself, so the folder must recompute + * its tab height once a tab control reports a zoom change. Test for issue 3456. + */ +@Test +public void test_tabHeightRecomputedOnTabControlZoomChange() { + makeCleanEnvironment(); + shell.setSize(800, 400); + + CTabItem item = new CTabItem(ctabFolder, SWT.NONE); + item.setText("Tab 1"); + ctabFolder.setSelection(0); + + int topRightHeight = 20; + Composite topRight = new Composite(ctabFolder, SWT.NONE); + FixedSizeLayout topRightLayout = new FixedSizeLayout(60, topRightHeight); + topRight.setLayout(topRightLayout); + ctabFolder.setTopRight(topRight, SWT.RIGHT | SWT.WRAP); + + SwtTestUtil.openShell(shell); + processEvents(); + + int defaultTabHeight = ctabFolder.getTabHeight(); + + // A taller tab control makes the folder grow its tab height + topRightLayout.height = defaultTabHeight * 3; + ctabFolder.setTabHeight(SWT.DEFAULT); + processEvents(); + int grownTabHeight = ctabFolder.getTabHeight(); + assertTrue(grownTabHeight > defaultTabHeight, "tab height should grow with the tab control"); + + // Shrinking the tab control alone leaves the tab height stale, which is the state + // the folder ends up in after measuring a control that was not yet rescaled + topRightLayout.height = topRightHeight; + assertEquals(grownTabHeight, ctabFolder.getTabHeight(), "precondition: tab height is stale"); + + Event zoomChanged = new Event(); + zoomChanged.detail = DPIUtil.getDeviceZoom(); + topRight.notifyListeners(SWT.ZoomChanged, zoomChanged); + processEvents(); + + assertEquals(defaultTabHeight, ctabFolder.getTabHeight(), + "tab height should be recomputed after a zoom change of a tab control"); +} + /** * Min/max and chevron icon can appear below tab row. * Test for bug 499215, 533582. @@ -1117,4 +1166,24 @@ public void test_moveItem_errorCases() { "out-of-range to index must be rejected"); } +/** Layout with a preferred size the test can change at will. */ +private static final class FixedSizeLayout extends Layout { + int width; + int height; + + FixedSizeLayout(int width, int height) { + this.width = width; + this.height = height; + } + + @Override + protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + return new Point(wHint == SWT.DEFAULT ? width : wHint, hHint == SWT.DEFAULT ? height : hHint); + } + + @Override + protected void layout(Composite composite, boolean flushCache) { + } +} + } diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java index 29b2292b5ab..5d7449c8e36 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java @@ -382,6 +382,20 @@ public void test_drawImageLorg_eclipse_swt_graphics_ImageIIIIIIII() { images.dispose(); } +/** + * See https://github.com/eclipse-platform/eclipse.platform.swt/issues/3442 + */ +@Test +public void test_drawImage_emptyImage() { + Image emptyImage = new Image(display, IMAGE_SIZE, IMAGE_SIZE); + try { + gc.drawImage(emptyImage, 0, 0, IMAGE_SIZE, IMAGE_SIZE, 0, 0, IMAGE_SIZE / 3, IMAGE_SIZE / 3); + ImageDataTestHelper.assertImageDataEqual(image.getImageData(), emptyImage.getImageData(), image.getImageData()); + } finally { + emptyImage.dispose(); + } +} + @Test public void test_drawImageLorg_eclipse_swt_graphics_ImageIIII() { diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java index 1db0b857eca..5ea3cbc497d 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java @@ -195,6 +195,19 @@ public void test_setEnabledZ() { assertFalse(menuItem.getEnabled()); } +@Test +public void test_setEnabledZ_cascade() { + MenuItem cascadeItem = new MenuItem(menu, SWT.CASCADE); + Menu subMenu = new Menu(shell, SWT.DROP_DOWN); + cascadeItem.setMenu(subMenu); + assertTrue(cascadeItem.getEnabled()); + cascadeItem.setEnabled(false); + assertFalse(cascadeItem.getEnabled()); + cascadeItem.setEnabled(true); + assertTrue(cascadeItem.getEnabled()); + cascadeItem.dispose(); +} + @Tag("gtk4-todo") @Override @Test