mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-06 14:00:55 +03:00
Compare commits
85 Commits
3.6
...
c583a21bf7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c583a21bf7 | ||
|
|
5263125a04 | ||
|
|
056da35758 | ||
|
|
3ccaacfb00 | ||
|
|
bdb7438672 | ||
|
|
299250a710 | ||
|
|
1e2a75a19c | ||
|
|
0d4946230e | ||
|
|
960f9d86c1 | ||
|
|
015645e173 | ||
|
|
36d5685f4c | ||
|
|
ddc8d6e29c | ||
|
|
119b4a922d | ||
|
|
5e4f00f0c8 | ||
|
|
15cbf28a0d | ||
|
|
f8e53c9064 | ||
|
|
b3c9638e47 | ||
|
|
d079741f94 | ||
|
|
c051ad5f72 | ||
|
|
1ed7aeaa45 | ||
|
|
2ac7234c32 | ||
|
|
6f63982054 | ||
|
|
d388158de7 | ||
|
|
e7a766bf8f | ||
|
|
97988e90b4 | ||
|
|
f71dbb2647 | ||
|
|
04ad21b5b6 | ||
|
|
ff722c0b34 | ||
|
|
34b19f00e4 | ||
|
|
286ce15146 | ||
|
|
abfaf86cd5 | ||
|
|
1eee35035d | ||
|
|
bc4c7b25d3 | ||
|
|
0863e289a1 | ||
|
|
5c2d8ba555 | ||
|
|
0f27125107 | ||
|
|
f2882370de | ||
|
|
6715886b24 | ||
|
|
4945378dd3 | ||
|
|
b178450e81 | ||
|
|
e3ffdd3b7c | ||
|
|
5326971287 | ||
|
|
cd34c08dc9 | ||
|
|
edee73e0ea | ||
|
|
54a53fb527 | ||
|
|
62b96fbccd | ||
|
|
42cbb0666d | ||
|
|
1465fbaabc | ||
|
|
5575854e68 | ||
|
|
640f2ba9a2 | ||
|
|
b221fd1894 | ||
|
|
b64ab09b88 | ||
|
|
35e86ba772 | ||
|
|
dade1cba5a | ||
|
|
3e8b213367 | ||
|
|
202a0d159b | ||
|
|
5d247f6269 | ||
|
|
d81bcd5254 | ||
|
|
03e5f8623e | ||
|
|
54d6959533 | ||
|
|
112116556d | ||
|
|
3283cfe22f | ||
|
|
aecb496142 | ||
|
|
1e3e4d7c61 | ||
|
|
b808f6e803 | ||
|
|
f3ca3a001a | ||
|
|
d524536575 | ||
|
|
0a4c01cd40 | ||
|
|
d513ec497b | ||
|
|
07fc190b5f | ||
|
|
078e59a443 | ||
|
|
d49282dfe8 | ||
|
|
c73fd51704 | ||
|
|
251198c66d | ||
|
|
d7462bd424 | ||
|
|
9af7f95197 | ||
|
|
2e16ded5d4 | ||
|
|
91e8d04a9f | ||
|
|
9453d55abd | ||
|
|
641fada6c4 | ||
|
|
a303cd2dec | ||
|
|
2b810addd8 | ||
|
|
63272a03cf | ||
|
|
49a0a83eca | ||
|
|
516bd80702 |
102
.github/workflows/ci.yml
vendored
102
.github/workflows/ci.yml
vendored
@@ -20,7 +20,6 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build (11)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -35,13 +34,35 @@ jobs:
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Check with Error Prone
|
||||
run: ./gradlew errorprone clean
|
||||
|
||||
- name: Build with Gradle
|
||||
# test against
|
||||
# - Java 8 (minimum requirement)
|
||||
# - Java LTS versions (11, 17, ...)
|
||||
# - latest Java version(s)
|
||||
|
||||
- name: Build with Java 8
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
run: ./gradlew build clean -Dtoolchain=8
|
||||
|
||||
- name: Build with Java 17 LTS
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
run: ./gradlew build clean -Dtoolchain=17
|
||||
|
||||
- name: Build with Java 21 LTS
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
run: ./gradlew build clean -Dtoolchain=21
|
||||
|
||||
- name: Build with Java 25 LTS
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
run: ./gradlew build clean -Dtoolchain=25
|
||||
|
||||
|
||||
# build with Java 11 for snapshot
|
||||
|
||||
- name: Build with Java 11 LTS
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Upload artifacts
|
||||
- name: Upload artifacts to GitHub Actions
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlatLaf-build-artifacts
|
||||
@@ -52,69 +73,20 @@ jobs:
|
||||
!**/*-sources.jar
|
||||
|
||||
|
||||
build-on:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# test against
|
||||
# - Java 8 (minimum requirement)
|
||||
# - Java LTS versions (11, 17, ...)
|
||||
# - latest Java version(s)
|
||||
java:
|
||||
- 8
|
||||
- 17 # LTS
|
||||
- 21 # LTS
|
||||
- 23 # latest
|
||||
toolchain: [""]
|
||||
# include:
|
||||
# - java: 21
|
||||
# toolchain: 22 # latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java ${{ matrix.java }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: temurin # Java 8, 11, 17 and 21 are pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
|
||||
|
||||
|
||||
snapshot:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-on
|
||||
if: |
|
||||
github.event_name == 'push' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) &&
|
||||
github.repository == 'JFormDesigner/FlatLaf'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Publish snapshot to oss.sonatype.org
|
||||
- name: Publish snapshot to Sonatype Central
|
||||
if: |
|
||||
github.repository == 'JFormDesigner/FlatLaf' &&
|
||||
github.event_name == 'push' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' ))
|
||||
run: ./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-on
|
||||
needs: build
|
||||
if: |
|
||||
github.event_name == 'push' &&
|
||||
startsWith( github.ref, 'refs/tags/' ) &&
|
||||
@@ -131,10 +103,10 @@ jobs:
|
||||
cache: gradle
|
||||
|
||||
- name: Release a new stable version to Maven Central
|
||||
run: ./gradlew publish :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
|
||||
run: ./gradlew publishToSonatype closeSonatypeStagingRepository :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||
|
||||
|
||||
37
.github/workflows/error-prone.yml
vendored
Normal file
37
.github/workflows/error-prone.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
|
||||
|
||||
name: Error Prone
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- '[0-9]*'
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.*'
|
||||
- '**/.settings/**'
|
||||
- 'flatlaf-core/svg/**'
|
||||
- 'flatlaf-testing/dumps/**'
|
||||
- 'flatlaf-testing/misc/**'
|
||||
- 'images/**'
|
||||
|
||||
jobs:
|
||||
error-prone:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Check with Error Prone
|
||||
run: ./gradlew errorprone clean
|
||||
10
.github/workflows/fonts.yml
vendored
10
.github/workflows/fonts.yml
vendored
@@ -45,18 +45,18 @@ jobs:
|
||||
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build
|
||||
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) ) != true
|
||||
|
||||
- name: Publish snapshot to oss.sonatype.org
|
||||
- name: Publish snapshot to Sonatype Central
|
||||
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:publish -Dorg.gradle.internal.publish.checksums.insecure=true
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
if: github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )
|
||||
|
||||
- name: Release a new stable version to Maven Central
|
||||
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build :flatlaf-fonts-${{ matrix.font }}:publish -Prelease
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )
|
||||
|
||||
79
.github/workflows/natives.yml
vendored
79
.github/workflows/natives.yml
vendored
@@ -33,13 +33,30 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
- name: install libxt-dev
|
||||
- name: apt update (Linux)
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
|
||||
run: sudo apt install libxt-dev
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: install g++-aarch64-linux-gnu
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt install g++-aarch64-linux-gnu
|
||||
- name: install libxt-dev and libgtk-3-dev (Linux)
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
|
||||
run: sudo apt-get install libxt-dev libgtk-3-dev
|
||||
|
||||
# - name: Download libgtk-3.so for arm64 (Linux)
|
||||
# if: matrix.os == 'ubuntu-latest'
|
||||
# working-directory: flatlaf-natives/flatlaf-natives-linux/lib/aarch64
|
||||
# run: |
|
||||
# pwd
|
||||
# ls -l /usr/lib/x86_64-linux-gnu/libgtk*
|
||||
# wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
|
||||
# ls -l
|
||||
# ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
|
||||
# tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
|
||||
# rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
|
||||
# ls -l
|
||||
|
||||
# - name: install g++-aarch64-linux-gnu (Linux)
|
||||
# if: matrix.os == 'ubuntu-latest'
|
||||
# run: sudo apt-get install g++-aarch64-linux-gnu
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v4
|
||||
@@ -53,10 +70,60 @@ jobs:
|
||||
# tar.exe: Couldn't open ~/.gradle/caches/modules-2/modules-2.lock: Permission denied
|
||||
run: ./gradlew build-natives --no-daemon
|
||||
|
||||
- name: Sign Windows DLLs
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: skymatic/code-sign-action@v3
|
||||
with:
|
||||
certificate: '${{ secrets.CODE_SIGN_CERT_BASE64 }}'
|
||||
password: '${{ secrets.CODE_SIGN_CERT_PASSWORD }}'
|
||||
certificatesha1: '${{ secrets.CODE_SIGN_CERT_SHA1 }}'
|
||||
folder: 'flatlaf-core/src/main/resources/com/formdev/flatlaf/natives'
|
||||
|
||||
- name: Sign macOS natives
|
||||
if: matrix.os == 'DISABLED--macos-latest'
|
||||
env:
|
||||
CERT_BASE64: ${{ secrets.CODE_SIGN_CERT_BASE64 }}
|
||||
CERT_PASSWORD: ${{ secrets.CODE_SIGN_CERT_PASSWORD }}
|
||||
CERT_IDENTITY: ${{ secrets.CODE_SIGN_CERT_IDENTITY }}
|
||||
run: |
|
||||
# https://docs.github.com/en/actions/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development
|
||||
# create variables
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/cert.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
|
||||
KEYCHAIN_PASSWORD=$CERT_PASSWORD
|
||||
# decode certificate
|
||||
printenv CERT_BASE64 | base64 --decode > $CERTIFICATE_PATH
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
# import certificate to keychain
|
||||
security import $CERTIFICATE_PATH -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
# set partition list (required for codesign)
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
# add keychain to keychain search list
|
||||
security list-keychains -d user -s $KEYCHAIN_PATH
|
||||
# sign code
|
||||
codesign --sign "$CERT_IDENTITY" --force --verbose=4 --timestamp \
|
||||
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
|
||||
codesign --display --verbose=4 flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
|
||||
# cleanup
|
||||
security delete-keychain $KEYCHAIN_PATH
|
||||
|
||||
- name: Set artifacts pattern
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.os }} in
|
||||
windows-latest) echo "artifactPattern=flatlaf-windows-*.dll" >> $GITHUB_ENV ;;
|
||||
macos-latest) echo "artifactPattern=libflatlaf-macos-*.dylib" >> $GITHUB_ENV ;;
|
||||
ubuntu-latest) echo "artifactPattern=libflatlaf-linux-x86_64.so" >> $GITHUB_ENV ;;
|
||||
ubuntu-24.04-arm) echo "artifactPattern=libflatlaf-linux-arm64.so" >> $GITHUB_ENV ;;
|
||||
esac
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
|
||||
path: |
|
||||
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
|
||||
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/${{ env.artifactPattern }}
|
||||
flatlaf-natives/flatlaf-natives-*/build
|
||||
|
||||
6
.github/workflows/pr-snapshots.yml
vendored
6
.github/workflows/pr-snapshots.yml
vendored
@@ -28,10 +28,10 @@ jobs:
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Publish PR snapshot to oss.sonatype.org
|
||||
- name: Publish PR snapshot to Sonatype Central
|
||||
run: >
|
||||
./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
|
||||
-Pgithub.event.pull_request.number=${{ github.event.pull_request.number }}
|
||||
env:
|
||||
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,6 +1,71 @@
|
||||
FlatLaf Change Log
|
||||
==================
|
||||
|
||||
## 3.7-SNAPSHOT
|
||||
|
||||
#### New features and improvements
|
||||
|
||||
- System File Chooser allows using **operating system file dialogs** in Java
|
||||
Swing applications. (PR #988)
|
||||
|
||||
#### Fixed bugs
|
||||
|
||||
- TextField: Fixed wrong leading/trailing icon placement if border is set to
|
||||
`null`. (issue #1047)
|
||||
- Extras: UI defaults inspector: Exclude inspector window from being blocked by
|
||||
modal dialogs. (issue #1048)
|
||||
- JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: Paint
|
||||
border in button style `TOOLBAR_STYLE` if in selected state. (issue #1045)
|
||||
|
||||
|
||||
## 3.6.2
|
||||
|
||||
#### New features and improvements
|
||||
|
||||
- If using `FlatLaf.registerCustomDefaultsSource( "com.myapp.themes" )` and
|
||||
named Java modules, it is no longer necessary to add `opens com.myapp.themes;`
|
||||
to `module-info.java`. (issue #1026)
|
||||
- Extras: Made animated theme change (class `FlatAnimatedLafChange`) smoother.
|
||||
|
||||
#### Fixed bugs
|
||||
|
||||
- Tree and List: Fixed painting of rounded drop backgrounds. (issue #1023)
|
||||
- Popup: Showing tooltip in inactive window brought that window to front (made
|
||||
it active) and potentially hid the previously active window. (issue #1037)
|
||||
- Popup: No longer reuse popup windows for menus to avoid immediately closing
|
||||
dialogs on ChromeOS. (issue #1029)
|
||||
- macOS: Fixed window "flashing" when switching from a light to a dark theme (or
|
||||
vice versa). Especially when using animated theme changer (see
|
||||
[FlatLaf Extras](flatlaf-extras)).
|
||||
|
||||
#### Incompatibilities
|
||||
|
||||
- FlatLaf properties files are now loaded using the UTF-8 character encoding
|
||||
instead of ISO 8859-1. In usual properties files you will not notice any
|
||||
difference because they use only ASCII characters, but if you've put localized
|
||||
(non-English) texts (e.g. German umlauts) into your properties files, you need
|
||||
to convert them to UTF-8. Properties files created with the FlatLaf Theme
|
||||
Editor already use UTF-8, including in older versions. (issue #1031)
|
||||
|
||||
|
||||
## 3.6.1
|
||||
|
||||
- Extras: Support JSVG 2.0.0. Minimum JSVG version is now 1.6.0. (issue #997)
|
||||
- FlatLaf window decorations (Windows 10/11 only): Improved diagonal window
|
||||
resizing on top-left and top-right window corners. Top window resize area now
|
||||
also covers iconify/maximize/close buttons. (issue #1015)
|
||||
- ToggleButton: Styling `selectedForeground` did not work if `foreground` is
|
||||
also styled. (issue #1017)
|
||||
- JideSplitButton: Fixed updating popup when switching theme. (issue #1000)
|
||||
- IntelliJ Themes: Fixed logging false errors when loading 3rd party
|
||||
`.theme.json` files. (issue #990)
|
||||
- Linux: Popups appeared in wrong position on multi-screen setup if primary
|
||||
display is located below or right to secondary display. (see
|
||||
[NetBeans issue #8532](https://github.com/apache/netbeans/issues/8532))
|
||||
- macOS: Fixed popup flickering after theme change. (issue #1009)
|
||||
- macOS with JetBrains Runtime: Fixed sometimes empty popups. (issue #1019)
|
||||
|
||||
|
||||
## 3.6
|
||||
|
||||
#### New features and improvements
|
||||
|
||||
113
README.md
113
README.md
@@ -35,6 +35,8 @@ Sponsors
|
||||
|
||||
### Current Sponsors
|
||||
|
||||
<a href="https://www.soptim.de/"><img src="https://www.formdev.com/flatlaf/sponsor/soptim.svg" width="200" alt="SOPTIM" title="SOPTIM - your expert in software solutions for the energy industry"></a>
|
||||
|
||||
<a href="https://exocharts.com/"><img src="https://www.formdev.com/flatlaf/sponsor/Exocharts.png" width="200" alt="Exocharts" title="Exocharts - Professional Grade OrderFlow"></a>
|
||||
|
||||
<!-- [](https://www.formdev.com/flatlaf/sponsor/) -->
|
||||
@@ -72,7 +74,7 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf)
|
||||
|
||||
See also
|
||||
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
|
||||
@@ -83,10 +85,10 @@ application.
|
||||
### Snapshots
|
||||
|
||||
FlatLaf snapshot binaries are available on
|
||||
[Sonatype OSSRH](https://oss.sonatype.org/content/repositories/snapshots/com/formdev/flatlaf/).
|
||||
[Sonatype Central](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/formdev/flatlaf/).
|
||||
To access the latest snapshot, change the FlatLaf version in your dependencies
|
||||
to `<version>-SNAPSHOT` (e.g. `0.27-SNAPSHOT`) and add the repository
|
||||
`https://oss.sonatype.org/content/repositories/snapshots/` to your build (see
|
||||
to `<version>-SNAPSHOT` (e.g. `3.7-SNAPSHOT`) and add the repository
|
||||
`https://central.sonatype.com/repository/maven-snapshots/` to your build (see
|
||||
[Maven](https://maven.apache.org/guides/mini/guide-multiple-repositories.html)
|
||||
and
|
||||
[Gradle](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:declaring_custom_repository)
|
||||
@@ -184,11 +186,18 @@ Applications using FlatLaf
|
||||
relational data browsing tool
|
||||
-  [MagicPlot](https://magicplot.com/) (**commercial**) -
|
||||
Software for nonlinear fitting, plotting and data analysis
|
||||
-  [Constellation](https://www.constellation-app.com/) -
|
||||
Data Visualization and Analytics (based on NetBeans platform)
|
||||
- 
|
||||
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
|
||||
- [Constellation](https://www.constellation-app.com/) - Data Visualization and
|
||||
Analytics (based on NetBeans platform)
|
||||
- [Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
|
||||
client
|
||||
- 
|
||||
[RedisFront](https://github.com/dromara/RedisFront/blob/master/README_EN.md) -
|
||||
Cross-platform redis GUI
|
||||
- 
|
||||
[Zettelkasten](https://github.com/Zettelkasten-Team/Zettelkasten) - knowledge
|
||||
management tool
|
||||
-  [QStudio](https://www.timestored.com/qstudio/) - free
|
||||
SQL editor
|
||||
|
||||
### Security
|
||||
|
||||
@@ -197,11 +206,9 @@ Applications using FlatLaf
|
||||
- 
|
||||
[Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro)
|
||||
(**commercial**) - the leading software for web security testing
|
||||
- 
|
||||
[Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
|
||||
- [Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
|
||||
reverse engineering (SRE) framework
|
||||
-  [jadx](https://github.com/skylot/jadx) - Dex to Java
|
||||
decompiler
|
||||
- [jadx](https://github.com/skylot/jadx) - Dex to Java decompiler
|
||||
- [BurpCustomizer](https://github.com/CoreyD97/BurpCustomizer) - adds more
|
||||
FlatLaf themes to Burp Suite
|
||||
- [Total Validator](https://www.totalvalidator.com/) (**commercial**) - checks
|
||||
@@ -213,13 +220,12 @@ Applications using FlatLaf
|
||||
|
||||
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib)
|
||||
- [KeyStore Explorer](https://keystore-explorer.org/)
|
||||
- 
|
||||
[muCommander](https://github.com/mucommander/mucommander) - lightweight
|
||||
- [muCommander](https://github.com/mucommander/mucommander) - lightweight
|
||||
cross-platform file manager
|
||||
-  [Guiffy](https://www.guiffy.com/) (**commercial**) -
|
||||
advanced cross-platform Diff/Merge
|
||||
-  [HashGarten](https://github.com/jonelo/HashGarten) -
|
||||
cross-platform Swing GUI for Jacksum
|
||||
- [Guiffy](https://www.guiffy.com/) (**commercial**) - advanced cross-platform
|
||||
Diff/Merge
|
||||
- [HashGarten](https://github.com/jonelo/HashGarten) - cross-platform Swing GUI
|
||||
for Jacksum
|
||||
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
|
||||
IDE for Pseudo-Assembler
|
||||
- [Linotte](https://github.com/cpc6128/LangageLinotte) - French programming
|
||||
@@ -228,6 +234,12 @@ Applications using FlatLaf
|
||||
systems development platform
|
||||
-  [Consulo](https://github.com/consulo/consulo) - open
|
||||
source cross-platform multi-language IDE (Java, .NET, JS, etc)
|
||||
- [Convertigo](https://github.com/convertigo/convertigo) - low code & no code
|
||||
mobile & web platform
|
||||
-  [EduMIPS64](https://github.com/EduMIPS64/edumips64) -
|
||||
visual MIPS64 CPU simulator
|
||||
-  [Launch4j](https://launch4j.sourceforge.net/) -
|
||||
cross-platform Java executable wrapper
|
||||
|
||||
### Electrical
|
||||
|
||||
@@ -235,6 +247,11 @@ Applications using FlatLaf
|
||||
designing, simulating and explaining digital circuits
|
||||
- [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) -
|
||||
Digital logic design tool and simulator
|
||||
-  [OpenPnP](https://github.com/openpnp/openpnp) - SMT
|
||||
Pick and Place Hardware and Software
|
||||
- 
|
||||
[TrainControl](https://github.com/bob123456678/TrainControl) - control Marklin
|
||||
/ Trix / DCC digital model train layout
|
||||
- [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) -
|
||||
for plotters, especially the wall-hanging polargraph
|
||||
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI
|
||||
@@ -249,8 +266,10 @@ Applications using FlatLaf
|
||||
|
||||
-  [jAlbum](https://jalbum.net/) (**commercial**) -
|
||||
creates photo album websites
|
||||
-  [MediathekView](https://mediathekview.de/) - search in
|
||||
media libraries of various German broadcasters
|
||||
- [MediathekView](https://mediathekview.de/) - search in media libraries of
|
||||
various German broadcasters
|
||||
-  [Pixelitor](https://github.com/lbalazscs/Pixelitor) -
|
||||
image editor
|
||||
- [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit
|
||||
sequences
|
||||
- [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a
|
||||
@@ -266,19 +285,31 @@ Applications using FlatLaf
|
||||
from any webnovel and lightnovel site
|
||||
- [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with
|
||||
ease
|
||||
-  [Nortantis](https://jandjheydorn.com/nortantis) -
|
||||
fantasy map generator and editor
|
||||
|
||||
### Modelling
|
||||
### Modelling / Planning
|
||||
|
||||
-  [Astah](https://astah.net/) (**commercial**) - create
|
||||
UML, ER Diagram, Flowchart, Data Flow Diagram, Requirement Diagram, SysML
|
||||
diagrams and more
|
||||
-  [OpenRocket](https://github.com/openrocket/openrocket) -
|
||||
model-rocketry aerodynamics and trajectory simulation software
|
||||
- 
|
||||
[Warteschlangensimulator](https://github.com/A-Herzog/Warteschlangensimulator) -
|
||||
discrete-event stochastic simulator
|
||||
-  [Gephi](https://github.com/gephi/gephi) - the Open
|
||||
Graph Viz Platform
|
||||
- [Astah](https://astah.net/) (**commercial**) - create UML, ER Diagram,
|
||||
Flowchart, Data Flow Diagram, Requirement Diagram, SysML diagrams and more
|
||||
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
|
||||
Application System
|
||||
-  [StarPlan](https://www.progotec.de/) (**commercial**) -
|
||||
die Stundenplan Software für Bildungseinrichtungen
|
||||
-  [SSPlot](https://github.com/babaissarkar/ssplot) -
|
||||
plotting utility for plotting CSV data
|
||||
|
||||
### Documents
|
||||
|
||||
-  [Big Faceless (BFO) PDF Viewer](https://bfo.com/)
|
||||
(**commercial**) - Swing PDF Viewer
|
||||
- [Big Faceless (BFO) PDF Viewer](https://bfo.com/) (**commercial**) - Swing PDF
|
||||
Viewer
|
||||
- [PDF Studio](https://www.qoppa.com/pdfstudio/) (**commercial**) - create,
|
||||
review and edit PDF documents
|
||||
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) (**commercial**)
|
||||
@@ -296,6 +327,9 @@ Applications using FlatLaf
|
||||
|
||||
### Business / Legal
|
||||
|
||||
-  
|
||||
[Lisheane ERP](https://www.lisheane.ch/) (**commercial**) - backoffice
|
||||
applikation
|
||||
- 
|
||||
[j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
|
||||
-  [Jeyla Studio](https://www.jeylastudio.com/) -
|
||||
@@ -312,20 +346,20 @@ Applications using FlatLaf
|
||||
|
||||
### Messaging
|
||||
|
||||
-  [Spark](https://github.com/igniterealtime/Spark) -
|
||||
cross-platform IM client optimized for businesses and organizations
|
||||
-  [Chatty](https://github.com/chatty/chatty) - Twitch
|
||||
Chat Client
|
||||
- [Spark](https://github.com/igniterealtime/Spark) - cross-platform IM client
|
||||
optimized for businesses and organizations
|
||||
- [Chatty](https://github.com/chatty/chatty) - Twitch Chat Client
|
||||
|
||||
### Gaming
|
||||
|
||||
-  
|
||||
[BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon
|
||||
-  [MCreator](https://github.com/MCreator/MCreator) -
|
||||
software used to make Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons,
|
||||
and data packs without programming knowledge
|
||||
-  [MapTool](https://github.com/RPTools/maptool) - virtual
|
||||
Tabletop for playing role-playing games
|
||||
-  [BGBlitz](https://www.bgblitz.com/)
|
||||
(**commercial**) - professional Backgammon
|
||||
-  [josé](https://peteschaefer.github.io/jose/) - a
|
||||
graphical chess tool
|
||||
-  [MCreator](https://github.com/MCreator/MCreator) - make
|
||||
Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons, and data packs
|
||||
- [MapTool](https://github.com/RPTools/maptool) - virtual Tabletop for playing
|
||||
role-playing games
|
||||
- [MegaMek](https://github.com/MegaMek/megamek),
|
||||
[MegaMekLab](https://github.com/MegaMek/megameklab) and
|
||||
[MekHQ](https://github.com/MegaMek/mekhq) - a sci-fi tabletop BattleTech
|
||||
@@ -337,8 +371,7 @@ Applications using FlatLaf
|
||||
|
||||
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
|
||||
OSHI, to view information about the system and hardware
|
||||
- 
|
||||
[Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
|
||||
- [Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
|
||||
GUI for monitoring and managing various aspects of a Linux system
|
||||
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
|
||||
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
|
||||
@@ -347,6 +380,8 @@ Applications using FlatLaf
|
||||
easy
|
||||
- [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb
|
||||
and fastboot commands easier to use
|
||||
-  [Termora](https://github.com/TermoraDev/termora) -
|
||||
Terminal emulator and SSH client
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import net.ltgt.gradle.errorprone.errorprone
|
||||
|
||||
group = "com.formdev"
|
||||
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
|
||||
|
||||
// for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT'
|
||||
@@ -42,13 +43,12 @@ println( "----------------------------------------------------------------------
|
||||
println( "FlatLaf Version: ${version}" )
|
||||
println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" )
|
||||
println( "Java ${System.getProperty( "java.version" )}" )
|
||||
val toolchainJavaVersion = System.getProperty( "toolchain" )
|
||||
if( !toolchainJavaVersion.isNullOrEmpty() )
|
||||
println( "Java toolchain ${toolchainJavaVersion}" )
|
||||
println( "Java toolchain ${System.getProperty( "toolchain", "11" )}" )
|
||||
println()
|
||||
|
||||
|
||||
plugins {
|
||||
alias( libs.plugins.gradle.nexus.publish.plugin )
|
||||
alias( libs.plugins.errorprone ) apply false
|
||||
}
|
||||
|
||||
@@ -143,3 +143,20 @@ allprojects {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
// see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/
|
||||
nexusUrl = uri( "https://ossrh-staging-api.central.sonatype.com/service/local/" )
|
||||
snapshotRepositoryUrl = uri( "https://central.sonatype.com/repository/maven-snapshots/" )
|
||||
|
||||
// get from gradle.properties
|
||||
val sonatypeUsername: String? by project
|
||||
val sonatypePassword: String? by project
|
||||
|
||||
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
|
||||
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ plugins {
|
||||
java
|
||||
}
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
if( java.toolchain.languageVersion.get().asInt() >= 9 ) {
|
||||
sourceSets {
|
||||
create( "java9" ) {
|
||||
java {
|
||||
|
||||
@@ -29,7 +29,7 @@ plugins {
|
||||
java
|
||||
}
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
if( java.toolchain.languageVersion.get().asInt() >= 9 ) {
|
||||
sourceSets {
|
||||
create( "module-info" ) {
|
||||
java {
|
||||
|
||||
@@ -86,24 +86,26 @@ publishing {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
repositories {
|
||||
maven {
|
||||
name = "OSSRH"
|
||||
name = "MavenCentral"
|
||||
|
||||
val releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
|
||||
val snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
|
||||
val releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
|
||||
val snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/"
|
||||
url = uri( if( rootProject.hasProperty( "release" ) ) releasesRepoUrl else snapshotsRepoUrl )
|
||||
|
||||
credentials {
|
||||
// get from gradle.properties
|
||||
val ossrhUsername: String? by project
|
||||
val ossrhPassword: String? by project
|
||||
val sonatypeUsername: String? by project
|
||||
val sonatypePassword: String? by project
|
||||
|
||||
username = System.getenv( "OSSRH_USERNAME" ) ?: ossrhUsername
|
||||
password = System.getenv( "OSSRH_PASSWORD" ) ?: ossrhPassword
|
||||
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
|
||||
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
signing {
|
||||
|
||||
@@ -18,9 +18,6 @@ plugins {
|
||||
java
|
||||
}
|
||||
|
||||
val toolchainJavaVersion = System.getProperty( "toolchain" )
|
||||
if( !toolchainJavaVersion.isNullOrEmpty() ) {
|
||||
java.toolchain {
|
||||
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
|
||||
}
|
||||
java.toolchain {
|
||||
languageVersion = JavaLanguageVersion.of( System.getProperty( "toolchain", "11" ) )
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ tasks {
|
||||
useJUnitPlatform()
|
||||
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
|
||||
if( java.toolchain.languageVersion.get().asInt() >= 9 )
|
||||
jvmArgs( listOf( "--add-opens", "java.desktop/javax.swing.plaf.basic=ALL-UNNAMED" ) )
|
||||
}
|
||||
|
||||
@@ -113,6 +113,10 @@ tasks {
|
||||
"version" to version,
|
||||
"release" to "1.8", // Java version
|
||||
"failonerror" to "true" )
|
||||
|
||||
"fixcrlf"(
|
||||
"file" to "${project.name}-sigtest.txt",
|
||||
"eol" to "lf" )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#Signature file v4.1
|
||||
#Version 3.6
|
||||
#Version 3.6.2
|
||||
|
||||
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
|
||||
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
|
||||
@@ -297,6 +297,7 @@ CLSS public abstract interface com.formdev.flatlaf.FlatSystemProperties
|
||||
fld public final static java.lang.String ANIMATION = "flatlaf.animation"
|
||||
fld public final static java.lang.String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded"
|
||||
fld public final static java.lang.String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"
|
||||
fld public final static java.lang.String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow"
|
||||
fld public final static java.lang.String UI_SCALE = "flatlaf.uiScale"
|
||||
fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown"
|
||||
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
|
||||
@@ -324,7 +325,7 @@ meth public static boolean setup(java.io.InputStream)
|
||||
meth public static com.formdev.flatlaf.FlatLaf createLaf(com.formdev.flatlaf.IntelliJTheme)
|
||||
meth public static com.formdev.flatlaf.FlatLaf createLaf(java.io.InputStream) throws java.io.IOException
|
||||
supr java.lang.Object
|
||||
hfds checkboxDuplicateColors,checkboxKeyMapping,jsonColors,jsonIcons,jsonUI,namedColors,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludes,uiKeyInverseMapping,uiKeyMapping
|
||||
hfds checkboxDuplicateColors,checkboxKeyMapping,jsonColors,jsonIcons,jsonUI,namedColors,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludesContains,uiKeyExcludesStartsWith,uiKeyInverseMapping,uiKeyMapping
|
||||
|
||||
CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf
|
||||
outer com.formdev.flatlaf.IntelliJTheme
|
||||
|
||||
@@ -911,8 +911,7 @@ public abstract class FlatLaf
|
||||
* <p>
|
||||
* Invoke this method before setting the look and feel.
|
||||
* <p>
|
||||
* If using Java modules, the package must be opened in {@code module-info.java}.
|
||||
* Otherwise, use {@link #registerCustomDefaultsSource(URL)}.
|
||||
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
|
||||
*
|
||||
* @param packageName a package name (e.g. "com.myapp.resources")
|
||||
*/
|
||||
@@ -959,9 +958,9 @@ public abstract class FlatLaf
|
||||
* <p>
|
||||
* See {@link #registerCustomDefaultsSource(String)} for details.
|
||||
* <p>
|
||||
* This method is useful if using Java modules and the package containing the properties files
|
||||
* is not opened in {@code module-info.java}.
|
||||
* E.g. {@code FlatLaf.registerCustomDefaultsSource( MyApp.class.getResource( "/com/myapp/themes/" ) )}.
|
||||
* <p>
|
||||
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
|
||||
*
|
||||
* @param packageUrl a package URL
|
||||
* @since 2
|
||||
|
||||
@@ -20,6 +20,9 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
@@ -62,8 +65,8 @@ public class FlatPropertiesLaf
|
||||
throws IOException
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
try( InputStream in2 = in ) {
|
||||
properties.load( in2 );
|
||||
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
|
||||
properties.load( reader );
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package com.formdev.flatlaf;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingUtilities;
|
||||
import com.formdev.flatlaf.util.SystemFileChooser;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
|
||||
/**
|
||||
@@ -147,6 +150,25 @@ public interface FlatSystemProperties
|
||||
*/
|
||||
String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder";
|
||||
|
||||
/**
|
||||
* Species whether popup windows may be reused without (temporary) hiding them.
|
||||
* E.g. if "moving" a tooltip to follow the mouse pointer, normally it is necessary
|
||||
* to hide the tooltip and show it again at the new location, which causes some
|
||||
* flicker with heavy-weight popup windows that FlatLaf uses on all platforms.
|
||||
* <p>
|
||||
* If {@code true}, hiding popup window is deferred for an event cycle,
|
||||
* which allows reusing still visible popup window and avoids flicker when "moving" the popup.
|
||||
* <p>
|
||||
* Note that {@link JPopupMenu} popup windows (menus and combobox lists) are newer reused.
|
||||
* <p>
|
||||
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
|
||||
* <strong>Default</strong> {@code true}
|
||||
*
|
||||
* @since 3.6.2
|
||||
*/
|
||||
String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow";
|
||||
|
||||
|
||||
/**
|
||||
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
|
||||
* <p>
|
||||
@@ -226,6 +248,17 @@ public interface FlatSystemProperties
|
||||
*/
|
||||
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
|
||||
|
||||
/**
|
||||
* Specifies whether {@link SystemFileChooser} uses operating system file dialogs.
|
||||
* If set to {@code false}, the {@link JFileChooser} is used instead.
|
||||
* <p>
|
||||
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
|
||||
* <strong>Default</strong> {@code true}
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser";
|
||||
|
||||
/**
|
||||
* Checks whether a system property is set and returns {@code true} if its value
|
||||
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.
|
||||
|
||||
@@ -413,22 +413,37 @@ public class IntelliJTheme
|
||||
key.equals( "Tree.rightChildIndent" ) )
|
||||
return; // ignore
|
||||
|
||||
// ignore icons
|
||||
if( key.endsWith( "Icon" ) )
|
||||
return; // ignore
|
||||
|
||||
// map keys
|
||||
key = uiKeyMapping.getOrDefault( key, key );
|
||||
if( key.isEmpty() )
|
||||
return; // ignore key
|
||||
|
||||
// exclude properties
|
||||
// exclude properties (1st level)
|
||||
int dot = key.indexOf( '.' );
|
||||
if( dot > 0 && uiKeyExcludes.contains( key.substring( 0, dot + 1 ) ) )
|
||||
if( dot > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot + 1 ) ) )
|
||||
return;
|
||||
|
||||
// exclude properties (2st level)
|
||||
int dot2 = (dot > 0) ? key.indexOf( '.', dot + 1 ) : -1;
|
||||
if( dot2 > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot2 + 1 ) ) )
|
||||
return;
|
||||
|
||||
// exclude properties (contains)
|
||||
for( String s : uiKeyExcludesContains ) {
|
||||
if( key.contains( s ) )
|
||||
return;
|
||||
}
|
||||
|
||||
if( uiKeyDoNotOverride.contains( key ) && jsonUIKeys.contains( key ) )
|
||||
return;
|
||||
|
||||
jsonUIKeys.add( key );
|
||||
|
||||
String valueStr = value.toString();
|
||||
String valueStr = value.toString().trim();
|
||||
|
||||
// map named colors
|
||||
String uiValue = namedColors.get( valueStr );
|
||||
@@ -657,7 +672,8 @@ public class IntelliJTheme
|
||||
}
|
||||
}
|
||||
|
||||
private static final Set<String> uiKeyExcludes;
|
||||
private static final Set<String> uiKeyExcludesStartsWith;
|
||||
private static final String[] uiKeyExcludesContains;
|
||||
private static final Set<String> uiKeyDoNotOverride;
|
||||
/** Rename UI default keys (key --> value). */
|
||||
private static final Map<String, String> uiKeyMapping = new HashMap<>();
|
||||
@@ -669,7 +685,7 @@ public class IntelliJTheme
|
||||
|
||||
static {
|
||||
// IntelliJ UI properties that are not used in FlatLaf
|
||||
uiKeyExcludes = new HashSet<>( Arrays.asList(
|
||||
uiKeyExcludesStartsWith = new HashSet<>( Arrays.asList(
|
||||
"ActionButton.", "ActionToolbar.", "ActionsList.", "AppInspector.", "AssignedMnemonic.", "Autocomplete.",
|
||||
"AvailableMnemonic.",
|
||||
"Badge.", "Banner.", "BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
|
||||
@@ -703,14 +719,24 @@ public class IntelliJTheme
|
||||
// possible typos in .theme.json files
|
||||
"Checkbox.", "Toolbar.", "Tooltip.", "UiDesigner.", "link."
|
||||
) );
|
||||
uiKeyExcludesContains = new String[] {
|
||||
".darcula."
|
||||
};
|
||||
|
||||
uiKeyDoNotOverride = new HashSet<>( Arrays.asList(
|
||||
"TabbedPane.selectedForeground"
|
||||
) );
|
||||
|
||||
// "*."
|
||||
uiKeyMapping.put( "*.fontFace", "" ); // ignore (used in OnePauintxi themes)
|
||||
uiKeyMapping.put( "*.fontSize", "" ); // ignore (used in OnePauintxi themes)
|
||||
|
||||
// Button
|
||||
uiKeyMapping.put( "Button.minimumSize", "" ); // ignore (used in Material Theme UI Lite)
|
||||
|
||||
// CheckBox.iconSize
|
||||
uiKeyMapping.put( "CheckBox.iconSize", "" ); // ignore (used in Rider themes)
|
||||
|
||||
// ComboBox
|
||||
uiKeyMapping.put( "ComboBox.background", "" ); // ignore
|
||||
uiKeyMapping.put( "ComboBox.buttonBackground", "" ); // ignore
|
||||
@@ -751,6 +777,9 @@ public class IntelliJTheme
|
||||
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
|
||||
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
|
||||
|
||||
// RadioButton
|
||||
uiKeyMapping.put( "RadioButton.iconSize", "" ); // ignore (used in Rider themes)
|
||||
|
||||
// ScrollBar
|
||||
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
|
||||
uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" );
|
||||
|
||||
@@ -70,6 +70,8 @@ class LinuxPopupMenuCanceler
|
||||
}
|
||||
|
||||
private void addWindowListeners( MenuElement selected ) {
|
||||
removeWindowListeners();
|
||||
|
||||
// see BasicPopupMenuUI.MouseGrabber.grabWindow()
|
||||
Component invoker = selected.getComponent();
|
||||
if( invoker instanceof JPopupMenu )
|
||||
|
||||
@@ -25,12 +25,15 @@ import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StreamTokenizer;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -112,6 +115,14 @@ class UIDefaultsLoader
|
||||
Set<String> specialPrefixes = FlatLaf.getUIKeySpecialPrefixes();
|
||||
|
||||
return new Properties() {
|
||||
@Override
|
||||
public void load( InputStream in ) throws IOException {
|
||||
// use UTF-8 to load properties file
|
||||
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
|
||||
super.load( reader );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Object put( Object k, Object value ) {
|
||||
// process key prefixes (while loading properties files)
|
||||
@@ -198,16 +209,46 @@ class UIDefaultsLoader
|
||||
if( classLoader == null )
|
||||
classLoader = FlatLaf.class.getClassLoader();
|
||||
|
||||
boolean found = false;
|
||||
for( Class<?> lafClass : lafClasses ) {
|
||||
String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
|
||||
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
|
||||
if( in != null )
|
||||
if( in != null ) {
|
||||
properties.load( in );
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback for named Java modules
|
||||
if( !found ) {
|
||||
// Get package URL using ClassLoader.getResource(...) because this works
|
||||
// also in named Java modules, even without opening the package in module-info.java.
|
||||
// This extra step is necessary because ClassLoader.getResource("<package>/<file>.properties")
|
||||
// does not work for named Java modules.
|
||||
URL url = classLoader.getResource( packageName );
|
||||
if( url == null )
|
||||
continue;
|
||||
|
||||
String packageUrl = url.toExternalForm();
|
||||
if( !packageUrl.endsWith( "/" ) )
|
||||
packageUrl = packageUrl.concat( "/" );
|
||||
|
||||
for( Class<?> lafClass : lafClasses ) {
|
||||
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
|
||||
|
||||
try( InputStream in = propertiesUrl.openStream() ) {
|
||||
properties.load( in );
|
||||
} catch( FileNotFoundException ex ) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if( source instanceof URL ) {
|
||||
// load from package URL
|
||||
URL packageUrl = (URL) source;
|
||||
String packageUrl = ((URL)source).toExternalForm();
|
||||
if( !packageUrl.endsWith( "/" ) )
|
||||
packageUrl = packageUrl.concat( "/" );
|
||||
for( Class<?> lafClass : lafClasses ) {
|
||||
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
|
||||
|
||||
@@ -649,22 +690,26 @@ class UIDefaultsLoader
|
||||
if( value.indexOf( ',' ) >= 0 ) {
|
||||
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
|
||||
List<String> parts = splitFunctionParams( value, ',' );
|
||||
Insets insets = parseInsets( value );
|
||||
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
|
||||
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
|
||||
: null;
|
||||
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
|
||||
? parseFloat( parts.get( 5 ) )
|
||||
: 1f;
|
||||
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
|
||||
? parseInteger( parts.get( 6 ) )
|
||||
: -1;
|
||||
try {
|
||||
Insets insets = parseInsets( value );
|
||||
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
|
||||
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
|
||||
: null;
|
||||
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
|
||||
? parseFloat( parts.get( 5 ) )
|
||||
: 1f;
|
||||
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
|
||||
? parseInteger( parts.get( 6 ) )
|
||||
: -1;
|
||||
|
||||
return (LazyValue) t -> {
|
||||
return (lineColor != null || arc > 0)
|
||||
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
|
||||
: new FlatEmptyBorder( insets );
|
||||
};
|
||||
return (LazyValue) t -> {
|
||||
return (lineColor != null || arc > 0)
|
||||
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
|
||||
: new FlatEmptyBorder( insets );
|
||||
};
|
||||
} catch( RuntimeException ex ) {
|
||||
throw new IllegalArgumentException( "invalid border '" + value + "' (" + ex.getMessage() + ")" );
|
||||
}
|
||||
} else
|
||||
return parseInstance( value, resolver, addonClassLoaders );
|
||||
}
|
||||
@@ -735,7 +780,7 @@ class UIDefaultsLoader
|
||||
Integer.parseInt( numbers.get( 1 ) ),
|
||||
Integer.parseInt( numbers.get( 2 ) ),
|
||||
Integer.parseInt( numbers.get( 3 ) ) );
|
||||
} catch( NumberFormatException ex ) {
|
||||
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
|
||||
throw new IllegalArgumentException( "invalid insets '" + value + "'" );
|
||||
}
|
||||
}
|
||||
@@ -748,7 +793,7 @@ class UIDefaultsLoader
|
||||
return new DimensionUIResource(
|
||||
Integer.parseInt( numbers.get( 0 ) ),
|
||||
Integer.parseInt( numbers.get( 1 ) ) );
|
||||
} catch( NumberFormatException ex ) {
|
||||
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
|
||||
throw new IllegalArgumentException( "invalid size '" + value + "'" );
|
||||
}
|
||||
}
|
||||
@@ -1379,17 +1424,17 @@ class UIDefaultsLoader
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch( IOException ex ) {
|
||||
throw new IllegalArgumentException( ex );
|
||||
} catch( RuntimeException | IOException ex ) {
|
||||
throw new IllegalArgumentException( "invalid font '" + value + "' (" + ex.getMessage() + ")" );
|
||||
}
|
||||
|
||||
if( style != -1 && styleChange != 0 )
|
||||
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
|
||||
throw new IllegalArgumentException( "invalid font '" + value + "': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" );
|
||||
if( styleChange != 0 ) {
|
||||
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
|
||||
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
|
||||
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+bold' and '-bold'" );
|
||||
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
|
||||
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
|
||||
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+italic' and '-italic'" );
|
||||
}
|
||||
|
||||
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
|
||||
@@ -1529,7 +1574,7 @@ class UIDefaultsLoader
|
||||
return (LazyValue) t -> {
|
||||
return new GrayFilter( brightness, contrast, alpha );
|
||||
};
|
||||
} catch( NumberFormatException ex ) {
|
||||
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
|
||||
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +381,12 @@ public class FlatButtonUI
|
||||
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
|
||||
}
|
||||
|
||||
// update internal values; otherwise isCustomBackground() and isCustomForeground() would return wrong results
|
||||
switch( key ) {
|
||||
case "background": background = (Color) value; break;
|
||||
case "foreground": foreground = (Color) value; break;
|
||||
}
|
||||
|
||||
if( "iconTextGap".equals( key ) && value instanceof Integer )
|
||||
value = UIScale.scale( (Integer) value );
|
||||
|
||||
|
||||
@@ -882,7 +882,7 @@ public class FlatComboBoxUI
|
||||
GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
|
||||
if( gc != null ) {
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
|
||||
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
|
||||
displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right );
|
||||
} else {
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
|
||||
@@ -302,6 +302,7 @@ public class FlatListUI
|
||||
ListModel dataModel, ListSelectionModel selModel, int leadIndex )
|
||||
{
|
||||
boolean isSelected = selModel.isSelectedIndex( row );
|
||||
boolean isDropRow = isDropRow( row );
|
||||
|
||||
// paint alternating rows
|
||||
if( alternateRowColor != null && row % 2 != 0 &&
|
||||
@@ -335,7 +336,7 @@ public class FlatListUI
|
||||
}
|
||||
|
||||
// rounded selection or selection insets
|
||||
if( isSelected &&
|
||||
if( (isSelected || isDropRow) &&
|
||||
!isFileList && // rounded selection is not supported for file list
|
||||
(rendererComponent instanceof DefaultListCellRenderer ||
|
||||
rendererComponent instanceof BasicComboBoxRenderer) &&
|
||||
@@ -376,7 +377,22 @@ public class FlatListUI
|
||||
this.getColor() == rendererComponent.getBackground() )
|
||||
{
|
||||
inPaintSelection = true;
|
||||
paintCellSelection( this, row, x, y, width, height );
|
||||
if( isDropRow ) {
|
||||
// for rounded drop background, it is necessary to first
|
||||
// paint selection background because may be not rounded on some corners
|
||||
if( isSelected ) {
|
||||
Color oldColor = getColor();
|
||||
setColor( list.getSelectionBackground() );
|
||||
paintCellSelection( this, row, x, y, width, height );
|
||||
setColor( oldColor );
|
||||
}
|
||||
|
||||
// paint drop background
|
||||
float arc = UIScale.scale( selectionArc / 2f );
|
||||
FlatUIUtils.paintSelection( this, x, y, width, height,
|
||||
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
|
||||
} else
|
||||
paintCellSelection( this, row, x, y, width, height );
|
||||
inPaintSelection = false;
|
||||
} else
|
||||
super.fillRect( x, y, width, height );
|
||||
@@ -475,4 +491,15 @@ public class FlatListUI
|
||||
FlatListUI ui = (FlatListUI) list.getUI();
|
||||
ui.paintCellSelection( g, row, x, y, width, height );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether dropping on a row.
|
||||
* See DefaultListCellRenderer.getListCellRendererComponent().
|
||||
*/
|
||||
private boolean isDropRow( int row ) {
|
||||
JList.DropLocation dropLocation = list.getDropLocation();
|
||||
return dropLocation != null &&
|
||||
!dropLocation.isInsert() &&
|
||||
dropLocation.getIndex() == row;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
|
||||
/**
|
||||
@@ -34,9 +35,9 @@ import com.formdev.flatlaf.util.SystemInfo;
|
||||
* @author Karl Tauber
|
||||
* @since 2.5
|
||||
*/
|
||||
class FlatNativeLinuxLibrary
|
||||
public class FlatNativeLinuxLibrary
|
||||
{
|
||||
private static int API_VERSION_LINUX = 3001;
|
||||
private static int API_VERSION_LINUX = 3002;
|
||||
|
||||
/**
|
||||
* Checks whether native library is loaded/available.
|
||||
@@ -44,10 +45,13 @@ class FlatNativeLinuxLibrary
|
||||
* <b>Note</b>: It is required to invoke this method before invoking any other
|
||||
* method of this class. Otherwise, the native library may not be loaded.
|
||||
*/
|
||||
static boolean isLoaded() {
|
||||
public static boolean isLoaded() {
|
||||
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
|
||||
}
|
||||
|
||||
|
||||
//---- X Window System ----------------------------------------------------
|
||||
|
||||
// direction for _NET_WM_MOVERESIZE message
|
||||
// see https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html
|
||||
static final int
|
||||
@@ -124,4 +128,109 @@ class FlatNativeLinuxLibrary
|
||||
return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) ||
|
||||
(window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated());
|
||||
}
|
||||
|
||||
|
||||
//---- GTK ----------------------------------------------------------------
|
||||
|
||||
private static Boolean isGtk3Available;
|
||||
|
||||
/**
|
||||
* Checks whether GTK 3 is available.
|
||||
* Use this before invoking any native method that uses GTK.
|
||||
* Otherwise the app may terminate immediately if GTK is not installed.
|
||||
* <p>
|
||||
* This works because Java uses {@code dlopen(RTLD_LAZY)} to load JNI libraries,
|
||||
* which only resolves symbols as the code that references them is executed.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public static boolean isGtk3Available() {
|
||||
if( isGtk3Available == null )
|
||||
isGtk3Available = isLibAvailable( "libgtk-3.so.0" ) || isLibAvailable( "libgtk-3.so" );
|
||||
return isGtk3Available;
|
||||
}
|
||||
|
||||
private native static boolean isLibAvailable( String libname );
|
||||
|
||||
/**
|
||||
* https://docs.gtk.org/gtk3/iface.FileChooser.html#properties
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public static final int
|
||||
FC_select_folder = 1 << 0,
|
||||
FC_select_multiple = 1 << 1,
|
||||
FC_show_hidden = 1 << 2,
|
||||
FC_local_only = 1 << 3, // default
|
||||
FC_do_overwrite_confirmation = 1 << 4, // GTK 3 only; removed and always-on in GTK 4
|
||||
FC_create_folders = 1 << 5; // default for Save
|
||||
|
||||
/**
|
||||
* Shows the Linux/GTK system file dialog
|
||||
* <a href="https://docs.gtk.org/gtk3/class.FileChooserDialog.html">GtkFileChooserDialog</a>.
|
||||
* <p>
|
||||
* Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}.
|
||||
* Otherwise uses {@code GTK_FILE_CHOOSER_ACTION_OPEN} if parameter {@code open} is {@code true},
|
||||
* or {@code GTK_FILE_CHOOSER_ACTION_SAVE} if {@code false}.
|
||||
* <p>
|
||||
* <b>Note:</b> This method blocks the current thread until the user closes
|
||||
* the file dialog. It is highly recommended to invoke it from a new thread
|
||||
* to avoid blocking the AWT event dispatching thread.
|
||||
*
|
||||
* @param owner the owner of the file dialog; or {@code null}
|
||||
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
|
||||
* @param title text displayed in dialog title; or {@code null}
|
||||
* @param okButtonLabel text displayed in default button; or {@code null}.
|
||||
* Use '_' for mnemonics (e.g. "_Choose")
|
||||
* Use '__' for '_' character (e.g. "Choose__and__Quit").
|
||||
* @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null}
|
||||
* @param currentFolder current directory shown in the dialog; or {@code null}
|
||||
* @param optionsSet options to set; see {@code FOS_*} constants
|
||||
* @param optionsClear options to clear; see {@code FOS_*} constants
|
||||
* @param callback approve callback; or {@code null}
|
||||
* @param fileTypeIndex the file type that appears as selected (zero-based)
|
||||
* @param fileTypes file types that the dialog can open or save.
|
||||
* Two or more strings and {@code null} are required for each filter.
|
||||
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
|
||||
* Subsequent strings are the filter patterns (e.g. "*.txt" or "*").
|
||||
* {@code null} is required to mark end of filter.
|
||||
* @return file path(s) that the user selected; an empty array if canceled;
|
||||
* or {@code null} on failures (no dialog shown)
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static String[] showFileChooser( Window owner, boolean open,
|
||||
String title, String okButtonLabel, String currentName, String currentFolder,
|
||||
int optionsSet, int optionsClear, FileChooserCallback callback,
|
||||
int fileTypeIndex, String... fileTypes );
|
||||
|
||||
/** @since 3.7 */
|
||||
public interface FileChooserCallback {
|
||||
boolean approve( String[] files, long hwndFileDialog );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a GTK message box
|
||||
* <a href="https://docs.gtk.org/gtk3/class.MessageDialog.html">GtkMessageDialog</a>.
|
||||
* <p>
|
||||
* For use in {@link FileChooserCallback} only.
|
||||
*
|
||||
* @param hwndParent the parent of the message box
|
||||
* @param messageType type of message being displayed:
|
||||
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE},
|
||||
* {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or
|
||||
* {@link JOptionPane#PLAIN_MESSAGE}
|
||||
* @param primaryText primary text; if the dialog has a secondary text,
|
||||
* this will appear as title in a larger bold font
|
||||
* @param secondaryText secondary text; shown below of primary text; or {@code null}
|
||||
* @param defaultButton index of the default button, which can be pressed using ENTER key
|
||||
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown.
|
||||
* Use '_' for mnemonics (e.g. "_Choose")
|
||||
* Use '__' for '_' character (e.g. "Choose__and__Quit").
|
||||
* @return index of pressed button; or -1 for ESC key
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static int showMessageDialog( long hwndParent, int messageType,
|
||||
String primaryText, String secondaryText, int defaultButton, String... buttons );
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import javax.swing.JOptionPane;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
|
||||
/**
|
||||
@@ -44,7 +45,7 @@ import com.formdev.flatlaf.util.SystemInfo;
|
||||
*/
|
||||
public class FlatNativeMacLibrary
|
||||
{
|
||||
private static int API_VERSION_MACOS = 2001;
|
||||
private static int API_VERSION_MACOS = 2002;
|
||||
|
||||
/**
|
||||
* Checks whether native library is loaded/available.
|
||||
@@ -68,4 +69,88 @@ public class FlatNativeMacLibrary
|
||||
/** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window );
|
||||
/** @since 3.4 */ public native static boolean isWindowFullScreen( Window window );
|
||||
/** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window );
|
||||
|
||||
|
||||
/** @since 3.7 */
|
||||
public static final int
|
||||
// NSOpenPanel (extends NSSavePanel)
|
||||
FC_canChooseFiles = 1 << 0, // default
|
||||
FC_canChooseDirectories = 1 << 1,
|
||||
FC_resolvesAliases = 1 << 2, // default
|
||||
FC_allowsMultipleSelection = 1 << 3,
|
||||
FC_accessoryViewDisclosed = 1 << 4,
|
||||
// NSSavePanel
|
||||
FC_showsTagField = 1 << 8, // default for Save
|
||||
FC_canCreateDirectories = 1 << 9, // default for Save
|
||||
FC_canSelectHiddenExtension = 1 << 10,
|
||||
FC_showsHiddenFiles = 1 << 11,
|
||||
FC_extensionHidden = 1 << 12,
|
||||
FC_allowsOtherFileTypes = 1 << 13,
|
||||
FC_treatsFilePackagesAsDirectories = 1 << 14,
|
||||
// custom
|
||||
FC_showSingleFilterField = 1 << 24;
|
||||
|
||||
/**
|
||||
* Shows the macOS system file dialogs
|
||||
* <a href="https://developer.apple.com/documentation/appkit/nsopenpanel?language=objc">NSOpenPanel</a> or
|
||||
* <a href="https://developer.apple.com/documentation/appkit/nssavepanel?language=objc">NSSavePanel</a>.
|
||||
* <p>
|
||||
* <b>Note:</b> This method blocks the current thread until the user closes
|
||||
* the file dialog. It is highly recommended to invoke it from a new thread
|
||||
* to avoid blocking the AWT event dispatching thread.
|
||||
*
|
||||
* @param owner the owner of the file dialog; or {@code null}
|
||||
* @param dark appearance of the file dialog: {@code 1} = dark, {@code 0} = light, {@code -1} = default
|
||||
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
|
||||
* @param title text displayed at top of save dialog (not used in open dialog); or {@code null}
|
||||
* @param prompt text displayed in default button; or {@code null}
|
||||
* @param message text displayed at top of open/save dialogs; or {@code null}
|
||||
* @param filterFieldLabel text displayed in front of the filter combobox; or {@code null}
|
||||
* @param nameFieldLabel text displayed in front of the filename text field in save dialog (not used in open dialog); or {@code null}
|
||||
* @param nameFieldStringValue user-editable filename currently shown in the name field in save dialog (not used in open dialog); or {@code null}
|
||||
* @param directoryURL current directory shown in the dialog; or {@code null}
|
||||
* @param optionsSet options to set; see {@code FC_*} constants
|
||||
* @param optionsClear options to clear; see {@code FC_*} constants
|
||||
* @param fileTypeIndex the file type that appears as selected (zero-based)
|
||||
* @param fileTypes file types that the dialog can open or save.
|
||||
* Two or more strings and {@code null} are required for each filter.
|
||||
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
|
||||
* Subsequent strings are the filter patterns (e.g. "txt" or "*").
|
||||
* {@code null} is required to mark end of filter.
|
||||
* @return file path(s) that the user selected; an empty array if canceled;
|
||||
* or {@code null} on failures (no dialog shown)
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static String[] showFileChooser( Window owner, int dark, boolean open,
|
||||
String title, String prompt, String message, String filterFieldLabel,
|
||||
String nameFieldLabel, String nameFieldStringValue, String directoryURL,
|
||||
int optionsSet, int optionsClear, FileChooserCallback callback,
|
||||
int fileTypeIndex, String... fileTypes );
|
||||
|
||||
/** @since 3.7 */
|
||||
public interface FileChooserCallback {
|
||||
boolean approve( String[] files, long hwndFileDialog );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a macOS alert
|
||||
* <a href="https://developer.apple.com/documentation/appkit/nsalert?language=objc">NSAlert</a>.
|
||||
* <p>
|
||||
* For use in {@link FileChooserCallback} only.
|
||||
*
|
||||
* @param hwndParent the parent of the message box
|
||||
* @param alertStyle type of alert being displayed:
|
||||
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE} or
|
||||
* {@link JOptionPane#WARNING_MESSAGE}
|
||||
* @param messageText main message of the alert
|
||||
* @param informativeText additional information about the alert; shown below of main message; or {@code null}
|
||||
* @param defaultButton index of the default button, which can be pressed using ENTER key
|
||||
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown
|
||||
* @return index of pressed button
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static int showMessageDialog( long hwndParent, int alertStyle,
|
||||
String messageText, String informativeText, int defaultButton, String... buttons );
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Window;
|
||||
import javax.swing.JOptionPane;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,7 @@ import com.formdev.flatlaf.util.SystemInfo;
|
||||
*/
|
||||
public class FlatNativeWindowsLibrary
|
||||
{
|
||||
private static int API_VERSION_WINDOWS = 1001;
|
||||
private static int API_VERSION_WINDOWS = 1002;
|
||||
|
||||
private static long osBuildNumber = Long.MIN_VALUE;
|
||||
|
||||
@@ -158,4 +159,125 @@ public class FlatNativeWindowsLibrary
|
||||
// DwmSetWindowAttribute() expects COLORREF as attribute value, which is defined as DWORD
|
||||
return dwmSetWindowAttributeDWORD( hwnd, attribute, rgb );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FILEOPENDIALOGOPTIONS
|
||||
* see https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public static final int
|
||||
FOS_OVERWRITEPROMPT = 0x2, // default for Save
|
||||
FOS_STRICTFILETYPES = 0x4,
|
||||
FOS_NOCHANGEDIR = 0x8, // default
|
||||
FOS_PICKFOLDERS = 0x20,
|
||||
FOS_FORCEFILESYSTEM = 0x40,
|
||||
FOS_ALLNONSTORAGEITEMS = 0x80,
|
||||
FOS_NOVALIDATE = 0x100,
|
||||
FOS_ALLOWMULTISELECT = 0x200,
|
||||
FOS_PATHMUSTEXIST = 0x800, // default
|
||||
FOS_FILEMUSTEXIST = 0x1000, // default for Open
|
||||
FOS_CREATEPROMPT = 0x2000,
|
||||
FOS_SHAREAWARE = 0x4000,
|
||||
FOS_NOREADONLYRETURN = 0x8000, // default for Save
|
||||
FOS_NOTESTFILECREATE = 0x10000,
|
||||
FOS_HIDEMRUPLACES = 0x20000,
|
||||
FOS_HIDEPINNEDPLACES = 0x40000,
|
||||
FOS_NODEREFERENCELINKS = 0x100000,
|
||||
FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
|
||||
FOS_DONTADDTORECENT = 0x2000000,
|
||||
FOS_FORCESHOWHIDDEN = 0x10000000,
|
||||
FOS_DEFAULTNOMINIMODE = 0x20000000,
|
||||
FOS_FORCEPREVIEWPANEON = 0x40000000,
|
||||
FOS_SUPPORTSTREAMABLEITEMS = 0x80000000;
|
||||
|
||||
/**
|
||||
* Shows the Windows system
|
||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog">file dialogs</a>
|
||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifileopendialog">IFileOpenDialog</a> or
|
||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesavedialog">IFileSaveDialog</a>.
|
||||
* <p>
|
||||
* <b>Note:</b> This method blocks the current thread until the user closes
|
||||
* the file dialog. It is highly recommended to invoke it from a new thread
|
||||
* to avoid blocking the AWT event dispatching thread.
|
||||
*
|
||||
* @param owner the owner of the file dialog; or {@code null}
|
||||
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
|
||||
* @param title text displayed in dialog title; or {@code null}
|
||||
* @param okButtonLabel text displayed in default button; or {@code null}.
|
||||
* Use '&' for mnemonics (e.g. "&Choose").
|
||||
* Use '&&' for '&' character (e.g. "Choose && Quit").
|
||||
* @param fileNameLabel text displayed in front of the filename text field; or {@code null}
|
||||
* @param fileName user-editable filename currently shown in the filename field; or {@code null}
|
||||
* @param folder current directory shown in the dialog; or {@code null}
|
||||
* @param saveAsItem file to be used as the initial entry in a Save As dialog; or {@code null}.
|
||||
* File name is shown in filename text field, folder is selected in view.
|
||||
* To be used for saving files that already exist. For new files use {@code fileName}.
|
||||
* @param defaultFolder folder used as a default if there is not a recently used folder value available; or {@code null}.
|
||||
* Windows somewhere stores default folder on a per-app basis.
|
||||
* So this is probably used only once when the app opens a file dialog for first time.
|
||||
* @param defaultExtension default extension to be added to file name in save dialog; or {@code null}
|
||||
* @param optionsSet options to set; see {@code FOS_*} constants
|
||||
* @param optionsClear options to clear; see {@code FOS_*} constants
|
||||
* @param callback approve callback; or {@code null}
|
||||
* @param fileTypeIndex the file type that appears as selected (zero-based)
|
||||
* @param fileTypes file types that the dialog can open or save.
|
||||
* Pairs of strings are required for each filter.
|
||||
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
|
||||
* Second string is the filter pattern (e.g. "*.txt", "*.exe;*.dll" or "*.*").
|
||||
* @return file path(s) that the user selected; an empty array if canceled;
|
||||
* or {@code null} on failures (no dialog shown)
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static String[] showFileChooser( Window owner, boolean open,
|
||||
String title, String okButtonLabel, String fileNameLabel, String fileName,
|
||||
String folder, String saveAsItem, String defaultFolder, String defaultExtension,
|
||||
int optionsSet, int optionsClear, FileChooserCallback callback,
|
||||
int fileTypeIndex, String... fileTypes );
|
||||
|
||||
/** @since 3.7 */
|
||||
public interface FileChooserCallback {
|
||||
boolean approve( String[] files, long hwndFileDialog );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a modal Windows message dialog.
|
||||
* <p>
|
||||
* For use in {@link FileChooserCallback} only.
|
||||
*
|
||||
* @param hwndParent the parent of the message box
|
||||
* @param messageType type of message being displayed:
|
||||
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE},
|
||||
* {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or
|
||||
* {@link JOptionPane#PLAIN_MESSAGE}
|
||||
* @param title dialog box title; or {@code null} to use title from parent window
|
||||
* @param text message to be displayed
|
||||
* @param defaultButton index of the default button, which can be pressed using ENTER key
|
||||
* @param buttons texts of the buttons.
|
||||
* Use '&' for mnemonics (e.g. "&Choose").
|
||||
* Use '&&' for '&' character (e.g. "Choose && Quit").
|
||||
* @return index of pressed button; or -1 for ESC key
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static int showMessageDialog( long hwndParent, int messageType,
|
||||
String title, String text, int defaultButton, String... buttons );
|
||||
|
||||
/**
|
||||
* Shows a Windows message box
|
||||
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox">MessageBox</a>.
|
||||
* <p>
|
||||
* For use in {@link FileChooserCallback} only.
|
||||
*
|
||||
* @param hwndParent the parent of the message box
|
||||
* @param text message to be displayed
|
||||
* @param caption dialog box title
|
||||
* @param type see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#parameters">MessageBox parameter uType</a>
|
||||
* @return see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#return-value">MessageBox Return value</a>
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public native static int showMessageBox( long hwndParent, String text, String caption, int type );
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.awt.Panel;
|
||||
import java.awt.Point;
|
||||
import java.awt.PointerInfo;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
@@ -312,7 +311,7 @@ public class FlatPopupFactory
|
||||
return null;
|
||||
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
|
||||
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
|
||||
int screenTop = screenBounds.y + screenInsets.top;
|
||||
|
||||
// place tooltip above mouse location if there is enough space
|
||||
@@ -547,7 +546,15 @@ public class FlatPopupFactory
|
||||
int x = popupWindow.getX();
|
||||
int y = popupWindow.getY();
|
||||
|
||||
popup.show();
|
||||
if( !popupWindow.isVisible() )
|
||||
popup.show();
|
||||
else {
|
||||
// if the popup window is already visible (because it is reused),
|
||||
// do not invoke Popup.show() because this would invoke Window.toFront(),
|
||||
// which may have the side effect that an inactive owner window
|
||||
// would be also moved to front and maybe hide previously active window
|
||||
popupWindow.pack();
|
||||
}
|
||||
|
||||
// restore popup window location if it has changed
|
||||
// (probably scaled when screens use different scale factors)
|
||||
@@ -619,7 +626,30 @@ public class FlatPopupFactory
|
||||
|
||||
void showImpl() {
|
||||
if( delegate != null ) {
|
||||
showPopupAndFixLocation( delegate, popupWindow );
|
||||
// On macOS and Linux, the empty popup window is shown in popup.show()
|
||||
// (in peer.setVisible(true) invoked from Component.show()),
|
||||
// but the popup content is painted later via repaint manager.
|
||||
// This may cause some flicker, especially during JVM warm-up or
|
||||
// when running JVM in interpreter mode (option -Xint).
|
||||
// To reduce flicker, immediately paint popup content as soon as popup window becomes visible.
|
||||
// This also fixes a problem with JetBrainsRuntime JVM, where sometimes the popups were empty.
|
||||
if( (SystemInfo.isMacOS || SystemInfo.isLinux) && popupWindow instanceof JWindow ) {
|
||||
HierarchyListener l = e -> {
|
||||
if( e.getID() == HierarchyEvent.HIERARCHY_CHANGED &&
|
||||
(e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 )
|
||||
{
|
||||
((JWindow)popupWindow).getRootPane().paintImmediately(
|
||||
0, 0, popupWindow.getWidth(), popupWindow.getHeight() );
|
||||
}
|
||||
};
|
||||
popupWindow.addHierarchyListener( l );
|
||||
try {
|
||||
showPopupAndFixLocation( delegate, popupWindow );
|
||||
} finally {
|
||||
popupWindow.removeHierarchyListener( l );
|
||||
}
|
||||
} else
|
||||
showPopupAndFixLocation( delegate, popupWindow );
|
||||
|
||||
// increase tooltip size if necessary because it may be too small on HiDPI screens
|
||||
// https://bugs.openjdk.java.net/browse/JDK-8213535
|
||||
@@ -646,8 +676,11 @@ public class FlatPopupFactory
|
||||
return;
|
||||
disposed = true;
|
||||
|
||||
// immediately hide non-heavy weight popups or combobox popups
|
||||
if( !(popupWindow instanceof JWindow) || contents instanceof BasicComboPopup ) {
|
||||
// immediately hide non-heavy weight popups, popup menus and combobox popups
|
||||
// of if system property is false
|
||||
if( !(popupWindow instanceof JWindow) || contents instanceof JPopupMenu ||
|
||||
!FlatSystemProperties.getBoolean( FlatSystemProperties.REUSE_VISIBLE_POPUP_WINDOW, true ) )
|
||||
{
|
||||
hideImpl();
|
||||
return;
|
||||
}
|
||||
@@ -678,6 +711,21 @@ public class FlatPopupFactory
|
||||
// restore background so that it can not affect other LaFs (when switching)
|
||||
// because popup windows are cached and reused
|
||||
popupWindow.setBackground( oldPopupWindowBackground );
|
||||
|
||||
// On macOS, popupWindow.setBackground(...) invoked from constructor,
|
||||
// has no affect if the popup window peer (a NSWindow) is already created,
|
||||
// which is the case when reusing a cached popup window
|
||||
// (see class PopupFactory.HeavyWeightPopup).
|
||||
// This may result in flicker when e.g. showing a popup in a light theme,
|
||||
// then switching to a dark theme and again showing a popup,
|
||||
// because the underling NSWindow still has a light background,
|
||||
// which may be shown shortly before the actual dark popup content is shown.
|
||||
// To fix this, dispose the popup window, which disposes the NSWindow.
|
||||
// The AWT popup window stays in the popup cache, but when reusing it later,
|
||||
// a new peer and a new NSWindow is created and gets the correct background.
|
||||
if( SystemInfo.isMacOS )
|
||||
popupWindow.dispose();
|
||||
|
||||
popupWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public class FlatPopupMenuUI
|
||||
// (always subtract screen insets because there is no API to detect whether
|
||||
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
|
||||
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
|
||||
return FlatUIUtils.subtractInsets( screenBounds, screenInsets );
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
@@ -153,8 +154,28 @@ public class FlatRootPaneUI
|
||||
Container parent = c.getParent();
|
||||
if( parent instanceof JFrame || parent instanceof JDialog ) {
|
||||
Color background = parent.getBackground();
|
||||
if( background == null || background instanceof UIResource )
|
||||
parent.setBackground( UIManager.getColor( "control" ) );
|
||||
if( background == null || background instanceof UIResource ) {
|
||||
if( SystemInfo.isMacOS ) {
|
||||
// Setting window background on macOS immediately fills the whole window
|
||||
// with that color, and slightly delayed, the Swing repaint manager
|
||||
// repaints the actual window content. This results in some flashing
|
||||
// when switching from a light to a dark theme (or vice versa).
|
||||
// --> delay setting window background and immediately repaint window content
|
||||
Runnable r = () -> {
|
||||
parent.setBackground( UIManager.getColor( "control" ) );
|
||||
c.paintImmediately( 0, 0, c.getWidth(), c.getHeight() );
|
||||
};
|
||||
|
||||
// for class FlatAnimatedLafChange:
|
||||
// if animated Laf change is in progress, set background color when
|
||||
// animation has finished to avoid/reduce flashing
|
||||
if( c.getClientProperty( "FlatLaf.internal.animatedLafChange" ) != null )
|
||||
c.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", r );
|
||||
else
|
||||
EventQueue.invokeLater( r );
|
||||
} else
|
||||
parent.setBackground( UIManager.getColor( "control" ) );
|
||||
}
|
||||
}
|
||||
|
||||
macClearBackgroundForTranslucentWindow( c );
|
||||
|
||||
@@ -39,6 +39,7 @@ import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.ActionMap;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
@@ -989,12 +990,14 @@ public class FlatTableUI
|
||||
|
||||
FlatBooleanRenderer() {
|
||||
setHorizontalAlignment( SwingConstants.CENTER );
|
||||
setIcon( new FlatCheckBoxIcon() {
|
||||
Icon icon = new FlatCheckBoxIcon() {
|
||||
@Override
|
||||
protected boolean isSelected( Component c ) {
|
||||
return selected;
|
||||
}
|
||||
} );
|
||||
};
|
||||
setIcon( icon );
|
||||
setDisabledIcon( icon );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -628,7 +628,7 @@ debug*/
|
||||
/**
|
||||
* Returns the rectangle used to paint leading and trailing icons.
|
||||
* It invokes {@code super.getVisibleEditorRect()} and reduces left and/or
|
||||
* right margin if the text field has leading or trailing icons or components.
|
||||
* right insets if the text field has leading or trailing icons or components.
|
||||
* Also, the preferred widths of leading and trailing components are removed.
|
||||
*
|
||||
* @since 2
|
||||
@@ -660,24 +660,24 @@ debug*/
|
||||
}
|
||||
}
|
||||
|
||||
// if a leading/trailing icons (or components) are shown, then the left/right margins are reduced
|
||||
// to the top margin, which places the icon nicely centered on left/right side
|
||||
// if a leading/trailing icons (or components) are shown, then the left/right insets are reduced
|
||||
// to the top inset, which places the icon nicely centered on left/right side
|
||||
if( leftVisible || (ltr ? hasLeadingIcon() : hasTrailingIcon()) ) {
|
||||
// reduce left margin
|
||||
Insets margin = getComponent().getMargin();
|
||||
int newLeftMargin = Math.min( margin.left, margin.top );
|
||||
if( newLeftMargin < margin.left ) {
|
||||
int diff = scale( margin.left - newLeftMargin );
|
||||
// reduce left inset
|
||||
Insets insets = getComponent().getInsets();
|
||||
int newLeftInset = Math.min( insets.left, insets.top );
|
||||
if( newLeftInset < insets.left ) {
|
||||
int diff = insets.left - newLeftInset;
|
||||
r.x -= diff;
|
||||
r.width += diff;
|
||||
}
|
||||
}
|
||||
if( rightVisible || (ltr ? hasTrailingIcon() : hasLeadingIcon()) ) {
|
||||
// reduce right margin
|
||||
Insets margin = getComponent().getMargin();
|
||||
int newRightMargin = Math.min( margin.right, margin.top );
|
||||
if( newRightMargin < margin.left )
|
||||
r.width += scale( margin.right - newRightMargin );
|
||||
// reduce right inset
|
||||
Insets insets = getComponent().getInsets();
|
||||
int newRightInset = Math.min( insets.right, insets.top );
|
||||
if( newRightInset < insets.left )
|
||||
r.width += insets.right - newRightInset;
|
||||
}
|
||||
|
||||
// make sure that width and height are not negative
|
||||
|
||||
@@ -914,7 +914,7 @@ public class FlatTitlePane
|
||||
// screen insets are in physical size, except for Java 15+
|
||||
// (see https://bugs.openjdk.java.net/browse/JDK-8243925)
|
||||
// and except for Java 8 on secondary screens where primary screen is scaled
|
||||
Insets screenInsets = window.getToolkit().getScreenInsets( gc );
|
||||
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
|
||||
|
||||
// maximized bounds are required in physical size, except for Java 15+
|
||||
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and
|
||||
|
||||
@@ -577,7 +577,6 @@ public class FlatTreeUI
|
||||
boolean isEditing = (editingComponent != null && editingRow == row);
|
||||
boolean isSelected = tree.isRowSelected( row );
|
||||
boolean isDropRow = isDropRow( row );
|
||||
boolean needsSelectionPainting = (isSelected || isDropRow) && isPaintSelection();
|
||||
|
||||
// paint alternating rows
|
||||
if( alternateRowColor != null && row % 2 != 0 ) {
|
||||
@@ -608,7 +607,7 @@ public class FlatTreeUI
|
||||
if( isSelected && isWideSelection() ) {
|
||||
Color oldColor = g.getColor();
|
||||
g.setColor( selectionInactiveBackground );
|
||||
paintWideSelection( g, bounds, row );
|
||||
paintWideSelection( g, bounds, row, false );
|
||||
g.setColor( oldColor );
|
||||
}
|
||||
return;
|
||||
@@ -628,7 +627,7 @@ public class FlatTreeUI
|
||||
|
||||
// renderer background/foreground
|
||||
Color oldBackgroundSelectionColor = null;
|
||||
if( isSelected && !hasFocus && !isDropRow ) {
|
||||
if( isSelected && !hasFocus ) {
|
||||
// apply inactive selection background/foreground if tree is not focused
|
||||
oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground );
|
||||
setRendererForeground( rendererComponent, selectionInactiveForeground );
|
||||
@@ -655,26 +654,12 @@ public class FlatTreeUI
|
||||
}
|
||||
|
||||
// paint selection background
|
||||
if( needsSelectionPainting ) {
|
||||
// set selection color
|
||||
Color oldColor = g.getColor();
|
||||
g.setColor( isDropRow
|
||||
? UIManager.getColor( "Tree.dropCellBackground" )
|
||||
: (rendererComponent instanceof DefaultTreeCellRenderer
|
||||
? ((DefaultTreeCellRenderer)rendererComponent).getBackgroundSelectionColor()
|
||||
: (hasFocus ? selectionBackground : selectionInactiveBackground)) );
|
||||
if( isSelected && isPaintSelection() ) {
|
||||
Color selectionColor = rendererComponent instanceof DefaultTreeCellRenderer
|
||||
? ((DefaultTreeCellRenderer)rendererComponent).getBackgroundSelectionColor()
|
||||
: (hasFocus ? selectionBackground : selectionInactiveBackground);
|
||||
|
||||
if( isWideSelection() ) {
|
||||
// wide selection
|
||||
paintWideSelection( g, bounds, row );
|
||||
} else {
|
||||
// non-wide selection
|
||||
paintCellBackground( g, rendererComponent, bounds, row, true );
|
||||
}
|
||||
|
||||
// this is actually not necessary because renderer should always set color
|
||||
// before painting, but doing anyway to avoid any side effect (in bad renderers)
|
||||
g.setColor( oldColor );
|
||||
paintRowSelection( g, selectionColor, rendererComponent, bounds, row, false );
|
||||
} else {
|
||||
// paint cell background if DefaultTreeCellRenderer.getBackgroundNonSelectionColor() is set
|
||||
if( rendererComponent instanceof DefaultTreeCellRenderer ) {
|
||||
@@ -683,12 +668,19 @@ public class FlatTreeUI
|
||||
if( bg != null && !bg.equals( defaultCellNonSelectionBackground ) ) {
|
||||
Color oldColor = g.getColor();
|
||||
g.setColor( bg );
|
||||
paintCellBackground( g, rendererComponent, bounds, row, false );
|
||||
paintCellBackground( g, rendererComponent, bounds, row, false, false );
|
||||
g.setColor( oldColor );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// paint drop background
|
||||
// (this needs to be an extra step for rounded selection)
|
||||
if( isDropRow && isPaintSelection() ) {
|
||||
paintRowSelection( g, UIManager.getColor( "Tree.dropCellBackground" ),
|
||||
rendererComponent, bounds, row, true );
|
||||
}
|
||||
|
||||
// paint renderer
|
||||
rendererPane.paintComponent( g, rendererComponent, tree, bounds.x, bounds.y, bounds.width, bounds.height, true );
|
||||
|
||||
@@ -699,6 +691,26 @@ public class FlatTreeUI
|
||||
((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor );
|
||||
}
|
||||
|
||||
private void paintRowSelection( Graphics g, Color color, Component rendererComponent,
|
||||
Rectangle bounds, int row, boolean paintDropSelection )
|
||||
{
|
||||
// set selection color
|
||||
Color oldColor = g.getColor();
|
||||
g.setColor( color );
|
||||
|
||||
if( isWideSelection() ) {
|
||||
// wide selection
|
||||
paintWideSelection( g, bounds, row, paintDropSelection );
|
||||
} else {
|
||||
// non-wide selection
|
||||
paintCellBackground( g, rendererComponent, bounds, row, true, paintDropSelection );
|
||||
}
|
||||
|
||||
// this is actually not necessary because renderer should always set color
|
||||
// before painting, but doing anyway to avoid any side effect (in bad renderers)
|
||||
g.setColor( oldColor );
|
||||
}
|
||||
|
||||
private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) {
|
||||
Color oldColor = null;
|
||||
|
||||
@@ -735,11 +747,11 @@ public class FlatTreeUI
|
||||
return oldColor;
|
||||
}
|
||||
|
||||
private void paintWideSelection( Graphics g, Rectangle bounds, int row ) {
|
||||
private void paintWideSelection( Graphics g, Rectangle bounds, int row, boolean paintDropSelection ) {
|
||||
float arcTop, arcBottom;
|
||||
arcTop = arcBottom = UIScale.scale( selectionArc / 2f );
|
||||
|
||||
if( useUnitedRoundedSelection() ) {
|
||||
if( useUnitedRoundedSelection() && !paintDropSelection ) {
|
||||
if( row > 0 && tree.isRowSelected( row - 1 ) )
|
||||
arcTop = 0;
|
||||
if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) )
|
||||
@@ -751,7 +763,7 @@ public class FlatTreeUI
|
||||
}
|
||||
|
||||
private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds,
|
||||
int row, boolean paintSelection )
|
||||
int row, boolean paintSelection, boolean paintDropSelection )
|
||||
{
|
||||
int xOffset = 0;
|
||||
int imageOffset = 0;
|
||||
@@ -769,7 +781,7 @@ public class FlatTreeUI
|
||||
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
|
||||
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
|
||||
|
||||
if( useUnitedRoundedSelection() ) {
|
||||
if( useUnitedRoundedSelection() && !paintDropSelection ) {
|
||||
if( row > 0 && tree.isRowSelected( row - 1 ) ) {
|
||||
Rectangle r = getPathBounds( tree, tree.getPathForRow( row - 1 ) );
|
||||
arcTopLeft = Math.min( arcTopLeft, r.x - bounds.x );
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Insets;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
import java.awt.Paint;
|
||||
@@ -34,6 +35,7 @@ import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.SystemColor;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
@@ -414,6 +416,17 @@ public class FlatUIUtils
|
||||
return (fullScreenWindow != null && fullScreenWindow == SwingUtilities.windowForComponent( c ));
|
||||
}
|
||||
|
||||
/** @since 3.6.1 */
|
||||
public static Insets getScreenInsets( GraphicsConfiguration gc ) {
|
||||
// on Linux, getScreenInsets() may report wrong values in multi-screen setups
|
||||
// see https://github.com/apache/netbeans/issues/8532#issuecomment-2909687016
|
||||
if( SystemInfo.isLinux &&
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length > 1 )
|
||||
return new Insets( 0, 0, 0, 0 );
|
||||
|
||||
return Toolkit.getDefaultToolkit().getScreenInsets( gc );
|
||||
}
|
||||
|
||||
public static Boolean isRoundRect( Component c ) {
|
||||
return (c instanceof JComponent)
|
||||
? FlatClientProperties.clientPropertyBooleanStrict(
|
||||
|
||||
@@ -331,7 +331,7 @@ public abstract class FlatWindowResizer
|
||||
protected Rectangle getParentBounds() {
|
||||
GraphicsConfiguration gc = window.getGraphicsConfiguration();
|
||||
Rectangle bounds = gc.getBounds();
|
||||
Insets insets = window.getToolkit().getScreenInsets( gc );
|
||||
Insets insets = FlatUIUtils.getScreenInsets( gc );
|
||||
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
|
||||
bounds.width - insets.left - insets.right,
|
||||
bounds.height - insets.top - insets.bottom );
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.awt.Dialog;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Frame;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
@@ -39,6 +40,7 @@ import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.EventListenerList;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
|
||||
//
|
||||
// Interesting resources:
|
||||
@@ -282,6 +284,8 @@ class FlatWindowsNativeWindowBorder
|
||||
HTMINBUTTON = 8,
|
||||
HTMAXBUTTON = 9,
|
||||
HTTOP = 12,
|
||||
HTTOPLEFT = 13,
|
||||
HTTOPRIGHT = 14,
|
||||
HTCLOSE = 20;
|
||||
|
||||
private Window window;
|
||||
@@ -341,6 +345,31 @@ class FlatWindowsNativeWindowBorder
|
||||
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
|
||||
Point pt = scaleDown( x, y );
|
||||
|
||||
// limit top resize border to 4px, which seems to be standard for modern WinUI apps
|
||||
if( isOnResizeBorder && pt.y > UIScale.scale( 4 ) )
|
||||
isOnResizeBorder = false;
|
||||
|
||||
if( isOnResizeBorder ) {
|
||||
Insets insets = window.getInsets();
|
||||
|
||||
// return HTTOPLEFT if mouse is over top resize border on left side
|
||||
// - hovering mouse shows top-left resize cursor
|
||||
// - left-click-and-drag resizes window
|
||||
if( pt.x <= insets.left + UIScale.scale( 12 ) )
|
||||
return HTTOPLEFT;
|
||||
|
||||
// return HTTOPRIGHT if mouse is over top resize border on right side
|
||||
// - hovering mouse shows top-right resize cursor
|
||||
// - left-click-and-drag resizes window
|
||||
if( pt.x >= window.getWidth() - insets.right - UIScale.scale( 12 ) )
|
||||
return HTTOPRIGHT;
|
||||
|
||||
// return HTTOP if mouse is over top resize border
|
||||
// - hovering mouse shows vertical resize cursor
|
||||
// - left-click-and-drag vertically resizes window
|
||||
return HTTOP;
|
||||
}
|
||||
|
||||
// return HTSYSMENU if mouse is over application icon
|
||||
// - left-click on HTSYSMENU area shows system menu
|
||||
// - double-left-click sends WM_CLOSE
|
||||
@@ -364,12 +393,6 @@ class FlatWindowsNativeWindowBorder
|
||||
if( contains( closeButtonBounds, pt ) )
|
||||
return HTCLOSE;
|
||||
|
||||
// return HTTOP if mouse is over top resize border
|
||||
// - hovering mouse shows vertical resize cursor
|
||||
// - left-click and drag vertically resizes window
|
||||
if( isOnResizeBorder )
|
||||
return HTTOP;
|
||||
|
||||
boolean isOnTitleBar = (pt.y < titleBarHeight);
|
||||
if( isOnTitleBar ) {
|
||||
// return HTCLIENT if mouse is over any Swing component in title bar
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
|
||||
MenuBar.selectionArc = 8
|
||||
MenuBar.selectionBackground = lighten(@menuBackground,15%,derived)
|
||||
MenuBar.selectionForeground = @foreground
|
||||
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
|
||||
|
||||
|
||||
#---- MenuItem ----
|
||||
|
||||
@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
|
||||
MenuBar.selectionArc = 8
|
||||
MenuBar.selectionBackground = darken(@menuBackground,15%,derived)
|
||||
MenuBar.selectionForeground = @foreground
|
||||
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
|
||||
|
||||
|
||||
#---- MenuItem ----
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.formdev.flatlaf;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
@@ -29,6 +30,7 @@ import javax.swing.border.Border;
|
||||
import javax.swing.UIDefaults.ActiveValue;
|
||||
import javax.swing.UIDefaults.LazyValue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import com.formdev.flatlaf.ui.FlatEmptyBorder;
|
||||
import com.formdev.flatlaf.ui.FlatLineBorder;
|
||||
import com.formdev.flatlaf.util.DerivedColor;
|
||||
@@ -452,6 +454,73 @@ public class TestUIDefaultsLoader
|
||||
return ((LazyValue)v).createValue( null );
|
||||
}
|
||||
|
||||
//---- invalid values -----------------------------------------------------
|
||||
|
||||
@Test
|
||||
void parseInvalidValue() {
|
||||
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", null ) );
|
||||
assertThrows( new NumberFormatException( "invalid integer or float '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", null ) );
|
||||
assertThrows( new NumberFormatException( "invalid integer or float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", null ) );
|
||||
|
||||
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3,4'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3,4", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", null ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseInvalidValueWithJavaType() {
|
||||
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", boolean.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", Boolean.class ) );
|
||||
|
||||
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", char.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", Character.class ) );
|
||||
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", int.class ) );
|
||||
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", Integer.class ) );
|
||||
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", float.class ) );
|
||||
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", Float.class ) );
|
||||
|
||||
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3", Insets.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", Insets.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", Dimension.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", Dimension.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", Color.class ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", Color.class ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseInvalidBorders() {
|
||||
assertThrows( new IllegalArgumentException( "invalid border '1,abc,3,4' (invalid insets '1,abc,3,4')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,abc,3,4", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid border '1,2,3' (invalid insets '1,2,3')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,,,' (invalid insets '1,2,3,,,')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,,,", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f0' (invalid color '#f0')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f0", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5abc' (invalid float '2.5abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5abc", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5,6abc' (invalid integer '6abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5,6abc", null ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseInvalidFonts() {
|
||||
// size
|
||||
assertThrows( new IllegalArgumentException( "invalid font '12abc' (invalid integer '12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "12abc", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font '+12abc' (invalid integer '+12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+12abc", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font '+3abc' (invalid integer '+3abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+3abc", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font '-4abc' (invalid integer '-4abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "-4abc", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font '150abc%' (invalid integer '150abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "150abc%", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font 'bold 13abc Monospaced' (invalid integer '13abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold 13abc Monospaced", null ) );
|
||||
|
||||
// invalid combinations of styles
|
||||
assertThrows( new IllegalArgumentException( "invalid font 'bold +italic': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold +italic", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font '+bold -bold': can not use '+bold' and '-bold'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+bold -bold", null ) );
|
||||
assertThrows( new IllegalArgumentException( "invalid font '+italic -italic': can not use '+italic' and '-italic'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+italic -italic", null ) );
|
||||
}
|
||||
|
||||
private void assertThrows( Throwable expected, Executable executable ) {
|
||||
Throwable actual = assertThrowsExactly( expected.getClass(), executable );
|
||||
assertEquals( expected.getMessage(), actual.getMessage() );
|
||||
}
|
||||
|
||||
//---- class TestInstance -------------------------------------------------
|
||||
|
||||
@SuppressWarnings( "EqualsHashCode" ) // Error Prone
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Copyright 2025 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.formdev.flatlaf.ui;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import java.awt.Color;
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.UIManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class TestFlatButton
|
||||
{
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
String[] defs = {
|
||||
"Button.background", "#000001",
|
||||
"Button.foreground", "#000002",
|
||||
"Button.focusedBackground", "#000003",
|
||||
"Button.focusedForeground", "#000004",
|
||||
"Button.hoverBackground", "#000005",
|
||||
"Button.hoverForeground", "#000006",
|
||||
"Button.pressedBackground", "#000007",
|
||||
"Button.pressedForeground", "#000008",
|
||||
"Button.selectedBackground", "#000009",
|
||||
"Button.selectedForeground", "#00000a",
|
||||
"Button.disabledBackground", "#00000b",
|
||||
"Button.disabledText", "#00000c",
|
||||
"Button.disabledSelectedBackground", "#00000d",
|
||||
"Button.disabledSelectedForeground", "#00000e",
|
||||
|
||||
"Button.default.background", "#000101",
|
||||
"Button.default.foreground", "#000102",
|
||||
"Button.default.focusedBackground", "#000103",
|
||||
"Button.default.focusedForeground", "#000104",
|
||||
"Button.default.hoverBackground", "#000105",
|
||||
"Button.default.hoverForeground", "#000106",
|
||||
"Button.default.pressedBackground", "#000107",
|
||||
"Button.default.pressedForeground", "#000108",
|
||||
|
||||
"Button.toolbar.hoverBackground", "#000201",
|
||||
"Button.toolbar.hoverForeground", "#000202",
|
||||
"Button.toolbar.pressedBackground", "#000203",
|
||||
"Button.toolbar.pressedForeground", "#000204",
|
||||
"Button.toolbar.selectedBackground", "#000205",
|
||||
"Button.toolbar.selectedForeground", "#000206",
|
||||
"Button.toolbar.disabledSelectedBackground", "#000207",
|
||||
"Button.toolbar.disabledSelectedForeground", "#000208",
|
||||
};
|
||||
|
||||
HashMap<String, String> globalExtraDefaults = new HashMap<>();
|
||||
for( int i = 0; i < defs.length; i += 2 )
|
||||
globalExtraDefaults.put( defs[i], defs[i+1] );
|
||||
FlatLaf.setGlobalExtraDefaults( globalExtraDefaults );
|
||||
|
||||
TestUtils.setup( false );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void cleanup() {
|
||||
TestUtils.cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
void background() {
|
||||
JButton b = new JButton();
|
||||
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
UIManager.getColor( "Button.background" ),
|
||||
UIManager.getColor( "Button.disabledBackground" ),
|
||||
UIManager.getColor( "Button.focusedBackground" ),
|
||||
UIManager.getColor( "Button.hoverBackground" ),
|
||||
UIManager.getColor( "Button.pressedBackground" ) );
|
||||
|
||||
// selected
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
UIManager.getColor( "Button.selectedBackground" ),
|
||||
UIManager.getColor( "Button.disabledSelectedBackground" ),
|
||||
null,
|
||||
null,
|
||||
UIManager.getColor( "Button.pressedBackground" ) );
|
||||
b.setSelected( false );
|
||||
|
||||
// default
|
||||
JRootPane rootPane = new JRootPane();
|
||||
rootPane.getContentPane().add( b );
|
||||
rootPane.setDefaultButton( b );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
UIManager.getColor( "Button.default.background" ),
|
||||
UIManager.getColor( "Button.disabledBackground" ),
|
||||
UIManager.getColor( "Button.default.focusedBackground" ),
|
||||
UIManager.getColor( "Button.default.hoverBackground" ),
|
||||
UIManager.getColor( "Button.default.pressedBackground" ) );
|
||||
rootPane.getContentPane().remove( b );
|
||||
}
|
||||
|
||||
@Test
|
||||
void foreground() {
|
||||
JButton b = new JButton();
|
||||
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
UIManager.getColor( "Button.foreground" ),
|
||||
UIManager.getColor( "Button.disabledText" ),
|
||||
UIManager.getColor( "Button.focusedForeground" ),
|
||||
UIManager.getColor( "Button.hoverForeground" ),
|
||||
UIManager.getColor( "Button.pressedForeground" ) );
|
||||
|
||||
// selected
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
UIManager.getColor( "Button.selectedForeground" ),
|
||||
FlatUIUtils.getUIColor( "Button.disabledSelectedForeground", "Button.disabledText" ),
|
||||
null,
|
||||
null,
|
||||
UIManager.getColor( "Button.pressedForeground" ) );
|
||||
b.setSelected( false );
|
||||
|
||||
// default
|
||||
JRootPane rootPane = new JRootPane();
|
||||
rootPane.getContentPane().add( b );
|
||||
rootPane.setDefaultButton( b );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
UIManager.getColor( "Button.default.foreground" ),
|
||||
UIManager.getColor( "Button.disabledText" ),
|
||||
UIManager.getColor( "Button.default.focusedForeground" ),
|
||||
UIManager.getColor( "Button.default.hoverForeground" ),
|
||||
UIManager.getColor( "Button.default.pressedForeground" ) );
|
||||
rootPane.getContentPane().remove( b );
|
||||
}
|
||||
|
||||
@Test
|
||||
void backgroundExplicit() {
|
||||
JButton b = new JButton();
|
||||
|
||||
Color c = new Color( 0x020001 );
|
||||
b.setBackground( c );
|
||||
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
c,
|
||||
UIManager.getColor( "Button.disabledBackground" ),
|
||||
null,
|
||||
UIManager.getColor( "Button.hoverBackground" ),
|
||||
UIManager.getColor( "Button.pressedBackground" ) );
|
||||
|
||||
// selected
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
UIManager.getColor( "Button.selectedBackground" ),
|
||||
UIManager.getColor( "Button.disabledSelectedBackground" ),
|
||||
null,
|
||||
null,
|
||||
UIManager.getColor( "Button.pressedBackground" ) );
|
||||
b.setSelected( false );
|
||||
|
||||
// default
|
||||
JRootPane rootPane = new JRootPane();
|
||||
rootPane.getContentPane().add( b );
|
||||
rootPane.setDefaultButton( b );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
c,
|
||||
UIManager.getColor( "Button.disabledBackground" ),
|
||||
null,
|
||||
UIManager.getColor( "Button.default.hoverBackground" ),
|
||||
UIManager.getColor( "Button.default.pressedBackground" ) );
|
||||
rootPane.getContentPane().remove( b );
|
||||
}
|
||||
|
||||
@Test
|
||||
void foregroundExplicit() {
|
||||
JButton b = new JButton();
|
||||
|
||||
Color c = new Color( 0x020001 );
|
||||
b.setForeground( c );
|
||||
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
c,
|
||||
UIManager.getColor( "Button.disabledText" ),
|
||||
null,
|
||||
UIManager.getColor( "Button.hoverForeground" ),
|
||||
UIManager.getColor( "Button.pressedForeground" ) );
|
||||
|
||||
// selected
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
c,
|
||||
FlatUIUtils.getUIColor( "Button.disabledSelectedForeground", "Button.disabledText" ),
|
||||
null,
|
||||
null,
|
||||
UIManager.getColor( "Button.pressedForeground" ) );
|
||||
b.setSelected( false );
|
||||
|
||||
// default
|
||||
JRootPane rootPane = new JRootPane();
|
||||
rootPane.getContentPane().add( b );
|
||||
rootPane.setDefaultButton( b );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
c,
|
||||
UIManager.getColor( "Button.disabledText" ),
|
||||
null,
|
||||
UIManager.getColor( "Button.default.hoverForeground" ),
|
||||
UIManager.getColor( "Button.default.pressedForeground" ) );
|
||||
rootPane.getContentPane().remove( b );
|
||||
}
|
||||
|
||||
@Test
|
||||
void backgroundStyled() {
|
||||
JButton b = new JButton();
|
||||
|
||||
b.putClientProperty( FlatClientProperties.STYLE,
|
||||
"background: #020001;" +
|
||||
"disabledBackground: #020002;" +
|
||||
"focusedBackground: #020003;" +
|
||||
"hoverBackground: #020004;" +
|
||||
"pressedBackground: #020005;" +
|
||||
"selectedBackground: #020006;" +
|
||||
"disabledSelectedBackground: #020007;" );
|
||||
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
new Color( 0x020001 ),
|
||||
new Color( 0x020002 ),
|
||||
new Color( 0x020003 ),
|
||||
new Color( 0x020004 ),
|
||||
new Color( 0x020005 ) );
|
||||
|
||||
// selected
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
new Color( 0x020006 ),
|
||||
new Color( 0x020007 ),
|
||||
null,
|
||||
null,
|
||||
new Color( 0x020005 ) );
|
||||
b.setSelected( false );
|
||||
|
||||
|
||||
Color c = new Color( 0x0a0001 );
|
||||
b.setBackground( c );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
c,
|
||||
new Color( 0x020002 ),
|
||||
c,
|
||||
new Color( 0x020004 ),
|
||||
new Color( 0x020005 ) );
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
new Color( 0x020006 ),
|
||||
new Color( 0x020007 ),
|
||||
null,
|
||||
null,
|
||||
new Color( 0x020005 ) );
|
||||
b.setSelected( false );
|
||||
|
||||
|
||||
b = new JButton();
|
||||
b.putClientProperty( FlatClientProperties.STYLE,
|
||||
"default.background: #020101;" +
|
||||
"disabledBackground: #020102;" +
|
||||
"default.focusedBackground: #020103;" +
|
||||
"default.hoverBackground: #020104;" +
|
||||
"default.pressedBackground: #020105;" );
|
||||
|
||||
// default
|
||||
JRootPane rootPane = new JRootPane();
|
||||
rootPane.getContentPane().add( b );
|
||||
rootPane.setDefaultButton( b );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
|
||||
new Color( 0x020101 ),
|
||||
new Color( 0x020102 ),
|
||||
new Color( 0x020103 ),
|
||||
new Color( 0x020104 ),
|
||||
new Color( 0x020105 ) );
|
||||
rootPane.getContentPane().remove( b );
|
||||
}
|
||||
|
||||
@Test
|
||||
void foregroundStyled() {
|
||||
JButton b = new JButton();
|
||||
|
||||
b.putClientProperty( FlatClientProperties.STYLE,
|
||||
"foreground: #020001;" +
|
||||
"disabledText: #020002;" +
|
||||
"focusedForeground: #020003;" +
|
||||
"hoverForeground: #020004;" +
|
||||
"pressedForeground: #020005;" +
|
||||
"selectedForeground: #020006;" +
|
||||
"disabledSelectedForeground: #020007;" );
|
||||
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
new Color( 0x020001 ),
|
||||
new Color( 0x020002 ),
|
||||
new Color( 0x020003 ),
|
||||
new Color( 0x020004 ),
|
||||
new Color( 0x020005 ) );
|
||||
|
||||
// selected
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
new Color( 0x020006 ),
|
||||
new Color( 0x020007 ),
|
||||
null,
|
||||
null,
|
||||
new Color( 0x020005 ) );
|
||||
b.setSelected( false );
|
||||
|
||||
|
||||
Color c = new Color( 0x0a0001 );
|
||||
b.setForeground( c );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
c,
|
||||
new Color( 0x020002 ),
|
||||
c,
|
||||
new Color( 0x020004 ),
|
||||
new Color( 0x020005 ) );
|
||||
b.setSelected( true );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
c,
|
||||
new Color( 0x020007 ),
|
||||
null,
|
||||
null,
|
||||
new Color( 0x020005 ) );
|
||||
b.setSelected( false );
|
||||
|
||||
|
||||
b = new JButton();
|
||||
b.putClientProperty( FlatClientProperties.STYLE,
|
||||
"default.foreground: #020101;" +
|
||||
"disabledText: #020102;" +
|
||||
"default.focusedForeground: #020103;" +
|
||||
"default.hoverForeground: #020104;" +
|
||||
"default.pressedForeground: #020105;" );
|
||||
|
||||
// default
|
||||
JRootPane rootPane = new JRootPane();
|
||||
rootPane.getContentPane().add( b );
|
||||
rootPane.setDefaultButton( b );
|
||||
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
|
||||
new Color( 0x020101 ),
|
||||
new Color( 0x020102 ),
|
||||
new Color( 0x020103 ),
|
||||
new Color( 0x020104 ),
|
||||
new Color( 0x020105 ) );
|
||||
rootPane.getContentPane().remove( b );
|
||||
}
|
||||
|
||||
private void testButtonColors( JButton b, Function<JButton, Color> f,
|
||||
Color expectedEnabled, Color expectedDisabled, Color expectedFocused,
|
||||
Color expectedHover, Color expectedPressed
|
||||
)
|
||||
{
|
||||
assertEquals( expectedEnabled, f.apply( b ) );
|
||||
|
||||
// disabled
|
||||
b.setEnabled( false );
|
||||
assertEquals( expectedDisabled, f.apply( b ) );
|
||||
b.setEnabled( true );
|
||||
|
||||
// focused
|
||||
if( expectedFocused != null ) {
|
||||
b.putClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER, (Predicate<JComponent>) c -> true );
|
||||
assertEquals( expectedFocused, f.apply( b ) );
|
||||
b.putClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER, null );
|
||||
}
|
||||
|
||||
// hover
|
||||
if( expectedHover != null ) {
|
||||
b.getModel().setRollover( true );
|
||||
assertEquals( expectedHover, f.apply( b ) );
|
||||
b.getModel().setRollover( false );
|
||||
}
|
||||
|
||||
// pressed
|
||||
if( expectedPressed != null ) {
|
||||
b.getModel().setPressed( true );
|
||||
assertEquals( expectedPressed, f.apply( b ) );
|
||||
b.getModel().setPressed( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ tasks {
|
||||
manifest {
|
||||
attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" )
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
|
||||
if( java.toolchain.languageVersion.get().asInt() >= 9 )
|
||||
attributes( "Multi-Release" to "true" )
|
||||
|
||||
// allow loading FlatLaf native library in Java 24+ (see https://openjdk.org/jeps/472)
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.formdev.flatlaf.demo;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -49,6 +50,7 @@ import com.formdev.flatlaf.extras.FlatSVGUtils;
|
||||
import com.formdev.flatlaf.util.ColorFunctions;
|
||||
import com.formdev.flatlaf.util.FontUtils;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
import com.formdev.flatlaf.util.SystemFileChooser;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
import net.miginfocom.layout.ConstraintParser;
|
||||
import net.miginfocom.layout.LC;
|
||||
@@ -172,6 +174,48 @@ class DemoFrame
|
||||
chooser.showSaveDialog( this );
|
||||
}
|
||||
|
||||
private void openSystemActionPerformed() {
|
||||
SystemFileChooser chooser = new SystemFileChooser();
|
||||
chooser.setMultiSelectionEnabled( true );
|
||||
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
|
||||
"Text Files", "txt", "md" ) );
|
||||
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
|
||||
"PDF Files", "pdf" ) );
|
||||
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
|
||||
"Archives", "zip", "tar", "jar", "7z" ) );
|
||||
|
||||
if( chooser.showOpenDialog( this ) != SystemFileChooser.APPROVE_OPTION )
|
||||
return;
|
||||
|
||||
File[] files = chooser.getSelectedFiles();
|
||||
System.out.println( Arrays.toString( files ).replace( ",", "\n" ) );
|
||||
}
|
||||
|
||||
private void saveAsSystemActionPerformed() {
|
||||
SystemFileChooser chooser = new SystemFileChooser();
|
||||
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
|
||||
"Text Files", "txt", "md" ) );
|
||||
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
|
||||
"Images", "png", "gif", "jpg" ) );
|
||||
|
||||
if( chooser.showSaveDialog( this ) != SystemFileChooser.APPROVE_OPTION )
|
||||
return;
|
||||
|
||||
File file = chooser.getSelectedFile();
|
||||
System.out.println( file );
|
||||
}
|
||||
|
||||
private void selectFolderSystemActionPerformed() {
|
||||
SystemFileChooser chooser = new SystemFileChooser();
|
||||
chooser.setFileSelectionMode( SystemFileChooser.DIRECTORIES_ONLY );
|
||||
|
||||
if( chooser.showOpenDialog( this ) != SystemFileChooser.APPROVE_OPTION )
|
||||
return;
|
||||
|
||||
File directory = chooser.getSelectedFile();
|
||||
System.out.println( directory );
|
||||
}
|
||||
|
||||
private void exitActionPerformed() {
|
||||
dispose();
|
||||
}
|
||||
@@ -508,6 +552,9 @@ class DemoFrame
|
||||
JMenuItem newMenuItem = new JMenuItem();
|
||||
JMenuItem openMenuItem = new JMenuItem();
|
||||
JMenuItem saveAsMenuItem = new JMenuItem();
|
||||
JMenuItem openSystemMenuItem = new JMenuItem();
|
||||
JMenuItem saveAsSystemMenuItem = new JMenuItem();
|
||||
JMenuItem selectFolderSystemMenuItem = new JMenuItem();
|
||||
JMenuItem closeMenuItem = new JMenuItem();
|
||||
exitMenuItem = new JMenuItem();
|
||||
JMenu editMenu = new JMenu();
|
||||
@@ -608,6 +655,25 @@ class DemoFrame
|
||||
fileMenu.add(saveAsMenuItem);
|
||||
fileMenu.addSeparator();
|
||||
|
||||
//---- openSystemMenuItem ----
|
||||
openSystemMenuItem.setText("Open (System)...");
|
||||
openSystemMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.SHIFT_DOWN_MASK));
|
||||
openSystemMenuItem.addActionListener(e -> openSystemActionPerformed());
|
||||
fileMenu.add(openSystemMenuItem);
|
||||
|
||||
//---- saveAsSystemMenuItem ----
|
||||
saveAsSystemMenuItem.setText("Save As (System)...");
|
||||
saveAsSystemMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.SHIFT_DOWN_MASK));
|
||||
saveAsSystemMenuItem.addActionListener(e -> saveAsSystemActionPerformed());
|
||||
fileMenu.add(saveAsSystemMenuItem);
|
||||
|
||||
//---- selectFolderSystemMenuItem ----
|
||||
selectFolderSystemMenuItem.setText("Select Folder (System)...");
|
||||
selectFolderSystemMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.SHIFT_DOWN_MASK));
|
||||
selectFolderSystemMenuItem.addActionListener(e -> selectFolderSystemActionPerformed());
|
||||
fileMenu.add(selectFolderSystemMenuItem);
|
||||
fileMenu.addSeparator();
|
||||
|
||||
//---- closeMenuItem ----
|
||||
closeMenuItem.setText("Close");
|
||||
closeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8"
|
||||
JFDML JFormDesigner: "8.3" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
@@ -182,6 +182,27 @@ new FormModel {
|
||||
"mnemonic": 83
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsActionPerformed", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
|
||||
name: "separator9"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "openSystemMenuItem"
|
||||
"text": "Open (System)..."
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 79, 4291, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openSystemActionPerformed", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "saveAsSystemMenuItem"
|
||||
"text": "Save As (System)..."
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 83, 4291, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsSystemActionPerformed", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "selectFolderSystemMenuItem"
|
||||
"text": "Select Folder (System)..."
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 70, 4291, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "selectFolderSystemActionPerformed", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
|
||||
name: "separator2"
|
||||
} )
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.formdev.flatlaf.demo;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.util.prefs.Preferences;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -27,6 +28,7 @@ import com.formdev.flatlaf.fonts.inter.FlatInterFont;
|
||||
import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont;
|
||||
import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont;
|
||||
import com.formdev.flatlaf.fonts.roboto_mono.FlatRobotoMonoFont;
|
||||
import com.formdev.flatlaf.util.SystemFileChooser;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
|
||||
/**
|
||||
@@ -73,6 +75,28 @@ public class FlatLafDemo
|
||||
DemoPrefs.init( PREFS_ROOT_PATH );
|
||||
DemoPrefs.initSystemScale();
|
||||
|
||||
// SystemFileChooser state storage
|
||||
SystemFileChooser.setStateStore( new SystemFileChooser.StateStore() {
|
||||
private static final String KEY_PREFIX = "fileChooser.";
|
||||
private final Preferences state = Preferences.userRoot().node( PREFS_ROOT_PATH );
|
||||
|
||||
@Override
|
||||
public String get( String key, String def ) {
|
||||
String value = state.get( KEY_PREFIX + key, def );
|
||||
System.out.println( "SystemFileChooser State GET " + key + " = " + value );
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put( String key, String value ) {
|
||||
System.out.println( "SystemFileChooser State PUT " + key + " = " + value );
|
||||
if( value != null )
|
||||
state.put( KEY_PREFIX + key, value );
|
||||
else
|
||||
state.remove( KEY_PREFIX + key );
|
||||
}
|
||||
} );
|
||||
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
// install fonts for lazy loading
|
||||
FlatInterFont.installLazy();
|
||||
|
||||
@@ -35,11 +35,16 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-extras-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-extras)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-extras)
|
||||
|
||||
If SVG classes are used, `jsvg-<version>.jar` is also required:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.github.weisj/jsvg)
|
||||
[](https://central.sonatype.com/artifact/com.github.weisj/jsvg)
|
||||
|
||||
Supported JSVG versions:
|
||||
|
||||
- FlatLaf 3.6.1+ supports JSVG 1.6.0 and later.
|
||||
- FlatLaf 3.6- supports only JSVG 1.x (but not 2.x).
|
||||
|
||||
|
||||
Tools
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLayeredPane;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.RootPaneContainer;
|
||||
import com.formdev.flatlaf.FlatSystemProperties;
|
||||
import com.formdev.flatlaf.util.Animator;
|
||||
@@ -52,15 +53,13 @@ public class FlatAnimatedLafChange
|
||||
public static int duration = 160;
|
||||
|
||||
/**
|
||||
* The resolution of the animation in milliseconds. Default is 30 ms.
|
||||
* The resolution of the animation in milliseconds. Default is 16 ms.
|
||||
*/
|
||||
public static int resolution = 30;
|
||||
public static int resolution = 16;
|
||||
|
||||
private static Animator animator;
|
||||
private static final Map<JLayeredPane, JComponent> oldUIsnapshots = new WeakHashMap<>();
|
||||
private static final Map<JLayeredPane, JComponent> newUIsnapshots = new WeakHashMap<>();
|
||||
private static final Map<JLayeredPane, SnapshotLayer> snapshots = new WeakHashMap<>();
|
||||
private static float alpha;
|
||||
private static boolean inShowSnapshot;
|
||||
|
||||
/**
|
||||
* Create a snapshot of the old UI and shows it on top of the UI.
|
||||
@@ -77,59 +76,52 @@ public class FlatAnimatedLafChange
|
||||
alpha = 1;
|
||||
|
||||
// show snapshot of old UI
|
||||
showSnapshot( true, oldUIsnapshots );
|
||||
showSnapshot( true );
|
||||
}
|
||||
|
||||
private static void showSnapshot( boolean useAlpha, Map<JLayeredPane, JComponent> map ) {
|
||||
inShowSnapshot = true;
|
||||
|
||||
private static void showSnapshot( boolean old ) {
|
||||
// create snapshots for all shown windows
|
||||
Window[] windows = Window.getWindows();
|
||||
for( Window window : windows ) {
|
||||
if( !(window instanceof RootPaneContainer) || !window.isShowing() )
|
||||
continue;
|
||||
|
||||
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
|
||||
|
||||
// create snapshot image
|
||||
// (using volatile image to have correct sub-pixel text rendering on Java 9+)
|
||||
VolatileImage snapshot = window.createVolatileImage( window.getWidth(), window.getHeight() );
|
||||
if( snapshot == null )
|
||||
VolatileImage snapshotImage = layeredPane.createVolatileImage( layeredPane.getWidth(), layeredPane.getHeight() );
|
||||
if( snapshotImage == null )
|
||||
continue;
|
||||
|
||||
// paint window to snapshot image
|
||||
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
|
||||
layeredPane.paint( snapshot.getGraphics() );
|
||||
layeredPane.paint( snapshotImage.getGraphics() );
|
||||
|
||||
// create snapshot layer, which is added to layered pane and paints
|
||||
// snapshot with animated alpha
|
||||
JComponent snapshotLayer = new JComponent() {
|
||||
@Override
|
||||
public void paint( Graphics g ) {
|
||||
if( inShowSnapshot || snapshot.contentsLost() )
|
||||
return;
|
||||
|
||||
if( useAlpha )
|
||||
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
|
||||
g.drawImage( snapshot, 0, 0, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
|
||||
// release system resources used by volatile image
|
||||
snapshot.flush();
|
||||
}
|
||||
};
|
||||
if( !useAlpha )
|
||||
if( old ) {
|
||||
// create snapshot layer, which is added to layered pane and paints
|
||||
// snapshot with animated alpha
|
||||
SnapshotLayer snapshotLayer = new SnapshotLayer();
|
||||
snapshotLayer.setOpaque( true );
|
||||
snapshotLayer.setSize( layeredPane.getSize() );
|
||||
snapshotLayer.setSize( layeredPane.getSize() );
|
||||
snapshotLayer.oldSnapshotImage = snapshotImage;
|
||||
|
||||
// add image layer to layered pane
|
||||
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + (useAlpha ? 2 : 1) ) );
|
||||
map.put( layeredPane, snapshotLayer );
|
||||
snapshots.put( layeredPane, snapshotLayer );
|
||||
} else {
|
||||
SnapshotLayer snapshotLayer = snapshots.get( layeredPane );
|
||||
if( snapshotLayer == null ) {
|
||||
snapshotImage.flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
snapshotLayer.newSnapshotImage = snapshotImage;
|
||||
|
||||
// add snapshot layer to layered pane
|
||||
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + 1 ) );
|
||||
|
||||
// let FlatRootPaneUI know that animated Laf change is in progress
|
||||
layeredPane.getRootPane().putClientProperty( "FlatLaf.internal.animatedLafChange", true );
|
||||
}
|
||||
}
|
||||
|
||||
inShowSnapshot = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,23 +133,22 @@ public class FlatAnimatedLafChange
|
||||
if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
|
||||
return;
|
||||
|
||||
if( oldUIsnapshots.isEmpty() )
|
||||
if( snapshots.isEmpty() )
|
||||
return;
|
||||
|
||||
// show snapshot of new UI
|
||||
showSnapshot( false, newUIsnapshots );
|
||||
showSnapshot( false );
|
||||
|
||||
// create animator
|
||||
animator = new Animator( duration, fraction -> {
|
||||
if( fraction < 0.1 || fraction > 0.9 )
|
||||
return; // ignore initial and last events
|
||||
|
||||
alpha = 1f - fraction;
|
||||
|
||||
// repaint snapshots
|
||||
for( Map.Entry<JLayeredPane, JComponent> e : oldUIsnapshots.entrySet() ) {
|
||||
if( e.getKey().isShowing() )
|
||||
e.getValue().repaint();
|
||||
for( Map.Entry<JLayeredPane, SnapshotLayer> e : snapshots.entrySet() ) {
|
||||
if( e.getKey().isShowing() ) {
|
||||
SnapshotLayer snapshotLayer = e.getValue();
|
||||
snapshotLayer.paintImmediately( 0, 0, snapshotLayer.getWidth(),snapshotLayer.getHeight() );
|
||||
}
|
||||
}
|
||||
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
@@ -171,18 +162,27 @@ public class FlatAnimatedLafChange
|
||||
}
|
||||
|
||||
private static void hideSnapshot() {
|
||||
hideSnapshot( oldUIsnapshots );
|
||||
hideSnapshot( newUIsnapshots );
|
||||
}
|
||||
|
||||
private static void hideSnapshot( Map<JLayeredPane, JComponent> map ) {
|
||||
// remove snapshots
|
||||
for( Map.Entry<JLayeredPane, JComponent> e : map.entrySet() ) {
|
||||
e.getKey().remove( e.getValue() );
|
||||
e.getKey().repaint();
|
||||
for( Map.Entry<JLayeredPane, SnapshotLayer> e : snapshots.entrySet() ) {
|
||||
JLayeredPane layeredPane = e.getKey();
|
||||
SnapshotLayer snapshotLayer = e.getValue();
|
||||
|
||||
layeredPane.remove( snapshotLayer );
|
||||
layeredPane.repaint();
|
||||
|
||||
snapshotLayer.flushSnapshotImages();
|
||||
|
||||
// run Runnable that FlatRootPaneUI put into client properties
|
||||
JRootPane rootPane = layeredPane.getRootPane();
|
||||
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange", null );
|
||||
Runnable r = (Runnable) rootPane.getClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished" );
|
||||
if( r != null ) {
|
||||
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", null );
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
map.clear();
|
||||
snapshots.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,4 +194,40 @@ public class FlatAnimatedLafChange
|
||||
else
|
||||
hideSnapshot();
|
||||
}
|
||||
|
||||
//---- class SnapshotLayer ------------------------------------------------
|
||||
|
||||
private static class SnapshotLayer
|
||||
extends JComponent
|
||||
{
|
||||
VolatileImage oldSnapshotImage;
|
||||
VolatileImage newSnapshotImage;
|
||||
|
||||
@Override
|
||||
public void paint( Graphics g ) {
|
||||
if( oldSnapshotImage.contentsLost() ||
|
||||
newSnapshotImage == null || newSnapshotImage.contentsLost() )
|
||||
return;
|
||||
|
||||
// draw new UI snapshot
|
||||
g.drawImage( newSnapshotImage, 0, 0, null );
|
||||
|
||||
// draw old UI snapshot
|
||||
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
|
||||
g.drawImage( oldSnapshotImage, 0, 0, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
flushSnapshotImages();
|
||||
}
|
||||
|
||||
void flushSnapshotImages() {
|
||||
// release system resources used by volatile image
|
||||
oldSnapshotImage.flush();
|
||||
if( newSnapshotImage != null )
|
||||
newSnapshotImage.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.awt.Image;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.LinearGradientPaint;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RGBImageFilter;
|
||||
@@ -52,7 +53,7 @@ import com.formdev.flatlaf.util.MultiResolutionImageSupport;
|
||||
import com.formdev.flatlaf.util.SoftCache;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
import com.github.weisj.jsvg.SVGDocument;
|
||||
import com.github.weisj.jsvg.geometry.size.FloatSize;
|
||||
import com.github.weisj.jsvg.parser.LoaderContext;
|
||||
import com.github.weisj.jsvg.parser.SVGLoader;
|
||||
|
||||
/**
|
||||
@@ -271,7 +272,7 @@ public class FlatSVGIcon
|
||||
this( null, -1, -1, 1, false, null, null );
|
||||
|
||||
try( InputStream in2 = in ) {
|
||||
document = svgLoader.load( in2 );
|
||||
document = svgLoader.load( in2, null, LoaderContext.createDefault() );
|
||||
|
||||
if( document == null ) {
|
||||
loadFailed = true;
|
||||
@@ -620,9 +621,9 @@ public class FlatSVGIcon
|
||||
|
||||
UIScale.scaleGraphics( g );
|
||||
if( width > 0 || height > 0 ) {
|
||||
FloatSize svgSize = document.size();
|
||||
double sx = (width > 0) ? width / svgSize.width : 1;
|
||||
double sy = (height > 0) ? height / svgSize.height : 1;
|
||||
Dimension2D svgSize = document.size();
|
||||
double sx = (width > 0) ? width / svgSize.getWidth() : 1;
|
||||
double sy = (height > 0) ? height / svgSize.getHeight() : 1;
|
||||
if( sx != 1 || sy != 1 )
|
||||
g.scale( sx, sy );
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.formdev.flatlaf.extras;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
@@ -28,7 +29,6 @@ import javax.swing.JWindow;
|
||||
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
import com.github.weisj.jsvg.SVGDocument;
|
||||
import com.github.weisj.jsvg.geometry.size.FloatSize;
|
||||
|
||||
/**
|
||||
* Utility methods for SVG.
|
||||
@@ -180,9 +180,9 @@ public class FlatSVGUtils
|
||||
*/
|
||||
public static BufferedImage svg2image( URL svgUrl, float scaleFactor ) {
|
||||
SVGDocument document = FlatSVGIcon.loadSVG( svgUrl );
|
||||
FloatSize size = document.size();
|
||||
int width = (int) (size.width * scaleFactor);
|
||||
int height = (int) (size.height * scaleFactor);
|
||||
Dimension2D size = document.size();
|
||||
int width = (int) (size.getWidth() * scaleFactor);
|
||||
int height = (int) (size.getHeight() * scaleFactor);
|
||||
return svg2image( document, width, height );
|
||||
}
|
||||
|
||||
@@ -202,9 +202,9 @@ public class FlatSVGUtils
|
||||
try {
|
||||
FlatSVGIcon.setRenderingHints( g );
|
||||
|
||||
FloatSize size = document.size();
|
||||
double sx = width / size.width;
|
||||
double sy = height / size.height;
|
||||
Dimension2D size = document.size();
|
||||
double sx = width / size.getWidth();
|
||||
double sy = height / size.getHeight();
|
||||
if( sx != 1 || sy != 1 )
|
||||
g.scale( sx, sy );
|
||||
|
||||
|
||||
@@ -202,6 +202,7 @@ public class FlatUIDefaultsInspector
|
||||
JFrame frame = new JFrame();
|
||||
frame.setTitle( "UI Defaults Inspector" );
|
||||
frame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
|
||||
frame.setModalExclusionType( Dialog.ModalExclusionType.TOOLKIT_EXCLUDE );
|
||||
frame.addWindowListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed( WindowEvent e ) {
|
||||
|
||||
@@ -102,4 +102,4 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-fonts-inter-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-inter)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-inter)
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -83,4 +83,4 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-fonts-jetbrains-mono-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-jetbrains-mono)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-jetbrains-mono)
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -83,4 +83,4 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-fonts-roboto-mono-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-roboto-mono)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-roboto-mono)
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -99,4 +99,4 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-fonts-roboto-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-roboto)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-roboto)
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-intellij-themes-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-intellij-themes)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-intellij-themes)
|
||||
|
||||
|
||||
How to use?
|
||||
|
||||
@@ -37,10 +37,10 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-jide-oss-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-jide-oss)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-jide-oss)
|
||||
|
||||
|
||||
JIDE Common Layer library `jide-oss-<version>.jar` (or
|
||||
`jide-common-<version>.jar`) is also required:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/jide-oss)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/jide-oss)
|
||||
|
||||
@@ -65,6 +65,7 @@ public class FlatJidePainter
|
||||
Color oldColor = g.getColor();
|
||||
g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
|
||||
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
|
||||
float arc = UIScale.scale( (float) this.arc );
|
||||
|
||||
if( c instanceof JideSplitButton ) {
|
||||
// For split buttons, this method is invoked twice:
|
||||
@@ -74,6 +75,8 @@ public class FlatJidePainter
|
||||
// the rounded rectangle with component bounds, but clip to the passed rectangle.
|
||||
|
||||
boolean horizontal = (((JideSplitButton)c).getOrientation() == SwingConstants.HORIZONTAL);
|
||||
int width = horizontal ? c.getWidth() : c.getHeight();
|
||||
int height = horizontal ? c.getHeight() : c.getWidth();
|
||||
|
||||
// for vertical orientation, the graphics context is rotated, but 1px wrong
|
||||
if( !horizontal )
|
||||
@@ -82,10 +85,13 @@ public class FlatJidePainter
|
||||
Shape oldClip = g.getClip();
|
||||
g.clipRect( rect.x, rect.y, rect.width, rect.height );
|
||||
|
||||
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0,
|
||||
horizontal ? c.getWidth() : c.getHeight(),
|
||||
horizontal ? c.getHeight() : c.getWidth(),
|
||||
0, UIScale.scale( (float) arc ) );
|
||||
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height, 0, arc );
|
||||
|
||||
if( borderColor != null ) {
|
||||
g.setColor( borderColor );
|
||||
FlatUIUtils.paintOutlinedComponent( (Graphics2D) g, 0, 0, width, height,
|
||||
0, 0, 0, UIScale.scale( 1f ), arc, null, borderColor, null );
|
||||
}
|
||||
|
||||
g.setClip( oldClip );
|
||||
|
||||
@@ -98,8 +104,15 @@ public class FlatJidePainter
|
||||
if( !horizontal )
|
||||
g.translate( 0, 1 );
|
||||
} else {
|
||||
FlatUIUtils.paintComponentBackground( (Graphics2D) g, rect.x, rect.y,
|
||||
rect.width, rect.height, 0, UIScale.scale( (float) arc ) );
|
||||
FlatUIUtils.paintComponentBackground( (Graphics2D) g,
|
||||
rect.x, rect.y, rect.width, rect.height, 0, arc );
|
||||
|
||||
if( borderColor != null ) {
|
||||
g.setColor( borderColor );
|
||||
FlatUIUtils.paintOutlinedComponent( (Graphics2D) g,
|
||||
rect.x, rect.y, rect.width, rect.height,
|
||||
0, 0, 0, UIScale.scale( 1f ), arc, null, borderColor, null );
|
||||
}
|
||||
}
|
||||
|
||||
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
|
||||
|
||||
@@ -25,9 +25,11 @@ import java.awt.geom.Rectangle2D;
|
||||
import javax.swing.ButtonModel;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.PopupMenuUI;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
import com.jidesoft.plaf.LookAndFeelFactory;
|
||||
@@ -54,6 +56,14 @@ public class FlatJideSplitButtonUI
|
||||
// but it does not because FlatLaf already has added the UI class to the UI defaults
|
||||
LookAndFeelFactory.installJideExtension();
|
||||
|
||||
// workaround for bug in JideSplitButton, which overrides JMenu.updateUI(),
|
||||
// but does not invoke super.updateUI() to update UI of JMenu.popupMenu field
|
||||
if( c instanceof JideSplitButton ) {
|
||||
JPopupMenu popupMenu = ((JideSplitButton)c).getPopupMenu();
|
||||
if( popupMenu != null )
|
||||
popupMenu.setUI( (PopupMenuUI) UIManager.getUI( popupMenu ) );
|
||||
}
|
||||
|
||||
return new FlatJideSplitButtonUI();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.awt.Dialog;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Frame;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
@@ -42,6 +43,7 @@ import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.EventListenerList;
|
||||
import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
|
||||
import com.formdev.flatlaf.util.SystemInfo;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
@@ -326,6 +328,8 @@ public class FlatWindowsNativeWindowBorder
|
||||
HTMINBUTTON = 8,
|
||||
HTMAXBUTTON = 9,
|
||||
HTTOP = 12,
|
||||
HTTOPLEFT = 13,
|
||||
HTTOPRIGHT = 14,
|
||||
HTCLOSE = 20;
|
||||
|
||||
private static final int ABS_AUTOHIDE = 0x0000001;
|
||||
@@ -674,6 +678,35 @@ public class FlatWindowsNativeWindowBorder
|
||||
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
|
||||
Point pt = scaleDown( x, y );
|
||||
|
||||
int resizeBorderHeight = getResizeHandleHeight();
|
||||
boolean isOnResizeBorder = (y < resizeBorderHeight) &&
|
||||
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
|
||||
|
||||
// limit top resize border to 4px, which seems to be standard for modern WinUI apps
|
||||
if( isOnResizeBorder && pt.y > UIScale.scale( 4 ) )
|
||||
isOnResizeBorder = false;
|
||||
|
||||
if( isOnResizeBorder ) {
|
||||
Insets insets = window.getInsets();
|
||||
|
||||
// return HTTOPLEFT if mouse is over top resize border on left side
|
||||
// - hovering mouse shows top-left resize cursor
|
||||
// - left-click-and-drag resizes window
|
||||
if( pt.x <= insets.left + UIScale.scale( 12 ) )
|
||||
return new LRESULT( HTTOPLEFT );
|
||||
|
||||
// return HTTOPRIGHT if mouse is over top resize border on right side
|
||||
// - hovering mouse shows top-right resize cursor
|
||||
// - left-click-and-drag resizes window
|
||||
if( pt.x >= window.getWidth() - insets.right - UIScale.scale( 12 ) )
|
||||
return new LRESULT( HTTOPRIGHT );
|
||||
|
||||
// return HTTOP if mouse is over top resize border
|
||||
// - hovering mouse shows vertical resize cursor
|
||||
// - left-click-and-drag vertically resizes window
|
||||
return new LRESULT( HTTOP );
|
||||
}
|
||||
|
||||
// return HTSYSMENU if mouse is over application icon
|
||||
// - left-click on HTSYSMENU area shows system menu
|
||||
// - double-left-click sends WM_CLOSE
|
||||
@@ -697,16 +730,6 @@ public class FlatWindowsNativeWindowBorder
|
||||
if( contains( closeButtonBounds, pt ) )
|
||||
return new LRESULT( HTCLOSE );
|
||||
|
||||
int resizeBorderHeight = getResizeHandleHeight();
|
||||
boolean isOnResizeBorder = (y < resizeBorderHeight) &&
|
||||
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
|
||||
|
||||
// return HTTOP if mouse is over top resize border
|
||||
// - hovering mouse shows vertical resize cursor
|
||||
// - left-click and drag vertically resizes window
|
||||
if( isOnResizeBorder )
|
||||
return new LRESULT( HTTOP );
|
||||
|
||||
boolean isOnTitleBar = (pt.y < titleBarHeight);
|
||||
if( isOnTitleBar ) {
|
||||
// return HTCLIENT if mouse is over any Swing component in title bar
|
||||
|
||||
@@ -25,6 +25,7 @@ To build the library on Linux, some packages needs to be installed:
|
||||
|
||||
- `build-essential` - GCC and development tools
|
||||
- `libxt-dev` - X11 toolkit development headers
|
||||
- `libgtk-3-dev` - GTK 3 toolkit development headers
|
||||
- `g++-aarch64-linux-gnu` - GNU C++ compiler for the arm64 architecture (only on
|
||||
x86_64 Linux for cross-compiling for arm64 architecture)
|
||||
|
||||
@@ -32,19 +33,39 @@ To build the library on Linux, some packages needs to be installed:
|
||||
### Ubuntu
|
||||
|
||||
~~~
|
||||
sudo apt update
|
||||
sudo apt install build-essential libxt-dev
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential libxt-dev libgtk-3-dev
|
||||
~~~
|
||||
|
||||
Only on x86_64 Linux for cross-compiling for arm64 architecture:
|
||||
#### Cross-compile for arm64 architecture on x86_64 Linux
|
||||
|
||||
Only needed on x86_64 Linux if you want cross-compile for arm64 architecture:
|
||||
|
||||
~~~
|
||||
sudo apt install g++-aarch64-linux-gnu
|
||||
sudo apt-get install g++-aarch64-linux-gnu
|
||||
~~~
|
||||
|
||||
Download `libgtk-3.so` for arm64 architecture:
|
||||
|
||||
~~~
|
||||
cd flatlaf-natives/flatlaf-natives-linux/lib/aarch64
|
||||
wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
|
||||
ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
|
||||
tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
|
||||
rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
|
||||
~~~
|
||||
|
||||
|
||||
### Fedora
|
||||
|
||||
~~~
|
||||
sudo dnf group install c-development
|
||||
sudo dnf install libXt-devel gtk3-devel
|
||||
~~~
|
||||
|
||||
|
||||
### CentOS
|
||||
|
||||
~~~
|
||||
sudo yum install libXt-devel
|
||||
sudo yum install libXt-devel gtk3-devel
|
||||
~~~
|
||||
|
||||
@@ -65,15 +65,37 @@ tasks {
|
||||
|
||||
includes.from(
|
||||
"${javaHome}/include",
|
||||
"${javaHome}/include/linux"
|
||||
"${javaHome}/include/linux",
|
||||
|
||||
// for GTK
|
||||
"/usr/include/gtk-3.0",
|
||||
"/usr/include/glib-2.0",
|
||||
if( name.contains( "X86-64" ) ) "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
|
||||
else "/usr/lib/aarch64-linux-gnu/glib-2.0/include",
|
||||
"/usr/include/gdk-pixbuf-2.0",
|
||||
"/usr/include/atk-1.0",
|
||||
"/usr/include/cairo",
|
||||
"/usr/include/pango-1.0",
|
||||
"/usr/include/harfbuzz",
|
||||
)
|
||||
|
||||
compilerArgs.addAll( toolChain.map {
|
||||
when( it ) {
|
||||
is Gcc, is Clang -> listOf()
|
||||
is Gcc, is Clang -> listOf( "-fvisibility=hidden" )
|
||||
else -> emptyList()
|
||||
}
|
||||
} )
|
||||
|
||||
doFirst {
|
||||
// check required Java version
|
||||
if( JavaVersion.current() < JavaVersion.VERSION_11 ) {
|
||||
println()
|
||||
println( "WARNING: Java 11 or later required to build Linux native library (running ${System.getProperty( "java.version" )})" )
|
||||
println( " Native library built with older Java versions throw following exception when running in Java 17+:" )
|
||||
println( " java.lang.UnsatisfiedLinkError: .../libjawt.so: version `SUNWprivate_1.1' not found" )
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withType<LinkSharedLibrary>().configureEach {
|
||||
@@ -88,7 +110,7 @@ tasks {
|
||||
|
||||
linkerArgs.addAll( toolChain.map {
|
||||
when( it ) {
|
||||
is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}" )
|
||||
is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}", "-lgtk-3" )
|
||||
else -> emptyList()
|
||||
}
|
||||
} )
|
||||
@@ -128,7 +150,20 @@ tasks {
|
||||
"-I", "${javaHome}/include/linux",
|
||||
"-I", "$include",
|
||||
|
||||
// for GTK
|
||||
"-I", "/usr/include/gtk-3.0",
|
||||
"-I", "/usr/include/glib-2.0",
|
||||
"-I", "/usr/lib/x86_64-linux-gnu/glib-2.0/include",
|
||||
"-I", "/usr/include/gdk-pixbuf-2.0",
|
||||
"-I", "/usr/include/atk-1.0",
|
||||
"-I", "/usr/include/cairo",
|
||||
"-I", "/usr/include/pango-1.0",
|
||||
"-I", "/usr/include/harfbuzz",
|
||||
|
||||
"$src/ApiVersion.cpp",
|
||||
"$src/GtkFileChooser.cpp",
|
||||
"$src/GtkMessageDialog.cpp",
|
||||
"$src/JNIUtils.cpp",
|
||||
"$src/X11WmUtils.cpp",
|
||||
)
|
||||
}
|
||||
@@ -152,10 +187,15 @@ tasks {
|
||||
"-o", "$outDir/$libraryName",
|
||||
|
||||
"$objDir/ApiVersion.o",
|
||||
"$objDir/GtkFileChooser.o",
|
||||
"$objDir/GtkMessageDialog.o",
|
||||
"$objDir/JNIUtils.o",
|
||||
"$objDir/X11WmUtils.o",
|
||||
|
||||
"-lstdc++",
|
||||
"-L${layout.projectDirectory}/lib/aarch64",
|
||||
"-ljawt",
|
||||
"-lgtk-3",
|
||||
)
|
||||
|
||||
doLast {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
// increase this version if changing API or functionality of native library
|
||||
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeLinuxLibrary
|
||||
#define API_VERSION_LINUX 3001
|
||||
#define API_VERSION_LINUX 3002
|
||||
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2025 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jawt.h>
|
||||
#include <linux/jawt_md.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include "JNIUtils.h"
|
||||
#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
* @since 3.7
|
||||
*/
|
||||
|
||||
// declare external methods
|
||||
extern Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return );
|
||||
|
||||
// declare internal methods
|
||||
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList );
|
||||
|
||||
//---- helper -----------------------------------------------------------------
|
||||
|
||||
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0)
|
||||
#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0)
|
||||
#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
|
||||
|
||||
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
|
||||
jclass stringClass = env->FindClass( "java/lang/String" );
|
||||
return env->NewObjectArray( count, stringClass, NULL );
|
||||
}
|
||||
|
||||
static void initFilters( GtkFileChooser* chooser, JNIEnv* env, jint fileTypeIndex, jobjectArray fileTypes ) {
|
||||
jint length = env->GetArrayLength( fileTypes );
|
||||
if( length <= 0 )
|
||||
return;
|
||||
|
||||
GtkFileFilter* filter = NULL;
|
||||
int filterIndex = 0;
|
||||
for( int i = 0; i < length; i++ ) {
|
||||
jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i );
|
||||
if( jstr == NULL ) {
|
||||
if( filter != NULL ) {
|
||||
gtk_file_chooser_add_filter( chooser, filter );
|
||||
if( fileTypeIndex == filterIndex )
|
||||
gtk_file_chooser_set_filter( chooser, filter );
|
||||
filter = NULL;
|
||||
filterIndex++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
AutoReleaseStringUTF8 str( env, jstr );
|
||||
if( filter == NULL ) {
|
||||
filter = gtk_file_filter_new();
|
||||
gtk_file_filter_set_name( filter, str );
|
||||
} else
|
||||
gtk_file_filter_add_pattern( filter, str );
|
||||
}
|
||||
}
|
||||
|
||||
static GdkWindow* getGdkWindow( JNIEnv* env, jobject window ) {
|
||||
// get the AWT
|
||||
JAWT awt;
|
||||
awt.version = JAWT_VERSION_1_4;
|
||||
if( !JAWT_GetAWT( env, &awt ) )
|
||||
return NULL;
|
||||
|
||||
// get Xlib window and display from AWT window
|
||||
Display* display;
|
||||
Window w = getWindowHandle( env, &awt, window, &display );
|
||||
if( w == 0 )
|
||||
return NULL;
|
||||
|
||||
// based on GetAllocNativeWindowHandle() from https://github.com/btzy/nativefiledialog-extended
|
||||
// https://github.com/btzy/nativefiledialog-extended/blob/29e3bcb578345b9fa345d1d7683f00c150565ca3/src/nfd_gtk.cpp#L384-L437
|
||||
GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay( display );
|
||||
if( gdkDisplay == NULL ) {
|
||||
// search for existing X11 display (there should only be one, even if multiple screens are connected)
|
||||
GdkDisplayManager* displayManager = gdk_display_manager_get();
|
||||
GSList* displays = gdk_display_manager_list_displays( displayManager );
|
||||
for( GSList* l = displays; l; l = l->next ) {
|
||||
if( GDK_IS_X11_DISPLAY( l->data ) ) {
|
||||
gdkDisplay = GDK_DISPLAY( l->data );
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_slist_free( displays );
|
||||
|
||||
// create our own X11 display
|
||||
if( gdkDisplay == NULL ) {
|
||||
gdk_set_allowed_backends( "x11" );
|
||||
gdkDisplay = gdk_display_manager_open_display( displayManager, NULL );
|
||||
gdk_set_allowed_backends( NULL );
|
||||
|
||||
if( gdkDisplay == NULL )
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return gdk_x11_window_foreign_new_for_display( gdkDisplay, w );
|
||||
}
|
||||
|
||||
static void handle_realize( GtkWidget* dialog, gpointer data ) {
|
||||
GdkWindow* gdkOwner = static_cast<GdkWindow*>( data );
|
||||
|
||||
// make file dialog a transient of owner window,
|
||||
// which centers file dialog on owner and keeps file dialog above owner
|
||||
gdk_window_set_transient_for( gtk_widget_get_window( dialog ), gdkOwner );
|
||||
|
||||
// necessary because gdk_x11_window_foreign_new_for_display() increases the reference counter
|
||||
g_object_unref( gdkOwner );
|
||||
}
|
||||
|
||||
struct ResponseData {
|
||||
JNIEnv* env;
|
||||
jobject callback;
|
||||
GSList* fileList;
|
||||
|
||||
ResponseData( JNIEnv* _env, jobject _callback ) {
|
||||
env = _env;
|
||||
callback = _callback;
|
||||
fileList = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) {
|
||||
// get filenames if user pressed OK
|
||||
if( responseId == GTK_RESPONSE_ACCEPT ) {
|
||||
ResponseData *response = static_cast<ResponseData*>( data );
|
||||
if( response->callback != NULL ) {
|
||||
GSList* fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
|
||||
jobjectArray files = fileListToStringArray( response->env, fileList );
|
||||
|
||||
GtkWindow* window = GTK_WINDOW( dialog );
|
||||
|
||||
// invoke callback: boolean approve( String[] files, long hwnd );
|
||||
jclass cls = response->env->GetObjectClass( response->callback );
|
||||
jmethodID approveID = response->env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
|
||||
if( approveID != NULL && !response->env->CallBooleanMethod( response->callback, approveID, files, window ) )
|
||||
return; // keep dialog open
|
||||
}
|
||||
|
||||
response->fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
|
||||
}
|
||||
|
||||
// hide/destroy file dialog and quit loop
|
||||
gtk_widget_hide( dialog );
|
||||
gtk_widget_destroy( dialog );
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
|
||||
( JNIEnv* env, jclass cls, jobject owner, jboolean open,
|
||||
jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder,
|
||||
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
|
||||
{
|
||||
// initialize GTK
|
||||
if( !gtk_init_check( NULL, NULL ) )
|
||||
return NULL;
|
||||
|
||||
// convert Java strings to C strings
|
||||
AutoReleaseStringUTF8 ctitle( env, title );
|
||||
AutoReleaseStringUTF8 cokButtonLabel( env, okButtonLabel );
|
||||
AutoReleaseStringUTF8 ccurrentName( env, currentName );
|
||||
AutoReleaseStringUTF8 ccurrentFolder( env, currentFolder );
|
||||
|
||||
// create GTK file chooser dialog
|
||||
// https://docs.gtk.org/gtk3/class.FileChooserDialog.html
|
||||
bool selectFolder = isOptionSet( FC_select_folder );
|
||||
bool multiSelect = isOptionSet( FC_select_multiple );
|
||||
GtkWidget* dialog = gtk_file_chooser_dialog_new(
|
||||
(ctitle != NULL) ? ctitle
|
||||
: (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder"))
|
||||
: (open ? ((multiSelect ? _("Open Files") : _("Open File"))) : _("Save File"))),
|
||||
NULL, // can not use AWT X11 window as parent because GtkWindow is required
|
||||
selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
|
||||
: (open ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE),
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
(cokButtonLabel != NULL) ? cokButtonLabel
|
||||
: (selectFolder ? _("_Select") : (open ? _("_Open") : _("_Save"))), GTK_RESPONSE_ACCEPT,
|
||||
NULL ); // marks end of buttons
|
||||
GtkFileChooser* chooser = GTK_FILE_CHOOSER( dialog );
|
||||
|
||||
// set current name and folder
|
||||
if( !open && ccurrentName != NULL )
|
||||
gtk_file_chooser_set_current_name( chooser, ccurrentName );
|
||||
if( ccurrentFolder != NULL )
|
||||
gtk_file_chooser_set_current_folder( chooser, ccurrentFolder );
|
||||
|
||||
// set options
|
||||
if( isOptionSetOrClear( FC_select_multiple ) )
|
||||
gtk_file_chooser_set_select_multiple( chooser, isOptionSet( FC_select_multiple ) );
|
||||
if( isOptionSetOrClear( FC_show_hidden ) )
|
||||
gtk_file_chooser_set_show_hidden( chooser, isOptionSet( FC_show_hidden ) );
|
||||
if( isOptionSetOrClear( FC_local_only ) )
|
||||
gtk_file_chooser_set_local_only( chooser, isOptionSet( FC_local_only ) );
|
||||
if( isOptionSetOrClear( FC_do_overwrite_confirmation ) )
|
||||
gtk_file_chooser_set_do_overwrite_confirmation( chooser, isOptionSet( FC_do_overwrite_confirmation ) );
|
||||
if( isOptionSetOrClear( FC_create_folders ) )
|
||||
gtk_file_chooser_set_create_folders( chooser, isOptionSet( FC_create_folders ) );
|
||||
|
||||
// initialize filter
|
||||
initFilters( chooser, env, fileTypeIndex, fileTypes );
|
||||
|
||||
// setup modality
|
||||
GdkWindow* gdkOwner = (owner != NULL) ? getGdkWindow( env, owner ) : NULL;
|
||||
if( gdkOwner != NULL ) {
|
||||
gtk_window_set_modal( GTK_WINDOW( dialog ), true );
|
||||
|
||||
// file dialog should use same screen as owner
|
||||
gtk_window_set_screen( GTK_WINDOW( dialog ), gdk_window_get_screen( gdkOwner ) );
|
||||
|
||||
// set the transient when the file dialog is realized
|
||||
g_signal_connect( dialog, "realize", G_CALLBACK( handle_realize ), gdkOwner );
|
||||
}
|
||||
|
||||
// show dialog
|
||||
// (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c)
|
||||
ResponseData responseData( env, callback );
|
||||
g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &responseData );
|
||||
gtk_widget_show( dialog );
|
||||
|
||||
// necessary to bring file dialog to the front (and make it active)
|
||||
// see issues:
|
||||
// https://github.com/btzy/nativefiledialog-extended/issues/31
|
||||
// https://github.com/mlabbe/nativefiledialog/pull/92
|
||||
// https://github.com/guillaumechereau/noc/pull/11
|
||||
if( GDK_IS_X11_DISPLAY( gtk_widget_get_display( GTK_WIDGET( dialog ) ) ) ) {
|
||||
GdkWindow* gdkWindow = gtk_widget_get_window( GTK_WIDGET( dialog ) );
|
||||
gdk_window_set_events( gdkWindow, static_cast<GdkEventMask>( gdk_window_get_events( gdkWindow ) | GDK_PROPERTY_CHANGE_MASK ) );
|
||||
gtk_window_present_with_time( GTK_WINDOW( dialog ), gdk_x11_get_server_time( gdkWindow ) );
|
||||
}
|
||||
|
||||
// start event loop (will be quit in respone handler)
|
||||
gtk_main();
|
||||
|
||||
// canceled?
|
||||
if( responseData.fileList == NULL )
|
||||
return newJavaStringArray( env, 0 );
|
||||
|
||||
// convert GSList to Java string array
|
||||
return fileListToStringArray( env, responseData.fileList );
|
||||
}
|
||||
|
||||
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList ) {
|
||||
guint count = g_slist_length( fileList );
|
||||
jobjectArray array = newJavaStringArray( env, count );
|
||||
GSList* it = fileList;
|
||||
for( int i = 0; i < count; i++, it = it->next ) {
|
||||
gchar* path = (gchar*) it->data;
|
||||
jstring jpath = env->NewStringUTF( path );
|
||||
g_free( path );
|
||||
|
||||
env->SetObjectArrayElement( array, i, jpath );
|
||||
env->DeleteLocalRef( jpath );
|
||||
}
|
||||
g_slist_free( fileList );
|
||||
return array;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2025 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jawt.h>
|
||||
#include <linux/jawt_md.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include "JNIUtils.h"
|
||||
#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
* @since 3.7
|
||||
*/
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showMessageDialog
|
||||
( JNIEnv* env, jclass cls, jlong hwndParent, jint messageType, jstring primaryText, jstring secondaryText,
|
||||
jint defaultButton, jobjectArray buttons )
|
||||
{
|
||||
GtkWindow* window = (GtkWindow*) hwndParent;
|
||||
|
||||
// convert message type
|
||||
GtkMessageType gmessageType;
|
||||
switch( messageType ) {
|
||||
case /* JOptionPane.ERROR_MESSAGE */ 0: gmessageType = GTK_MESSAGE_ERROR; break;
|
||||
case /* JOptionPane.INFORMATION_MESSAGE */ 1: gmessageType = GTK_MESSAGE_INFO; break;
|
||||
case /* JOptionPane.WARNING_MESSAGE */ 2: gmessageType = GTK_MESSAGE_WARNING; break;
|
||||
case /* JOptionPane.QUESTION_MESSAGE */ 3: gmessageType = GTK_MESSAGE_QUESTION; break;
|
||||
default:
|
||||
case /* JOptionPane.PLAIN_MESSAGE */ -1: gmessageType = GTK_MESSAGE_OTHER; break;
|
||||
}
|
||||
|
||||
// convert Java strings to C strings
|
||||
AutoReleaseStringUTF8 cprimaryText( env, primaryText );
|
||||
AutoReleaseStringUTF8 csecondaryText( env, secondaryText );
|
||||
|
||||
// create GTK file chooser dialog
|
||||
// https://docs.gtk.org/gtk3/class.MessageDialog.html
|
||||
jint buttonCount = env->GetArrayLength( buttons );
|
||||
GtkWidget* dialog = gtk_message_dialog_new( window, GTK_DIALOG_MODAL, gmessageType,
|
||||
(buttonCount > 0) ? GTK_BUTTONS_NONE : GTK_BUTTONS_OK,
|
||||
"%s", (const gchar*) cprimaryText );
|
||||
if( csecondaryText != NULL )
|
||||
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( dialog ), "%s", (const gchar*) csecondaryText );
|
||||
|
||||
// add buttons
|
||||
for( int i = 0; i < buttonCount; i++ ) {
|
||||
AutoReleaseStringUTF8 str( env, (jstring) env->GetObjectArrayElement( buttons, i ) );
|
||||
gtk_dialog_add_button( GTK_DIALOG( dialog ), str, i );
|
||||
}
|
||||
|
||||
// set default button
|
||||
gtk_dialog_set_default_response( GTK_DIALOG( dialog ), MIN( MAX( defaultButton, 0 ), buttonCount - 1 ) );
|
||||
|
||||
// show message dialog
|
||||
gint responseID = gtk_dialog_run( GTK_DIALOG( dialog ) );
|
||||
gtk_widget_destroy( dialog );
|
||||
|
||||
// return -1 if closed with ESC key
|
||||
return (responseID >= 0) ? responseID : -1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2024 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// avoid inlining of printf()
|
||||
#define _NO_CRT_STDIO_INLINE
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include "JNIUtils.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
//---- class AutoReleaseStringUTF8 --------------------------------------------
|
||||
|
||||
AutoReleaseStringUTF8::AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString ) {
|
||||
env = _env;
|
||||
javaString = _javaString;
|
||||
chars = (javaString != NULL) ? env->GetStringUTFChars( javaString, NULL ) : NULL;
|
||||
}
|
||||
|
||||
AutoReleaseStringUTF8::~AutoReleaseStringUTF8() {
|
||||
if( chars != NULL )
|
||||
env->ReleaseStringUTFChars( javaString, chars );
|
||||
}
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_isLibAvailable
|
||||
( JNIEnv* env, jclass cls, jstring libname )
|
||||
{
|
||||
AutoReleaseStringUTF8 clibname( env, libname );
|
||||
|
||||
void* lib = dlopen( clibname, RTLD_LAZY );
|
||||
if( lib == NULL )
|
||||
return false;
|
||||
|
||||
dlclose( lib );
|
||||
return true;
|
||||
}
|
||||
@@ -25,18 +25,21 @@
|
||||
*/
|
||||
|
||||
|
||||
bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
|
||||
long data0, long data1, long data2, long data3, long data4 );
|
||||
bool isWMHintSupported( Display* display, Window rootWindow, Atom atom );
|
||||
// declare exported methods
|
||||
Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return );
|
||||
|
||||
// declare internal methods
|
||||
static bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
|
||||
long data0, long data1, long data2, long data3, long data4 );
|
||||
static bool isWMHintSupported( Display* display, Window rootWindow, Atom atom );
|
||||
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Send _NET_WM_MOVERESIZE to window to initiate moving or resizing.
|
||||
*
|
||||
* https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45446104441728
|
||||
* https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#id-1.5.4
|
||||
* https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/x11/gdksurface-x11.c#L3841-3881
|
||||
*/
|
||||
extern "C"
|
||||
@@ -79,7 +82,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xS
|
||||
0 );
|
||||
}
|
||||
|
||||
bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
|
||||
static bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
|
||||
long data0, long data1, long data2, long data3, long data4 )
|
||||
{
|
||||
// get the AWT
|
||||
@@ -131,7 +134,7 @@ bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
|
||||
}
|
||||
|
||||
|
||||
bool isWMHintSupported( Display* display, Window rootWindow, Atom atom ) {
|
||||
static bool isWMHintSupported( Display* display, Window rootWindow, Atom atom ) {
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long n_atoms;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <jni.h>
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
//---- class AutoReleaseStringUTF8 --------------------------------------------
|
||||
|
||||
class AutoReleaseStringUTF8 {
|
||||
JNIEnv* env;
|
||||
jstring javaString;
|
||||
const char* chars;
|
||||
|
||||
public:
|
||||
AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString );
|
||||
~AutoReleaseStringUTF8();
|
||||
|
||||
operator const gchar*() { return chars; }
|
||||
};
|
||||
@@ -9,6 +9,18 @@ extern "C" {
|
||||
#endif
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder 1L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple 2L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden 4L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only 8L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation 16L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders 32L
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
|
||||
* Method: xMoveOrResizeWindow
|
||||
@@ -25,6 +37,30 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xM
|
||||
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xShowWindowMenu
|
||||
(JNIEnv *, jclass, jobject, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
|
||||
* Method: isLibAvailable
|
||||
* Signature: (Ljava/lang/String;)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_isLibAvailable
|
||||
(JNIEnv *, jclass, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
|
||||
* Method: showFileChooser
|
||||
* Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeLinuxLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
|
||||
(JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
|
||||
* Method: showMessageDialog
|
||||
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showMessageDialog
|
||||
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -75,7 +75,7 @@ tasks {
|
||||
|
||||
compilerArgs.addAll( toolChain.map {
|
||||
when( it ) {
|
||||
is Gcc, is Clang -> listOf( "-x", "objective-c++", "-mmacosx-version-min=$minOs" )
|
||||
is Gcc, is Clang -> listOf( "-x", "objective-c++", "-mmacosx-version-min=$minOs", "-fvisibility=hidden" )
|
||||
else -> emptyList()
|
||||
}
|
||||
} )
|
||||
|
||||
@@ -46,7 +46,33 @@
|
||||
JNI_COCOA_CATCH() \
|
||||
}
|
||||
|
||||
#define JNI_THREAD_ENTER( jvm, returnValue ) \
|
||||
JNIEnv *env; \
|
||||
bool detach = false; \
|
||||
switch( jvm->GetEnv( (void**) &env, JNI_VERSION_1_6 ) ) { \
|
||||
case JNI_OK: break; \
|
||||
case JNI_EDETACHED: \
|
||||
if( jvm->AttachCurrentThread( (void**) &env, NULL ) != JNI_OK ) \
|
||||
return returnValue; \
|
||||
detach = true; \
|
||||
break; \
|
||||
default: return returnValue; \
|
||||
} \
|
||||
@try {
|
||||
|
||||
#define JNI_THREAD_EXIT( jvm ) \
|
||||
} @finally { \
|
||||
if( env->ExceptionCheck() ) \
|
||||
env->ExceptionDescribe(); \
|
||||
if( detach ) \
|
||||
jvm->DetachCurrentThread(); \
|
||||
}
|
||||
|
||||
|
||||
jclass findClass( JNIEnv *env, const char* className, bool globalRef );
|
||||
jfieldID getFieldID( JNIEnv *env, jclass cls, const char* fieldName, const char* fieldSignature, bool staticField );
|
||||
jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const char* methodSignature, bool staticMethod );
|
||||
|
||||
NSString* JavaToNSString( JNIEnv *env, jstring javaString );
|
||||
jstring NSToJavaString( JNIEnv *env, NSString *nsString );
|
||||
jstring NormalizedPathJavaFromNSString( JNIEnv* env, NSString *nsString );
|
||||
|
||||
@@ -13,6 +13,32 @@ extern "C" {
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM 1L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE 2L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles 1L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories 2L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases 4L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsMultipleSelection
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsMultipleSelection 8L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_accessoryViewDisclosed
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_accessoryViewDisclosed 16L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField 256L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories 512L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension 1024L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles 2048L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden 4096L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes 8192L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories 16384L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showSingleFilterField
|
||||
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showSingleFilterField 16777216L
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
|
||||
* Method: setWindowRoundedBorder
|
||||
@@ -53,6 +79,22 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi
|
||||
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen
|
||||
(JNIEnv *, jclass, jobject);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
|
||||
* Method: showFileChooser
|
||||
* Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeMacLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
|
||||
(JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
|
||||
* Method: showMessageDialog
|
||||
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showMessageDialog
|
||||
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
|
||||
#import <jni.h>
|
||||
#import "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
// increase this version if changing API or functionality of native library
|
||||
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeMacLibrary
|
||||
#define API_VERSION_MACOS 2001
|
||||
#define API_VERSION_MACOS 2002
|
||||
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
@@ -75,3 +75,38 @@ jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const ch
|
||||
|
||||
return methodID;
|
||||
}
|
||||
|
||||
NSString* JavaToNSString( JNIEnv *env, jstring javaString ) {
|
||||
if( javaString == NULL )
|
||||
return NULL;
|
||||
|
||||
int len = env->GetStringLength( javaString );
|
||||
const jchar* chars = env->GetStringChars( javaString, NULL );
|
||||
if( chars == NULL )
|
||||
return NULL;
|
||||
|
||||
NSString* nsString = [NSString stringWithCharacters:(unichar*)chars length:len];
|
||||
env->ReleaseStringChars( javaString, chars );
|
||||
return nsString;
|
||||
}
|
||||
|
||||
jstring NSToJavaString( JNIEnv *env, NSString *nsString ) {
|
||||
if( nsString == NULL )
|
||||
return NULL;
|
||||
|
||||
jsize len = [nsString length];
|
||||
unichar* buffer = (unichar*) calloc( len, sizeof( unichar ) );
|
||||
if( buffer == NULL )
|
||||
return NULL;
|
||||
|
||||
[nsString getCharacters:buffer];
|
||||
jstring javaString = env->NewString( buffer, len );
|
||||
free( buffer );
|
||||
return javaString;
|
||||
}
|
||||
|
||||
jstring NormalizedPathJavaFromNSString( JNIEnv* env, NSString *nsString ) {
|
||||
return (nsString != NULL)
|
||||
? NSToJavaString( env, [nsString precomposedStringWithCanonicalMapping] )
|
||||
: NULL;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
/*
|
||||
* Copyright 2024 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <jni.h>
|
||||
#import "JNIUtils.h"
|
||||
#import "JNFRunLoop.h"
|
||||
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
* @since 3.7
|
||||
*/
|
||||
|
||||
// declare internal methods
|
||||
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count );
|
||||
static jobjectArray urlsToStringArray( JNIEnv* env, NSArray* urls );
|
||||
static NSArray* getDialogURLs( NSSavePanel* dialog );
|
||||
|
||||
//---- class FileChooserDelegate ----------------------------------------------
|
||||
|
||||
@interface FileChooserDelegate : NSObject <NSOpenSavePanelDelegate, NSWindowDelegate> {
|
||||
NSArray* _filters;
|
||||
|
||||
JavaVM* _jvm;
|
||||
jobject _callback;
|
||||
NSMutableSet* _urlsSet;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) NSSavePanel* dialog;
|
||||
|
||||
- (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex
|
||||
:(NSString*)filterFieldLabel :(bool)showSingleFilterField;
|
||||
- (void) selectFormat: (id)sender;
|
||||
- (void) selectFormatAtIndex: (int)index;
|
||||
@end
|
||||
|
||||
@implementation FileChooserDelegate
|
||||
|
||||
- (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex
|
||||
:(NSString*)filterFieldLabel :(bool)showSingleFilterField
|
||||
{
|
||||
_filters = filters;
|
||||
|
||||
// get filter names
|
||||
NSArray* filterNames = filters.lastObject;
|
||||
[filters removeLastObject];
|
||||
|
||||
// do not add filter/format combobox if there is only one filter
|
||||
if( filters.count <= 1 && !showSingleFilterField ) {
|
||||
[self selectFormatAtIndex:0];
|
||||
return;
|
||||
}
|
||||
|
||||
// create label
|
||||
NSTextField* label = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
label.stringValue = (filterFieldLabel != NULL) ? filterFieldLabel : @"Format:";
|
||||
label.editable = NO;
|
||||
label.bordered = NO;
|
||||
label.bezeled = NO;
|
||||
label.drawsBackground = NO;
|
||||
|
||||
// create combobox
|
||||
NSPopUpButton* popupButton = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
|
||||
[popupButton addItemsWithTitles:filterNames];
|
||||
[popupButton selectItemAtIndex:MIN( MAX( filterIndex, 0 ), filterNames.count - 1 )];
|
||||
[popupButton setTarget:self];
|
||||
[popupButton setAction:@selector(selectFormat:)];
|
||||
|
||||
// create view
|
||||
NSView* accessoryView = [[NSView alloc] initWithFrame:NSZeroRect];
|
||||
[accessoryView addSubview:label];
|
||||
[accessoryView addSubview:popupButton];
|
||||
|
||||
// autolayout
|
||||
label.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
popupButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
int labelWidth = label.intrinsicContentSize.width;
|
||||
int gap = 12;
|
||||
int popupButtonWidth = popupButton.intrinsicContentSize.width;
|
||||
int popupButtonMinimumWidth = 140;
|
||||
int totalWidth = labelWidth + gap + MAX( popupButtonWidth, popupButtonMinimumWidth );
|
||||
[accessoryView addConstraints:@[
|
||||
// horizontal layout
|
||||
[label.leadingAnchor constraintEqualToAnchor:accessoryView.centerXAnchor constant:-(totalWidth / 2)],
|
||||
[popupButton.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:gap],
|
||||
[popupButton.widthAnchor constraintGreaterThanOrEqualToConstant:popupButtonMinimumWidth],
|
||||
|
||||
// vertical layout
|
||||
[popupButton.topAnchor constraintEqualToAnchor:accessoryView.topAnchor constant:8],
|
||||
[popupButton.bottomAnchor constraintEqualToAnchor:accessoryView.bottomAnchor constant:-8],
|
||||
[label.firstBaselineAnchor constraintEqualToAnchor:popupButton.firstBaselineAnchor],
|
||||
]];
|
||||
|
||||
[_dialog setAccessoryView:accessoryView];
|
||||
|
||||
// initial filter
|
||||
[self selectFormatAtIndex:filterIndex];
|
||||
}
|
||||
|
||||
- (void) selectFormat: (id)sender {
|
||||
NSPopUpButton* popupButton = (NSPopUpButton*) sender;
|
||||
[self selectFormatAtIndex:popupButton.indexOfSelectedItem];
|
||||
}
|
||||
|
||||
- (void) selectFormatAtIndex: (int)index {
|
||||
index = MIN( MAX( index, 0 ), _filters.count - 1 );
|
||||
NSArray* fileTypes = [_filters objectAtIndex:index];
|
||||
|
||||
// use deprecated allowedFileTypes instead of newer allowedContentTypes (since macOS 11+)
|
||||
// to support older macOS versions 10.14+ and because of some problems with allowedContentTypes:
|
||||
// https://github.com/chromium/chromium/blob/d8e0032963b7ca4728ff4117933c0feb3e479b7a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm#L209-232
|
||||
_dialog.allowedFileTypes = [fileTypes containsObject:@"*"] ? nil : fileTypes;
|
||||
}
|
||||
|
||||
//---- NSOpenSavePanelDelegate ----
|
||||
|
||||
- (void) initCallback: (JavaVM*)jvm :(jobject)callback {
|
||||
_jvm = jvm;
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
- (BOOL) panel: (id) sender validateURL:(NSURL*) url error:(NSError**) outError {
|
||||
JNI_COCOA_TRY()
|
||||
|
||||
if( _callback == NULL )
|
||||
return true;
|
||||
|
||||
NSArray* urls = getDialogURLs( sender );
|
||||
|
||||
// if multiple files are selected for opening, then the validateURL method
|
||||
// is invoked for earch file, but our callback should be invoked only once for all files
|
||||
if( urls != NULL && urls.count > 1 ) {
|
||||
if( _urlsSet == NULL ) {
|
||||
// invoked for first selected file --> invoke callback
|
||||
_urlsSet = [NSMutableSet setWithArray:urls];
|
||||
[_urlsSet removeObject:url];
|
||||
} else {
|
||||
// invoked for other selected files --> do not invoke callback
|
||||
[_urlsSet removeObject:url];
|
||||
if( _urlsSet.count == 0 )
|
||||
_urlsSet = NULL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
JNI_THREAD_ENTER( _jvm, true )
|
||||
|
||||
jobjectArray files = urlsToStringArray( env, urls );
|
||||
jlong window = (jlong) sender;
|
||||
|
||||
// invoke callback: boolean approve( String[] files, long hwnd );
|
||||
jclass cls = env->GetObjectClass( _callback );
|
||||
jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
|
||||
if( approveID != NULL && !env->CallBooleanMethod( _callback, approveID, files, window ) ) {
|
||||
_urlsSet = NULL;
|
||||
return false; // keep dialog open
|
||||
}
|
||||
|
||||
JNI_THREAD_EXIT( _jvm )
|
||||
JNI_COCOA_CATCH()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//---- NSWindowDelegate ----
|
||||
|
||||
- (void) windowDidBecomeMain:(NSNotification *) notification {
|
||||
JNI_COCOA_TRY()
|
||||
|
||||
// Disable main menu bar because the file dialog is modal and it should be not possible
|
||||
// to select any menu item. Otherwiese an action could show a Swing dialog, which would
|
||||
// be shown under the file dialog.
|
||||
//
|
||||
// NOTE: It is not necessary to re-enable the main menu bar because Swing does this itself.
|
||||
// When the file dialog is closed and a Swing window becomes active,
|
||||
// macOS sends windowDidBecomeMain (and windowDidBecomeKey) message to AWTWindow,
|
||||
// which invokes [self activateWindowMenuBar],
|
||||
// which invokes [CMenuBar activate:menuBar modallyDisabled:isDisabled],
|
||||
// which updates main menu bar.
|
||||
NSMenu* mainMenu = [NSApp mainMenu];
|
||||
int count = [mainMenu numberOfItems];
|
||||
for( int i = 0; i < count; i++ ) {
|
||||
NSMenuItem* menuItem = [mainMenu itemAtIndex:i];
|
||||
NSMenu *subenu = [menuItem submenu];
|
||||
if( [subenu isJavaMenu] )
|
||||
[menuItem setEnabled:NO];
|
||||
}
|
||||
|
||||
JNI_COCOA_CATCH()
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//---- helper -----------------------------------------------------------------
|
||||
|
||||
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
|
||||
#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
|
||||
#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
|
||||
|
||||
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
|
||||
jclass stringClass = env->FindClass( "java/lang/String" );
|
||||
return env->NewObjectArray( count, stringClass, NULL );
|
||||
}
|
||||
|
||||
static NSMutableArray* initFilters( JNIEnv* env, jobjectArray fileTypes ) {
|
||||
jint length = env->GetArrayLength( fileTypes );
|
||||
if( length <= 0 )
|
||||
return NULL;
|
||||
|
||||
NSMutableArray* filterNames = [NSMutableArray array];
|
||||
NSMutableArray* filters = [NSMutableArray array];
|
||||
NSString* filterName = NULL;
|
||||
NSMutableArray* filter = NULL;
|
||||
for( int i = 0; i < length; i++ ) {
|
||||
jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i );
|
||||
if( jstr == NULL ) {
|
||||
if( filter != NULL ) {
|
||||
if( filter.count > 0 ) {
|
||||
[filterNames addObject:filterName];
|
||||
[filters addObject:filter];
|
||||
}
|
||||
filterName = NULL;
|
||||
filter = NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString* str = JavaToNSString( env, jstr );
|
||||
env->DeleteLocalRef( jstr );
|
||||
if( filter == NULL ) {
|
||||
filterName = str;
|
||||
filter = [NSMutableArray array];
|
||||
} else
|
||||
[filter addObject:str];
|
||||
}
|
||||
|
||||
if( filters.count == 0 )
|
||||
return NULL;
|
||||
|
||||
// add filter names to array (removed again after creating combobox)
|
||||
[filters addObject:filterNames];
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
|
||||
( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open,
|
||||
jstring title, jstring prompt, jstring message, jstring filterFieldLabel,
|
||||
jstring nameFieldLabel, jstring nameFieldStringValue, jstring directoryURL,
|
||||
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
|
||||
{
|
||||
JNI_COCOA_ENTER()
|
||||
|
||||
JavaVM* jvm;
|
||||
if( env->GetJavaVM( &jvm ) != JNI_OK )
|
||||
return NULL;
|
||||
|
||||
// convert Java strings to NSString (on Java thread)
|
||||
NSString* nsTitle = JavaToNSString( env, title );
|
||||
NSString* nsPrompt = JavaToNSString( env, prompt );
|
||||
NSString* nsMessage = JavaToNSString( env, message );
|
||||
NSString* nsFilterFieldLabel = JavaToNSString( env, filterFieldLabel );
|
||||
NSString* nsNameFieldLabel = JavaToNSString( env, nameFieldLabel );
|
||||
NSString* nsNameFieldStringValue = JavaToNSString( env, nameFieldStringValue );
|
||||
NSString* nsDirectoryURL = JavaToNSString( env, directoryURL );
|
||||
NSMutableArray* filters = initFilters( env, fileTypes );
|
||||
|
||||
NSArray* urls = NULL;
|
||||
NSArray** purls = &urls;
|
||||
|
||||
// show file dialog on macOS thread
|
||||
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
|
||||
JNI_COCOA_TRY()
|
||||
|
||||
// create open/save panel
|
||||
NSSavePanel* dialog = open ? [NSOpenPanel openPanel] : [NSSavePanel savePanel];
|
||||
|
||||
// set appearance
|
||||
if( dark == 1 )
|
||||
dialog.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
|
||||
else if( dark == 0 )
|
||||
dialog.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
|
||||
|
||||
if( nsTitle != NULL )
|
||||
dialog.title = nsTitle;
|
||||
if( nsPrompt != NULL )
|
||||
dialog.prompt = nsPrompt;
|
||||
if( nsMessage != NULL )
|
||||
dialog.message = nsMessage;
|
||||
if( nsNameFieldLabel != NULL )
|
||||
dialog.nameFieldLabel = nsNameFieldLabel;
|
||||
if( nsNameFieldStringValue != NULL )
|
||||
dialog.nameFieldStringValue = nsNameFieldStringValue;
|
||||
if( nsDirectoryURL != NULL )
|
||||
dialog.directoryURL = [NSURL fileURLWithPath:nsDirectoryURL isDirectory:YES];
|
||||
|
||||
// set open options
|
||||
if( open ) {
|
||||
NSOpenPanel* openDialog = (NSOpenPanel*) dialog;
|
||||
|
||||
bool canChooseFiles = isOptionSet( FC_canChooseFiles );
|
||||
bool canChooseDirectories = isOptionSet( FC_canChooseDirectories );
|
||||
if( !canChooseFiles && !canChooseDirectories )
|
||||
canChooseFiles = true;
|
||||
openDialog.canChooseFiles = canChooseFiles;
|
||||
openDialog.canChooseDirectories = canChooseDirectories;
|
||||
|
||||
if( isOptionSetOrClear( FC_resolvesAliases ) )
|
||||
openDialog.resolvesAliases = isOptionSet( FC_resolvesAliases );
|
||||
if( isOptionSetOrClear( FC_allowsMultipleSelection ) )
|
||||
openDialog.allowsMultipleSelection = isOptionSet( FC_allowsMultipleSelection );
|
||||
}
|
||||
|
||||
// set options
|
||||
if( isOptionSetOrClear( FC_showsTagField ) )
|
||||
dialog.showsTagField = isOptionSet( FC_showsTagField );
|
||||
if( isOptionSetOrClear( FC_canCreateDirectories ) )
|
||||
dialog.canCreateDirectories = isOptionSet( FC_canCreateDirectories );
|
||||
if( isOptionSetOrClear( FC_canSelectHiddenExtension ) )
|
||||
dialog.canSelectHiddenExtension = isOptionSet( FC_canSelectHiddenExtension );
|
||||
if( isOptionSetOrClear( FC_showsHiddenFiles) )
|
||||
dialog.showsHiddenFiles = isOptionSet( FC_showsHiddenFiles);
|
||||
if( isOptionSetOrClear( FC_extensionHidden ) )
|
||||
dialog.extensionHidden = isOptionSet( FC_extensionHidden );
|
||||
if( isOptionSetOrClear( FC_allowsOtherFileTypes ) )
|
||||
dialog.allowsOtherFileTypes = isOptionSet( FC_allowsOtherFileTypes );
|
||||
if( isOptionSetOrClear( FC_treatsFilePackagesAsDirectories ) )
|
||||
dialog.treatsFilePackagesAsDirectories = isOptionSet( FC_treatsFilePackagesAsDirectories );
|
||||
|
||||
FileChooserDelegate* delegate = [FileChooserDelegate new];
|
||||
delegate.dialog = dialog;
|
||||
|
||||
// initialize filter accessory view
|
||||
if( filters != NULL ) {
|
||||
[delegate initFilterAccessoryView:filters :fileTypeIndex :nsFilterFieldLabel :isOptionSet( FC_showSingleFilterField )];
|
||||
|
||||
if( open && isOptionSetOrClear( FC_accessoryViewDisclosed ) )
|
||||
((NSOpenPanel*)dialog).accessoryViewDisclosed = isOptionSet( FC_accessoryViewDisclosed );
|
||||
}
|
||||
|
||||
// initialize callback
|
||||
if( callback != NULL )
|
||||
[delegate initCallback :jvm :callback];
|
||||
|
||||
// set file dialog delegate
|
||||
dialog.delegate = delegate;
|
||||
|
||||
// show dialog
|
||||
NSModalResponse response = [dialog runModal];
|
||||
[delegate release];
|
||||
if( response != NSModalResponseOK ) {
|
||||
*purls = @[];
|
||||
return;
|
||||
}
|
||||
|
||||
*purls = getDialogURLs( dialog );
|
||||
|
||||
JNI_COCOA_CATCH()
|
||||
}];
|
||||
|
||||
if( urls == NULL )
|
||||
return NULL;
|
||||
|
||||
// convert URLs to Java string array
|
||||
return urlsToStringArray( env, urls );
|
||||
|
||||
JNI_COCOA_EXIT()
|
||||
}
|
||||
|
||||
static NSArray* getDialogURLs( NSSavePanel* dialog ) {
|
||||
if( [dialog isKindOfClass:[NSOpenPanel class]] )
|
||||
return [[NSArray alloc] initWithArray: static_cast<NSOpenPanel*>(dialog).URLs];
|
||||
|
||||
NSURL* url = dialog.URL;
|
||||
// use '[[NSArray alloc] initWithObject:url]' here because '@[url]' crashes on macOS 10.14
|
||||
return (url != NULL) ? [[NSArray alloc] initWithObject:url] : @[];
|
||||
}
|
||||
|
||||
static jobjectArray urlsToStringArray( JNIEnv* env, NSArray* urls ) {
|
||||
jsize count = (urls != NULL) ? urls.count : 0;
|
||||
jobjectArray array = newJavaStringArray( env, count );
|
||||
for( int i = 0; i < count; i++ ) {
|
||||
jstring filename = NormalizedPathJavaFromNSString( env, [urls[i] path] );
|
||||
env->SetObjectArrayElement( array, i, filename );
|
||||
env->DeleteLocalRef( filename );
|
||||
}
|
||||
return array;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2024 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <jni.h>
|
||||
#import "JNIUtils.h"
|
||||
#import "JNFRunLoop.h"
|
||||
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
* @since 3.7
|
||||
*/
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showMessageDialog
|
||||
( JNIEnv* env, jclass cls, jlong hwndParent, jint alertStyle, jstring messageText, jstring informativeText,
|
||||
jint defaultButton, jobjectArray buttons )
|
||||
{
|
||||
JNI_COCOA_ENTER()
|
||||
|
||||
// convert Java strings to NSString (on Java thread)
|
||||
NSString* nsMessageText = JavaToNSString( env, messageText );
|
||||
NSString* nsInformativeText = JavaToNSString( env, informativeText );
|
||||
|
||||
jint buttonCount = env->GetArrayLength( buttons );
|
||||
NSMutableArray* nsButtons = [NSMutableArray array];
|
||||
for( int i = 0; i < buttonCount; i++ ) {
|
||||
NSString* nsButton = JavaToNSString( env, (jstring) env->GetObjectArrayElement( buttons, i ) );
|
||||
[nsButtons addObject:nsButton];
|
||||
}
|
||||
|
||||
jint result = -1;
|
||||
jint* presult = &result;
|
||||
|
||||
// show alert on macOS thread
|
||||
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
|
||||
NSAlert* alert = [[NSAlert alloc] init];
|
||||
|
||||
// use appearance from parent window
|
||||
NSWindow* parent = (NSWindow*) hwndParent;
|
||||
if( parent != NULL )
|
||||
alert.window.appearance = parent.appearance;
|
||||
|
||||
// use empty string because if alert.messageText is not set it displays "Alert"
|
||||
alert.messageText = (nsMessageText != NULL) ? nsMessageText : @"";
|
||||
if( nsInformativeText != NULL )
|
||||
alert.informativeText = nsInformativeText;
|
||||
|
||||
// alert style
|
||||
switch( alertStyle ) {
|
||||
case /* JOptionPane.ERROR_MESSAGE */ 0: alert.alertStyle = NSAlertStyleCritical; break;
|
||||
default:
|
||||
case /* JOptionPane.INFORMATION_MESSAGE */ 1: alert.alertStyle = NSAlertStyleInformational; break;
|
||||
case /* JOptionPane.WARNING_MESSAGE */ 2: alert.alertStyle = NSAlertStyleWarning; break;
|
||||
}
|
||||
|
||||
// add buttons
|
||||
for( int i = 0; i < nsButtons.count; i++ ) {
|
||||
NSButton* b = [alert addButtonWithTitle:nsButtons[i]];
|
||||
if( i == defaultButton )
|
||||
alert.window.defaultButtonCell = b.cell;
|
||||
}
|
||||
|
||||
// show alert
|
||||
NSInteger response = [alert runModal];
|
||||
|
||||
// if no buttons added, which shows a single OK button, the response is 0 when clicking OK
|
||||
// if buttons added, response is 1000+buttonIndex
|
||||
*presult = MAX( response - NSAlertFirstButtonReturn, 0 );
|
||||
}];
|
||||
|
||||
return result;
|
||||
|
||||
JNI_COCOA_EXIT()
|
||||
}
|
||||
@@ -39,13 +39,15 @@
|
||||
@implementation WindowData
|
||||
@end
|
||||
|
||||
// declare internal methods
|
||||
// declare exported methods
|
||||
NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window );
|
||||
WindowData* getWindowData( NSWindow* nsWindow, bool allocate );
|
||||
void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden );
|
||||
int getWindowButtonAreaWidth( NSWindow* nsWindow );
|
||||
int getWindowTitleBarHeight( NSWindow* nsWindow );
|
||||
bool isWindowFullScreen( NSWindow* nsWindow );
|
||||
|
||||
// declare internal methods
|
||||
static WindowData* getWindowData( NSWindow* nsWindow, bool allocate );
|
||||
static void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden );
|
||||
static int getWindowButtonAreaWidth( NSWindow* nsWindow );
|
||||
static int getWindowTitleBarHeight( NSWindow* nsWindow );
|
||||
static bool isWindowFullScreen( NSWindow* nsWindow );
|
||||
|
||||
|
||||
NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
|
||||
@@ -79,7 +81,7 @@ NSWindow* getNSWindow( JNIEnv* env, jclass cls, jobject window ) {
|
||||
return (NSWindow *) jlong_to_ptr( env->GetLongField( platformWindow, ptrID ) );
|
||||
}
|
||||
|
||||
WindowData* getWindowData( NSWindow* nsWindow, bool allocate ) {
|
||||
static WindowData* getWindowData( NSWindow* nsWindow, bool allocate ) {
|
||||
static char key;
|
||||
WindowData* windowData = objc_getAssociatedObject( nsWindow, &key );
|
||||
if( windowData == NULL && allocate ) {
|
||||
@@ -252,7 +254,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_setW
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ) {
|
||||
static void setWindowButtonsHidden( NSWindow* nsWindow, bool hidden ) {
|
||||
// get buttons
|
||||
NSView* buttons[3] = {
|
||||
[nsWindow standardWindowButton:NSWindowCloseButton],
|
||||
@@ -312,7 +314,7 @@ JNIEXPORT jobject JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_getWi
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
|
||||
static int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
|
||||
// get buttons
|
||||
NSView* buttons[3] = {
|
||||
[nsWindow standardWindowButton:NSWindowCloseButton],
|
||||
@@ -344,7 +346,7 @@ int getWindowButtonAreaWidth( NSWindow* nsWindow ) {
|
||||
return right + left;
|
||||
}
|
||||
|
||||
int getWindowTitleBarHeight( NSWindow* nsWindow ) {
|
||||
static int getWindowTitleBarHeight( NSWindow* nsWindow ) {
|
||||
NSView* closeButton = [nsWindow standardWindowButton:NSWindowCloseButton];
|
||||
if( closeButton == NULL )
|
||||
return -1;
|
||||
@@ -369,7 +371,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool isWindowFullScreen( NSWindow* nsWindow ) {
|
||||
static bool isWindowFullScreen( NSWindow* nsWindow ) {
|
||||
return ((nsWindow.styleMask & NSWindowStyleMaskFullScreen) != 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ tasks {
|
||||
compilerArgs.addAll( toolChain.map {
|
||||
when( it ) {
|
||||
is Gcc, is Clang -> listOf( "-O2", "-DUNICODE" )
|
||||
is VisualCpp -> listOf( "/O2", "/Zl", "/GS-", "/DUNICODE" )
|
||||
is VisualCpp -> listOf( "/O2", "/GS-", "/DUNICODE" )
|
||||
else -> emptyList()
|
||||
}
|
||||
} )
|
||||
@@ -80,8 +80,8 @@ tasks {
|
||||
|
||||
linkerArgs.addAll( toolChain.map {
|
||||
when( it ) {
|
||||
is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi" )
|
||||
is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "/NODEFAULTLIB" )
|
||||
is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi", "-lOle32", "-luuid" )
|
||||
is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "Ole32.lib", "uuid.lib" )
|
||||
else -> emptyList()
|
||||
}
|
||||
} )
|
||||
@@ -93,6 +93,15 @@ tasks {
|
||||
into( nativesDir )
|
||||
rename( linkedFile.get().asFile.name, libraryName )
|
||||
}
|
||||
|
||||
/*dump
|
||||
val dumpbin = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.42.34433/bin/Hostx64/x64/dumpbin.exe"
|
||||
val dll = linkedFile.asFile.get()
|
||||
val dllDir = dll.parent
|
||||
exec { commandLine( dumpbin, "/all", "/rawdata:none", "/out:$dllDir/objdump.txt", dll ) }
|
||||
exec { commandLine( dumpbin, "/all", "/out:$dllDir/full-contents.txt", dll ) }
|
||||
exec { commandLine( dumpbin, "/disasm", "/out:$dllDir/disassemble.txt", dll ) }
|
||||
dump*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
// increase this version if changing API or functionality of native library
|
||||
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeWindowsLibrary
|
||||
#define API_VERSION_WINDOWS 1001
|
||||
#define API_VERSION_WINDOWS 1002
|
||||
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
// declare exported methods
|
||||
HWND getWindowHandle( JNIEnv* env, jobject window );
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// avoid inlining of printf()
|
||||
#define _NO_CRT_STDIO_INLINE
|
||||
|
||||
#include "JNIUtils.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
//---- class AutoReleaseString ------------------------------------------------
|
||||
|
||||
AutoReleaseString::AutoReleaseString( JNIEnv* _env, jstring _javaString ) {
|
||||
env = _env;
|
||||
javaString = _javaString;
|
||||
chars = (javaString != NULL) ? env->GetStringChars( javaString, NULL ) : NULL;
|
||||
}
|
||||
|
||||
AutoReleaseString::~AutoReleaseString() {
|
||||
if( chars != NULL )
|
||||
env->ReleaseStringChars( javaString, chars );
|
||||
}
|
||||
|
||||
//---- class AutoReleaseStringArray -------------------------------------------
|
||||
|
||||
AutoReleaseStringArray::AutoReleaseStringArray( JNIEnv* _env, jobjectArray _javaStringArray ) {
|
||||
env = _env;
|
||||
count = (_javaStringArray != NULL) ? env->GetArrayLength( _javaStringArray ) : 0;
|
||||
if( count <= 0 )
|
||||
return;
|
||||
|
||||
javaStringArray = new jstring[count];
|
||||
charsArray = new const jchar*[count];
|
||||
|
||||
for( int i = 0; i < count; i++ ) {
|
||||
javaStringArray[i] = (jstring) env->GetObjectArrayElement( _javaStringArray, i );
|
||||
charsArray[i] = env->GetStringChars( javaStringArray[i] , NULL );
|
||||
}
|
||||
}
|
||||
|
||||
AutoReleaseStringArray::~AutoReleaseStringArray() {
|
||||
if( count == 0 )
|
||||
return;
|
||||
|
||||
for( int i = 0; i < count; i++ ) {
|
||||
env->ReleaseStringChars( javaStringArray[i], charsArray[i] );
|
||||
env->DeleteLocalRef( javaStringArray[i] );
|
||||
}
|
||||
|
||||
delete[] javaStringArray;
|
||||
delete[] charsArray;
|
||||
}
|
||||
@@ -36,8 +36,11 @@
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
HINSTANCE _instance;
|
||||
|
||||
extern "C"
|
||||
BOOL WINAPI _DllMainCRTStartup( HINSTANCE instance, DWORD reason, LPVOID reserved ) {
|
||||
_instance = instance;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Copyright 2024 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// avoid inlining of printf()
|
||||
#define _NO_CRT_STDIO_INLINE
|
||||
|
||||
#include <windows.h>
|
||||
#include <shobjidl.h>
|
||||
#include "JNIUtils.h"
|
||||
#include "com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
* @since 3.7
|
||||
*/
|
||||
|
||||
// declare external methods
|
||||
extern HWND getWindowHandle( JNIEnv* env, jobject window );
|
||||
|
||||
// declare internal methods
|
||||
static jobjectArray getFiles( JNIEnv* env, jboolean open, IFileDialog* dialog );
|
||||
|
||||
//---- class AutoReleasePtr ---------------------------------------------------
|
||||
|
||||
template<class T> class AutoReleasePtr {
|
||||
T* ptr;
|
||||
|
||||
public:
|
||||
AutoReleasePtr() {
|
||||
ptr = NULL;
|
||||
}
|
||||
AutoReleasePtr( T* p ) {
|
||||
ptr = p;
|
||||
ptr->AddRef();
|
||||
}
|
||||
~AutoReleasePtr() {
|
||||
if( ptr != NULL )
|
||||
ptr->Release();
|
||||
}
|
||||
T** operator&() { return &ptr; }
|
||||
T* operator->() { return ptr; }
|
||||
operator T*() { return ptr; }
|
||||
};
|
||||
|
||||
//---- class AutoReleaseIShellItem --------------------------------------------
|
||||
|
||||
class AutoReleaseIShellItem : public AutoReleasePtr<IShellItem> {
|
||||
public:
|
||||
AutoReleaseIShellItem( JNIEnv* env, jstring path ) {
|
||||
AutoReleaseString cpath( env, path );
|
||||
::SHCreateItemFromParsingName( cpath, NULL, IID_IShellItem, reinterpret_cast<void**>( &*this ) );
|
||||
}
|
||||
};
|
||||
|
||||
//---- class FilterSpec -------------------------------------------------------
|
||||
|
||||
class FilterSpec {
|
||||
AutoReleaseStringArray fileTypes;
|
||||
|
||||
public:
|
||||
UINT count = 0;
|
||||
COMDLG_FILTERSPEC* specs = NULL;
|
||||
|
||||
public:
|
||||
FilterSpec( JNIEnv* _env, jobjectArray _fileTypes )
|
||||
: fileTypes( _env, _fileTypes )
|
||||
{
|
||||
if( fileTypes.count == 0 )
|
||||
return;
|
||||
|
||||
count = fileTypes.count / 2;
|
||||
specs = new COMDLG_FILTERSPEC[fileTypes.count];
|
||||
|
||||
for( int i = 0; i < count; i++ ) {
|
||||
specs[i].pszName = fileTypes[i * 2];
|
||||
specs[i].pszSpec = fileTypes[(i * 2) + 1];
|
||||
}
|
||||
}
|
||||
~FilterSpec() {
|
||||
if( specs != NULL )
|
||||
delete[] specs;
|
||||
}
|
||||
};
|
||||
|
||||
//---- class DialogEventHandler -----------------------------------------------
|
||||
|
||||
// see https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/winui/shell/appplatform/commonfiledialog/CommonFileDialogApp.cpp
|
||||
|
||||
class DialogEventHandler : public IFileDialogEvents {
|
||||
JNIEnv* env;
|
||||
jboolean open;
|
||||
jobject callback;
|
||||
LONG refCount = 1;
|
||||
|
||||
public:
|
||||
DialogEventHandler( JNIEnv* _env, jboolean _open, jobject _callback ) {
|
||||
env = _env;
|
||||
open = _open;
|
||||
callback = _callback;
|
||||
}
|
||||
|
||||
//---- IFileDialogEvents methods ----
|
||||
|
||||
IFACEMETHODIMP OnFileOk( IFileDialog* dialog ) {
|
||||
if( callback == NULL )
|
||||
return S_OK;
|
||||
|
||||
// get files
|
||||
jobjectArray files;
|
||||
if( open ) {
|
||||
AutoReleasePtr<IFileOpenDialog> openDialog;
|
||||
HRESULT hr = dialog->QueryInterface( &openDialog );
|
||||
files = SUCCEEDED( hr ) ? getFiles( env, true, openDialog ) : getFiles( env, false, dialog );
|
||||
} else
|
||||
files = getFiles( env, false, dialog );
|
||||
|
||||
// get hwnd of file dialog
|
||||
HWND hwndFileDialog = 0;
|
||||
AutoReleasePtr<IOleWindow> window;
|
||||
if( SUCCEEDED( dialog->QueryInterface( &window ) ) )
|
||||
window->GetWindow( &hwndFileDialog );
|
||||
|
||||
// invoke callback: boolean approve( String[] files, long hwnd );
|
||||
jclass cls = env->GetObjectClass( callback );
|
||||
jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
|
||||
if( approveID == NULL )
|
||||
return S_OK;
|
||||
return env->CallBooleanMethod( callback, approveID, files, hwndFileDialog ) ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP OnFolderChange( IFileDialog* ) { return S_OK; }
|
||||
IFACEMETHODIMP OnFolderChanging( IFileDialog*, IShellItem* ) { return S_OK; }
|
||||
IFACEMETHODIMP OnHelp( IFileDialog* ) { return S_OK; }
|
||||
IFACEMETHODIMP OnSelectionChange( IFileDialog* ) { return S_OK; }
|
||||
IFACEMETHODIMP OnShareViolation( IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE* ) { return S_OK; }
|
||||
IFACEMETHODIMP OnTypeChange( IFileDialog*pfd ) { return S_OK; }
|
||||
IFACEMETHODIMP OnOverwrite( IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE* ) { return S_OK; }
|
||||
|
||||
//---- IUnknown methods ----
|
||||
|
||||
IFACEMETHODIMP QueryInterface( REFIID riid, void** ppv ) {
|
||||
if( riid != IID_IFileDialogEvents && riid != IID_IUnknown )
|
||||
return E_NOINTERFACE;
|
||||
|
||||
*ppv = static_cast<IFileDialogEvents*>( this );
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) AddRef() {
|
||||
return InterlockedIncrement( &refCount );
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) Release() {
|
||||
LONG newRefCount = InterlockedDecrement( &refCount );
|
||||
if( newRefCount == 0 )
|
||||
delete this;
|
||||
return newRefCount;
|
||||
}
|
||||
|
||||
private:
|
||||
~DialogEventHandler() {}
|
||||
};
|
||||
|
||||
//---- class CoInitializer ----------------------------------------------------
|
||||
|
||||
class CoInitializer {
|
||||
public:
|
||||
bool initialized;
|
||||
|
||||
CoInitializer() {
|
||||
HRESULT result = ::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE );
|
||||
initialized = SUCCEEDED( result );
|
||||
}
|
||||
~CoInitializer() {
|
||||
if( initialized )
|
||||
::CoUninitialize();
|
||||
}
|
||||
};
|
||||
|
||||
//---- helper -----------------------------------------------------------------
|
||||
|
||||
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_ ## option) != 0)
|
||||
#define CHECK_HRESULT( code ) { if( (code) != S_OK ) return NULL; }
|
||||
|
||||
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
|
||||
jclass stringClass = env->FindClass( "java/lang/String" );
|
||||
return env->NewObjectArray( count, stringClass, NULL );
|
||||
}
|
||||
|
||||
static jstring newJavaString( JNIEnv* env, LPWSTR str ) {
|
||||
return env->NewString( reinterpret_cast<jchar*>( str ), static_cast<jsize>( wcslen( str ) ) );
|
||||
}
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showFileChooser
|
||||
( JNIEnv* env, jclass cls, jobject owner, jboolean open,
|
||||
jstring title, jstring okButtonLabel, jstring fileNameLabel, jstring fileName,
|
||||
jstring folder, jstring saveAsItem, jstring defaultFolder, jstring defaultExtension,
|
||||
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
|
||||
{
|
||||
// initialize COM library
|
||||
CoInitializer coInitializer;
|
||||
if( !coInitializer.initialized )
|
||||
return NULL;
|
||||
|
||||
// handle limitations (without this, some Win32 method fails and this method returns NULL)
|
||||
if( isOptionSet( FOS_PICKFOLDERS ) ) {
|
||||
open = true; // always use IFileOpenDialog for picking folders
|
||||
fileTypes = NULL; // no filter allowed for picking folders
|
||||
}
|
||||
if( !open && isOptionSet( FOS_ALLOWMULTISELECT ) )
|
||||
optionsSet &= ~FOS_ALLOWMULTISELECT;
|
||||
|
||||
// convert Java strings to C strings
|
||||
AutoReleaseString ctitle( env, title );
|
||||
AutoReleaseString cokButtonLabel( env, okButtonLabel );
|
||||
AutoReleaseString cfileNameLabel( env, fileNameLabel );
|
||||
AutoReleaseString cfileName( env, fileName );
|
||||
AutoReleaseIShellItem cfolder( env, folder );
|
||||
AutoReleaseIShellItem csaveAsItem( env, saveAsItem );
|
||||
AutoReleaseIShellItem cdefaultFolder( env, defaultFolder );
|
||||
AutoReleaseString cdefaultExtension( env, defaultExtension );
|
||||
FilterSpec specs( env, fileTypes );
|
||||
|
||||
// create IFileOpenDialog or IFileSaveDialog
|
||||
// https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
AutoReleasePtr<IFileDialog> dialog;
|
||||
CHECK_HRESULT( ::CoCreateInstance( open ? CLSID_FileOpenDialog : CLSID_FileSaveDialog,
|
||||
NULL, CLSCTX_INPROC_SERVER, open ? IID_IFileOpenDialog : IID_IFileSaveDialog,
|
||||
reinterpret_cast<LPVOID*>( &dialog ) ) );
|
||||
|
||||
// set title, etc.
|
||||
if( ctitle != NULL )
|
||||
CHECK_HRESULT( dialog->SetTitle( ctitle ) );
|
||||
if( cokButtonLabel != NULL )
|
||||
CHECK_HRESULT( dialog->SetOkButtonLabel( cokButtonLabel ) );
|
||||
if( cfileNameLabel != NULL )
|
||||
CHECK_HRESULT( dialog->SetFileNameLabel( cfileNameLabel ) );
|
||||
if( cfileName != NULL )
|
||||
CHECK_HRESULT( dialog->SetFileName( cfileName ) );
|
||||
if( cfolder != NULL )
|
||||
CHECK_HRESULT( dialog->SetFolder( cfolder ) );
|
||||
if( !open && csaveAsItem != NULL )
|
||||
CHECK_HRESULT( ((IFileSaveDialog*)(IFileDialog*)dialog)->SetSaveAsItem( csaveAsItem ) );
|
||||
if( cdefaultFolder != NULL )
|
||||
CHECK_HRESULT( dialog->SetDefaultFolder( cdefaultFolder ) );
|
||||
if( cdefaultExtension != NULL )
|
||||
CHECK_HRESULT( dialog->SetDefaultExtension( cdefaultExtension ) );
|
||||
|
||||
// set options
|
||||
FILEOPENDIALOGOPTIONS existingOptions;
|
||||
CHECK_HRESULT( dialog->GetOptions( &existingOptions ) );
|
||||
CHECK_HRESULT( dialog->SetOptions ( (existingOptions & ~optionsClear) | optionsSet ) );
|
||||
|
||||
// initialize filter
|
||||
if( specs.count > 0 ) {
|
||||
CHECK_HRESULT( dialog->SetFileTypes( specs.count, specs.specs ) );
|
||||
if( fileTypeIndex > 0 )
|
||||
CHECK_HRESULT( dialog->SetFileTypeIndex( min( fileTypeIndex + 1, specs.count ) ) );
|
||||
}
|
||||
|
||||
// add event handler
|
||||
AutoReleasePtr<DialogEventHandler> handler( new DialogEventHandler( env, open, callback ) );
|
||||
DWORD dwCookie = 0;
|
||||
CHECK_HRESULT( dialog->Advise( handler, &dwCookie ) );
|
||||
|
||||
// show dialog
|
||||
HWND hwndOwner = (owner != NULL) ? getWindowHandle( env, owner ) : NULL;
|
||||
HRESULT hr = dialog->Show( hwndOwner );
|
||||
dialog->Unadvise( dwCookie );
|
||||
if( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
|
||||
return newJavaStringArray( env, 0 );
|
||||
CHECK_HRESULT( hr );
|
||||
|
||||
// get selected files as Java string array
|
||||
return getFiles( env, open, dialog );
|
||||
}
|
||||
|
||||
static jobjectArray getFiles( JNIEnv* env, jboolean open, IFileDialog* dialog ) {
|
||||
if( open ) {
|
||||
AutoReleasePtr<IShellItemArray> shellItems;
|
||||
DWORD count;
|
||||
CHECK_HRESULT( ((IFileOpenDialog*)(IFileDialog*)dialog)->GetResults( &shellItems ) );
|
||||
CHECK_HRESULT( shellItems->GetCount( &count ) );
|
||||
|
||||
// convert shell items to Java string array
|
||||
jobjectArray array = newJavaStringArray( env, count );
|
||||
for( int i = 0; i < count; i++ ) {
|
||||
AutoReleasePtr<IShellItem> shellItem;
|
||||
LPWSTR path;
|
||||
CHECK_HRESULT( shellItems->GetItemAt( i, &shellItem ) );
|
||||
CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) );
|
||||
|
||||
jstring jpath = newJavaString( env, path );
|
||||
CoTaskMemFree( path );
|
||||
|
||||
env->SetObjectArrayElement( array, i, jpath );
|
||||
env->DeleteLocalRef( jpath );
|
||||
}
|
||||
return array;
|
||||
} else {
|
||||
AutoReleasePtr<IShellItem> shellItem;
|
||||
LPWSTR path;
|
||||
CHECK_HRESULT( dialog->GetResult( &shellItem ) );
|
||||
CHECK_HRESULT( shellItem->GetDisplayName( SIGDN_FILESYSPATH, &path ) );
|
||||
|
||||
// convert shell item to Java string array
|
||||
jstring jpath = newJavaString( env, path );
|
||||
CoTaskMemFree( path );
|
||||
|
||||
jobjectArray array = newJavaStringArray( env, 1 );
|
||||
env->SetObjectArrayElement( array, 0, jpath );
|
||||
env->DeleteLocalRef( jpath );
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright 2025 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// avoid inlining of printf()
|
||||
#define _NO_CRT_STDIO_INLINE
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include "JNIUtils.h"
|
||||
#include "com_formdev_flatlaf_ui_FlatNativeWindowsLibrary.h"
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
* @since 3.7
|
||||
*/
|
||||
|
||||
#define ID_BUTTON1 101
|
||||
|
||||
// declare external fields
|
||||
extern HINSTANCE _instance;
|
||||
|
||||
// declare internal methods
|
||||
static byte* createInMemoryTemplate( HWND owner, int messageType, LPCWSTR title, LPCWSTR text,
|
||||
int defaultButton, int buttonCount, LPCWSTR* buttons );
|
||||
static INT_PTR CALLBACK messageDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
|
||||
static int textLengthAsDLUs( HDC hdc, LPCWSTR str, int strLen );
|
||||
static LONG pixel2dluX( LONG px );
|
||||
static LONG pixel2dluY( LONG px );
|
||||
static LONG dluX2pixel( LONG dluX );
|
||||
static LPWORD lpwAlign( LPWORD lpIn );
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageBox
|
||||
( JNIEnv* env, jclass cls, jlong hwndParent, jstring text, jstring caption, jint type )
|
||||
{
|
||||
// convert Java strings to C strings
|
||||
AutoReleaseString ctext( env, text );
|
||||
AutoReleaseString ccaption( env, caption );
|
||||
|
||||
return ::MessageBox( reinterpret_cast<HWND>( hwndParent ), ctext, ccaption, type );
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog
|
||||
( JNIEnv* env, jclass cls, jlong hwndParent, jint messageType, jstring title,
|
||||
jstring text, jint defaultButton, jobjectArray buttons )
|
||||
{
|
||||
HWND owner = reinterpret_cast<HWND>( hwndParent );
|
||||
|
||||
// convert Java strings to C strings
|
||||
AutoReleaseString ctitle( env, title );
|
||||
AutoReleaseString ctext( env, text );
|
||||
AutoReleaseStringArray cbuttons( env, buttons );
|
||||
|
||||
// get title from parent window if necessary
|
||||
WCHAR parentTitle[100];
|
||||
if( ctitle == NULL )
|
||||
::GetWindowText( owner, parentTitle, 100 );
|
||||
|
||||
byte* templ = createInMemoryTemplate( owner, messageType, (ctitle != NULL) ? ctitle : parentTitle,
|
||||
ctext, defaultButton, cbuttons.count, cbuttons );
|
||||
if( templ == NULL )
|
||||
return -1;
|
||||
|
||||
LRESULT ret = ::DialogBoxIndirect( _instance, (LPDLGTEMPLATE) templ, owner, messageDialogProc );
|
||||
delete templ;
|
||||
return (ret >= ID_BUTTON1) ? ret - ID_BUTTON1 : -1;
|
||||
}
|
||||
|
||||
|
||||
// all values in DLUs
|
||||
|
||||
#define INSETS_TOP 12
|
||||
#define INSETS_LEFT 12
|
||||
#define INSETS_RIGHT 12
|
||||
#define INSETS_BOTTOM 6
|
||||
|
||||
#define ICON_TEXT_GAP 8
|
||||
|
||||
#define LABEL_MIN_WIDTH 100
|
||||
#define LABEL_MAX_WIDTH 250
|
||||
#define LABEL_HEIGHT 8
|
||||
|
||||
#define BUTTON_WIDTH 50
|
||||
#define BUTTON_HEIGHT 12
|
||||
#define BUTTON_GAP 5
|
||||
#define BUTTON_TOP_GAP 14
|
||||
#define BUTTON_LEFT_RIGHT_GAP 8
|
||||
|
||||
// based on https://learn.microsoft.com/en-us/windows/win32/dlgbox/using-dialog-boxes#creating-a-template-in-memory
|
||||
static byte* createInMemoryTemplate( HWND owner, int messageType, LPCWSTR title, LPCWSTR text,
|
||||
int defaultButton, int buttonCount, LPCWSTR* buttons )
|
||||
{
|
||||
// get font info needed for DS_SETFONT
|
||||
NONCLIENTMETRICS ncMetrics;
|
||||
ncMetrics.cbSize = sizeof( NONCLIENTMETRICS );
|
||||
if( !::SystemParametersInfo( SPI_GETNONCLIENTMETRICS, 0, &ncMetrics, 0 ) )
|
||||
return NULL;
|
||||
|
||||
// create DC to use message font
|
||||
HDC hdcOwner = ::GetDC( owner );
|
||||
HDC hdc = ::CreateCompatibleDC( hdcOwner );
|
||||
::ReleaseDC( owner, hdcOwner );
|
||||
if( hdc == NULL )
|
||||
return NULL;
|
||||
|
||||
HFONT hfont = ::CreateFontIndirect( &ncMetrics.lfMessageFont );
|
||||
if( hfont == NULL ) {
|
||||
::DeleteDC( hdc );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( ::SelectObject( hdc, hfont ) == NULL ) {
|
||||
::DeleteDC( hdc );
|
||||
::DeleteObject( hfont );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//---- calculate layout (in DLUs) ----
|
||||
|
||||
// layout icon
|
||||
LPWSTR icon;
|
||||
switch( messageType ) {
|
||||
case /* JOptionPane.ERROR_MESSAGE */ 0: icon = IDI_ERROR; break;
|
||||
case /* JOptionPane.INFORMATION_MESSAGE */ 1: icon = IDI_INFORMATION; break;
|
||||
case /* JOptionPane.WARNING_MESSAGE */ 2: icon = IDI_WARNING; break;
|
||||
case /* JOptionPane.QUESTION_MESSAGE */ 3: icon = IDI_QUESTION; break;
|
||||
default:
|
||||
case /* JOptionPane.PLAIN_MESSAGE */ -1: icon = NULL; break;
|
||||
}
|
||||
int ix = INSETS_LEFT;
|
||||
int iy = INSETS_TOP;
|
||||
int iw = pixel2dluX( ::GetSystemMetrics( SM_CXICON ) );
|
||||
int ih = pixel2dluY( ::GetSystemMetrics( SM_CYICON ) );
|
||||
|
||||
// layout text
|
||||
int tx = ix + (icon != NULL ? iw + ICON_TEXT_GAP : 0);
|
||||
int ty = iy;
|
||||
int tw = 0;
|
||||
int th = 0;
|
||||
if( text == NULL )
|
||||
text = L"";
|
||||
LPWSTR wrappedText = new WCHAR[wcslen( text ) + 1];
|
||||
wcscpy( wrappedText, text );
|
||||
LPWSTR lineStart = wrappedText;
|
||||
for( LPWSTR t = wrappedText; ; t++ ) {
|
||||
if( *t != '\n' && *t != 0 )
|
||||
continue;
|
||||
|
||||
// calculate line width (in pixels) and number of charaters that fit into LABEL_MAX_WIDTH
|
||||
int lineLen = t - lineStart;
|
||||
int fit = 0;
|
||||
SIZE size{ 0 };
|
||||
if( !::GetTextExtentExPoint( hdc, lineStart, lineLen, dluX2pixel( LABEL_MAX_WIDTH ), &fit, NULL, &size ) )
|
||||
break;
|
||||
|
||||
if( fit < lineLen ) {
|
||||
// wrap too long line --> try to wrap at space character
|
||||
bool wrapped = false;
|
||||
for( LPWSTR t2 = lineStart + fit - 1; t2 > lineStart; t2-- ) {
|
||||
if( *t2 == ' ' || *t2 == '\t' ) {
|
||||
*t2 = '\n';
|
||||
int w = textLengthAsDLUs( hdc, lineStart, t2 - lineStart );
|
||||
tw = max( tw, w );
|
||||
th += LABEL_HEIGHT;
|
||||
|
||||
// continue wrapping after inserted line break
|
||||
t = t2;
|
||||
lineStart = t + 1;
|
||||
wrapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !wrapped ) {
|
||||
// not able to wrap at word --> break long word
|
||||
int breakIndex = (lineStart + fit) - wrappedText;
|
||||
int w = textLengthAsDLUs( hdc, lineStart, breakIndex );
|
||||
tw = max( tw, w );
|
||||
th += LABEL_HEIGHT;
|
||||
|
||||
// duplicate string
|
||||
LPWSTR wrappedText2 = new WCHAR[wcslen( wrappedText ) + 1 + 1];
|
||||
// use wcscpy(), instead of wcsncpy(), because this method is inlined and does not require linking to runtime lib
|
||||
wcscpy( wrappedText2, wrappedText );
|
||||
wrappedText2[breakIndex] = '\n';
|
||||
wcscpy( wrappedText2 + breakIndex + 1, wrappedText + breakIndex );
|
||||
|
||||
// delete old text
|
||||
delete[] wrappedText;
|
||||
wrappedText = wrappedText2;
|
||||
|
||||
// continue wrapping after inserted line break
|
||||
t = wrappedText + breakIndex;
|
||||
lineStart = t + 1;
|
||||
}
|
||||
} else {
|
||||
// line fits into LABEL_MAX_WIDTH
|
||||
int w = pixel2dluX( size.cx );
|
||||
tw = max( tw, w );
|
||||
th += LABEL_HEIGHT;
|
||||
lineStart = t + 1;
|
||||
}
|
||||
|
||||
if( *t == 0 )
|
||||
break;
|
||||
}
|
||||
tw = min( max( tw, LABEL_MIN_WIDTH ), LABEL_MAX_WIDTH );
|
||||
th = max( th, LABEL_HEIGHT );
|
||||
if( icon != NULL && th < ih )
|
||||
ty += (ih - th) / 2; // vertically center text
|
||||
|
||||
// layout buttons
|
||||
int* bw = new int[buttonCount];
|
||||
int buttonTotalWidth = BUTTON_GAP * (buttonCount - 1);
|
||||
for( int i = 0; i < buttonCount; i++ ) {
|
||||
int w = textLengthAsDLUs( hdc, buttons[i], -1 ) + 16;
|
||||
bw[i] = max( BUTTON_WIDTH, w );
|
||||
buttonTotalWidth += bw[i];
|
||||
}
|
||||
|
||||
// layout dialog
|
||||
int dx = 0;
|
||||
int dy = 0;
|
||||
int dw = max( tx + tw + INSETS_RIGHT, BUTTON_LEFT_RIGHT_GAP + buttonTotalWidth + BUTTON_LEFT_RIGHT_GAP );
|
||||
int dh = max( iy + ih, ty + th ) + BUTTON_TOP_GAP + BUTTON_HEIGHT + INSETS_BOTTOM;
|
||||
|
||||
// center dialog in owner
|
||||
RECT ownerRect{ 0 };
|
||||
if( ::GetClientRect( owner, &ownerRect ) ) {
|
||||
dx = (pixel2dluX( ownerRect.right - ownerRect.left ) - dw) / 2;
|
||||
dy = (pixel2dluY( ownerRect.bottom - ownerRect.top ) - dh) / 2;
|
||||
}
|
||||
|
||||
// layout button area
|
||||
int bx = dw - buttonTotalWidth - BUTTON_LEFT_RIGHT_GAP;
|
||||
int by = dh - BUTTON_HEIGHT - INSETS_BOTTOM;
|
||||
|
||||
// get font info needed for DS_SETFONT
|
||||
int fontPointSize = (ncMetrics.lfMessageFont.lfHeight < 0)
|
||||
? -MulDiv( ncMetrics.lfMessageFont.lfHeight, 72, ::GetDeviceCaps( hdc, LOGPIXELSY ) )
|
||||
: ncMetrics.lfMessageFont.lfHeight;
|
||||
LPCWSTR fontFaceName = ncMetrics.lfMessageFont.lfFaceName;
|
||||
|
||||
// delete DC and font
|
||||
::DeleteDC( hdc );
|
||||
::DeleteObject( hfont );
|
||||
|
||||
// (approximately) calculate memory size needed for in-memory template
|
||||
int templSize = (sizeof(DLGTEMPLATE) + /*menu*/ 2 + /*class*/ 2 + /*title*/ 2)
|
||||
+ ((sizeof(DLGITEMTEMPLATE) + /*class*/ 4 + /*title/icon*/ 4 + /*creation data*/ 2) * (/*icon+text*/2 + buttonCount))
|
||||
+ (title != NULL ? (wcslen( title ) + 1) * sizeof(wchar_t) : 0)
|
||||
+ /*fontPointSize*/ 2 + ((wcslen( fontFaceName ) + 1) * sizeof(wchar_t))
|
||||
+ ((wcslen( wrappedText ) + 1) * sizeof(wchar_t));
|
||||
for( int i = 0; i < buttonCount; i++ )
|
||||
templSize += ((wcslen( buttons[i] ) + 1) * sizeof(wchar_t));
|
||||
|
||||
templSize += (2 * (1 + 1 + buttonCount)); // necessary for DWORD alignment
|
||||
templSize += 100; // some reserve
|
||||
|
||||
// allocate memory for in-memory template
|
||||
byte* templ = new byte[templSize];
|
||||
if( templ == NULL )
|
||||
return NULL;
|
||||
|
||||
|
||||
//---- define dialog box ----
|
||||
|
||||
LPDLGTEMPLATE lpdt = (LPDLGTEMPLATE) templ;
|
||||
lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION | DS_SETFONT;
|
||||
lpdt->cdit = /*text*/ 1 + buttonCount; // number of controls
|
||||
lpdt->x = dx;
|
||||
lpdt->y = dy;
|
||||
lpdt->cx = dw;
|
||||
lpdt->cy = dh;
|
||||
|
||||
LPWORD lpw = (LPWORD) (lpdt + 1);
|
||||
*lpw++ = 0; // no menu
|
||||
*lpw++ = 0; // predefined dialog box class (by default)
|
||||
if( title != NULL ) {
|
||||
wcscpy( (LPWSTR) lpw, title );
|
||||
lpw += wcslen( title ) + 1;
|
||||
} else
|
||||
*lpw++ = 0; // no title
|
||||
|
||||
// for DS_SETFONT
|
||||
*lpw++ = fontPointSize;
|
||||
wcscpy( (LPWSTR) lpw, fontFaceName );
|
||||
lpw += wcslen( fontFaceName ) + 1;
|
||||
|
||||
//---- define icon ----
|
||||
|
||||
if( icon != NULL ) {
|
||||
lpdt->cdit++;
|
||||
|
||||
lpw = lpwAlign( lpw );
|
||||
LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw;
|
||||
lpdit->x = ix;
|
||||
lpdit->y = iy;
|
||||
lpdit->cx = iw;
|
||||
lpdit->cy = ih;
|
||||
lpdit->id = ID_BUTTON1 - 1;
|
||||
lpdit->style = WS_CHILD | WS_VISIBLE | SS_ICON;
|
||||
|
||||
lpw = (LPWORD) (lpdit + 1);
|
||||
*lpw++ = 0xffff; *lpw++ = 0x0082; // Static class
|
||||
*lpw++ = 0xffff; *lpw++ = (WORD) icon; // icon
|
||||
*lpw++ = 0; // creation data
|
||||
}
|
||||
|
||||
|
||||
//---- define text ----
|
||||
|
||||
lpw = lpwAlign( lpw );
|
||||
LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw;
|
||||
lpdit->x = tx;
|
||||
lpdit->y = ty;
|
||||
lpdit->cx = tw;
|
||||
lpdit->cy = th;
|
||||
lpdit->id = ID_BUTTON1 - 2;
|
||||
lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
|
||||
|
||||
lpw = (LPWORD) (lpdit + 1);
|
||||
*lpw++ = 0xffff; *lpw++ = 0x0082; // Static class
|
||||
wcscpy( (LPWSTR) lpw, wrappedText ); lpw += wcslen( wrappedText ) + 1; // text
|
||||
*lpw++ = 0; // creation data
|
||||
|
||||
|
||||
//---- define buttons ----
|
||||
|
||||
defaultButton = min( max( defaultButton, 0 ), buttonCount - 1 );
|
||||
int buttonId = ID_BUTTON1;
|
||||
for( int i = 0; i < buttonCount; i++ ) {
|
||||
lpw = lpwAlign( lpw );
|
||||
LPDLGITEMTEMPLATE lpdit = (LPDLGITEMTEMPLATE) lpw;
|
||||
lpdit->x = bx;
|
||||
lpdit->y = by;
|
||||
lpdit->cx = bw[i];
|
||||
lpdit->cy = BUTTON_HEIGHT;
|
||||
lpdit->id = buttonId++;
|
||||
lpdit->style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | (i == 0 ? WS_GROUP : 0)
|
||||
| BS_TEXT | (i == defaultButton ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON);
|
||||
|
||||
lpw = (LPWORD) (lpdit + 1);
|
||||
*lpw++ = 0xffff; *lpw++ = 0x0080; // Button class
|
||||
wcscpy( (LPWSTR) lpw, buttons[i] ); lpw += wcslen( buttons[i] ) + 1; // text
|
||||
*lpw++ = 0; // creation data
|
||||
|
||||
bx += bw[i] + BUTTON_GAP;
|
||||
}
|
||||
|
||||
delete[] wrappedText;
|
||||
delete[] bw;
|
||||
|
||||
return templ;
|
||||
}
|
||||
|
||||
static BOOL CALLBACK focusDefaultButtonProc( HWND hwnd, LPARAM lParam ) {
|
||||
if( ::GetWindowLong( hwnd, GWL_ID ) >= ID_BUTTON1 ) {
|
||||
LONG style = ::GetWindowLong( hwnd, GWL_STYLE );
|
||||
if( (style & BS_DEFPUSHBUTTON) != 0 ) {
|
||||
::SetFocus( hwnd );
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static INT_PTR CALLBACK messageDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
|
||||
switch( uMsg ) {
|
||||
case WM_INITDIALOG:
|
||||
::EnumChildWindows( hwnd, focusDefaultButtonProc, 0 );
|
||||
break;
|
||||
|
||||
case WM_COMMAND:
|
||||
::EndDialog( hwnd, wParam );
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static int textLengthAsDLUs( HDC hdc, LPCWSTR str, int strLen ) {
|
||||
SIZE size{ 0 };
|
||||
::GetTextExtentPoint32( hdc, str, (strLen >= 0) ? strLen : wcslen( str ), &size );
|
||||
return pixel2dluX( size.cx );
|
||||
}
|
||||
|
||||
static LONG pixel2dluX( LONG px ) {
|
||||
return MulDiv( px, 4, LOWORD( ::GetDialogBaseUnits() ) );
|
||||
}
|
||||
|
||||
static LONG pixel2dluY( LONG py ) {
|
||||
return MulDiv( py, 8, HIWORD( ::GetDialogBaseUnits() ) );
|
||||
}
|
||||
|
||||
static LONG dluX2pixel( LONG dluX ) {
|
||||
return MulDiv( dluX, LOWORD( ::GetDialogBaseUnits() ), 4 );
|
||||
}
|
||||
|
||||
static LPWORD lpwAlign( LPWORD lpIn ) {
|
||||
ULONG_PTR ul = (ULONG_PTR) lpIn;
|
||||
ul += 3;
|
||||
ul >>= 2;
|
||||
ul <<= 2;
|
||||
return (LPWORD) ul;
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
// see FlatWndProc.cpp
|
||||
HWND getWindowHandle( JNIEnv* env, jobject window );
|
||||
// declare external methods
|
||||
extern HWND getWindowHandle( JNIEnv* env, jobject window );
|
||||
|
||||
//---- Utility ----------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2025 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <jni.h>
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
|
||||
//---- class AutoReleaseString ------------------------------------------------
|
||||
|
||||
class AutoReleaseString {
|
||||
JNIEnv* env;
|
||||
jstring javaString;
|
||||
const jchar* chars;
|
||||
|
||||
public:
|
||||
AutoReleaseString( JNIEnv* _env, jstring _javaString );
|
||||
~AutoReleaseString();
|
||||
|
||||
operator LPCWSTR() { return (LPCWSTR) chars; }
|
||||
};
|
||||
|
||||
//---- class AutoReleaseStringArray -------------------------------------------
|
||||
|
||||
class AutoReleaseStringArray {
|
||||
JNIEnv* env;
|
||||
jstring* javaStringArray;
|
||||
const jchar** charsArray;
|
||||
|
||||
public:
|
||||
UINT count;
|
||||
|
||||
public:
|
||||
AutoReleaseStringArray( JNIEnv* _env, jobjectArray _javaStringArray );
|
||||
~AutoReleaseStringArray();
|
||||
|
||||
operator LPCWSTR*() { return (LPCWSTR*) charsArray; }
|
||||
};
|
||||
@@ -27,6 +27,52 @@ extern "C" {
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_DEFAULT -1L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_NONE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_DWMWA_COLOR_NONE -2L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OVERWRITEPROMPT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OVERWRITEPROMPT 2L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_STRICTFILETYPES
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_STRICTFILETYPES 4L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOCHANGEDIR
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOCHANGEDIR 8L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PICKFOLDERS
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PICKFOLDERS 32L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEFILESYSTEM
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEFILESYSTEM 64L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLNONSTORAGEITEMS
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLNONSTORAGEITEMS 128L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOVALIDATE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOVALIDATE 256L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLOWMULTISELECT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_ALLOWMULTISELECT 512L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PATHMUSTEXIST
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_PATHMUSTEXIST 2048L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FILEMUSTEXIST
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FILEMUSTEXIST 4096L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_CREATEPROMPT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_CREATEPROMPT 8192L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SHAREAWARE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SHAREAWARE 16384L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOREADONLYRETURN
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOREADONLYRETURN 32768L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOTESTFILECREATE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NOTESTFILECREATE 65536L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEMRUPLACES
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEMRUPLACES 131072L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEPINNEDPLACES
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_HIDEPINNEDPLACES 262144L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NODEREFERENCELINKS
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_NODEREFERENCELINKS 1048576L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OKBUTTONNEEDSINTERACTION
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_OKBUTTONNEEDSINTERACTION 2097152L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DONTADDTORECENT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DONTADDTORECENT 33554432L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCESHOWHIDDEN
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCESHOWHIDDEN 268435456L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DEFAULTNOMINIMODE
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_DEFAULTNOMINIMODE 536870912L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEPREVIEWPANEON
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_FORCEPREVIEWPANEON 1073741824L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SUPPORTSTREAMABLEITEMS
|
||||
#define com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_FOS_SUPPORTSTREAMABLEITEMS -2147483648L
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
|
||||
* Method: getOSBuildNumberImpl
|
||||
@@ -67,6 +113,30 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_
|
||||
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_dwmSetWindowAttributeDWORD
|
||||
(JNIEnv *, jclass, jlong, jint, jint);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
|
||||
* Method: showFileChooser
|
||||
* Signature: (Ljava/awt/Window;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeWindowsLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showFileChooser
|
||||
(JNIEnv *, jclass, jobject, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
|
||||
* Method: showMessageDialog
|
||||
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageDialog
|
||||
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
|
||||
|
||||
/*
|
||||
* Class: com_formdev_flatlaf_ui_FlatNativeWindowsLibrary
|
||||
* Method: showMessageBox
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_showMessageBox
|
||||
(JNIEnv *, jclass, jlong, jstring, jstring, jint);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,10 @@ extern "C" {
|
||||
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTMAXBUTTON 9L
|
||||
#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP
|
||||
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP 12L
|
||||
#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOPLEFT
|
||||
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOPLEFT 13L
|
||||
#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOPRIGHT
|
||||
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOPRIGHT 14L
|
||||
#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCLOSE
|
||||
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCLOSE 20L
|
||||
/*
|
||||
|
||||
@@ -38,9 +38,9 @@ build script:
|
||||
|
||||
Otherwise, download `flatlaf-swingx-<version>.jar` here:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-swingx)
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf-swingx)
|
||||
|
||||
|
||||
SwingX library `swingx-all-<version>.jar` is also required:
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/org.swinglabs.swingx/swingx-all)
|
||||
[](https://central.sonatype.com/artifact/org.swinglabs.swingx/swingx-all)
|
||||
|
||||
@@ -41,6 +41,7 @@ dependencies {
|
||||
implementation( libs.jide.oss )
|
||||
implementation( libs.glazedlists )
|
||||
implementation( libs.netbeans.api.awt )
|
||||
implementation( libs.nativejfilechooser )
|
||||
|
||||
components.all<TargetJvmVersion8Rule>()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user