mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 06:27:13 -06:00
Compare commits
98 Commits
3.6.2
...
84e95764fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84e95764fa | ||
|
|
1a80a65411 | ||
|
|
bf2227e1b8 | ||
|
|
7fc26fe77e | ||
|
|
465254c0da | ||
|
|
aaca7cace1 | ||
|
|
c995b4cbdf | ||
|
|
b251ff76e8 | ||
|
|
ea2a985481 | ||
|
|
3ca7ebbfee | ||
|
|
7e6dc269c5 | ||
|
|
7680c3a817 | ||
|
|
0e3cb95791 | ||
|
|
3f0b5d5a0e | ||
|
|
6e3633cca3 | ||
|
|
4f1e5cdb05 | ||
|
|
070cf9c40d | ||
|
|
5b0f13110a | ||
|
|
d7a5c353fe | ||
|
|
9e8b8697d1 | ||
|
|
58e073a05b | ||
|
|
1c6e8774cf | ||
|
|
1e724029ae | ||
|
|
9fdaa827b5 | ||
|
|
b9441050b2 | ||
|
|
8239cdd540 | ||
|
|
08419d6135 | ||
|
|
3a72232ae3 | ||
|
|
e61499e7c6 | ||
|
|
d3e6c7af14 | ||
|
|
ff11a11d28 | ||
|
|
855c41bf4a | ||
|
|
2b587d4dba | ||
|
|
5fabfc7051 | ||
|
|
db4173dd06 | ||
|
|
03b7a1c29e | ||
|
|
60968f77eb | ||
|
|
8bafa37b4a | ||
|
|
04602ac227 | ||
|
|
02636b260a | ||
|
|
c8e2e78955 | ||
|
|
19c86cf1f7 | ||
|
|
7ebc1b27c1 | ||
|
|
d4827b6ddf | ||
|
|
02f7cb8972 | ||
|
|
10677d469f | ||
|
|
df8212b49e | ||
|
|
c583a21bf7 | ||
|
|
5263125a04 | ||
|
|
056da35758 | ||
|
|
3ccaacfb00 | ||
|
|
bdb7438672 | ||
|
|
299250a710 | ||
|
|
1e2a75a19c | ||
|
|
0fb4c811f6 | ||
|
|
0d4946230e | ||
|
|
960f9d86c1 | ||
|
|
015645e173 | ||
|
|
8cfe1ca597 | ||
|
|
1eee35035d | ||
|
|
5c2d8ba555 | ||
|
|
0f27125107 | ||
|
|
f2882370de | ||
|
|
6715886b24 | ||
|
|
35e86ba772 | ||
|
|
dade1cba5a | ||
|
|
dcb4c09387 | ||
|
|
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 |
@@ -7,6 +7,11 @@ charset = latin1
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[{*.yaml,*.yml}]
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.java]
|
||||
indent_style = tab
|
||||
ij_continuation_indent_size = 4
|
||||
|
||||
32
.github/actions/cache-gradle/action.yml
vendored
Normal file
32
.github/actions/cache-gradle/action.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action#creating-a-composite-action-within-the-same-repository
|
||||
|
||||
# cache uses two files:
|
||||
#
|
||||
# - gradle-wrapper-<os>-<arch>-<hash>:
|
||||
# contains the Gradle wrapper/distribution (~/.gradle/wrapper)
|
||||
# and is updated when the Gradle version changed
|
||||
# - gradle-caches-<os>-<arch>-<hash>:
|
||||
# contains the Gradle caches (~/.gradle/caches), buildSrc/build and buildSrc/.gradle
|
||||
# and is updated when Gradle related files were changed
|
||||
# buildSrc/.gradle is needed so that buildSrc tasks are UP-TO-DATE
|
||||
|
||||
name: 'Cache Gradle'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
|
||||
- name: Cache '.gradle/wrapper'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: gradle-wrapper-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( 'gradle/wrapper/gradle-wrapper.properties' ) }}
|
||||
path: |
|
||||
~/.gradle/wrapper
|
||||
|
||||
- name: Cache '.gradle/caches' and 'buildSrc/build'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: gradle-caches-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( '**/*.gradle*', 'gradle/**', 'gradle.properties', 'buildSrc/src/**' ) }}
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
buildSrc/build
|
||||
buildSrc/.gradle
|
||||
108
.github/workflows/ci.yml
vendored
108
.github/workflows/ci.yml
vendored
@@ -14,13 +14,13 @@ on:
|
||||
- '.*'
|
||||
- '**/.settings/**'
|
||||
- 'flatlaf-core/svg/**'
|
||||
- 'flatlaf-natives/**'
|
||||
- 'flatlaf-testing/dumps/**'
|
||||
- 'flatlaf-testing/misc/**'
|
||||
- 'images/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build (11)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -28,20 +28,44 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Check with Error Prone
|
||||
run: ./gradlew errorprone clean
|
||||
- name: Cache Gradle
|
||||
uses: ./.github/actions/cache-gradle
|
||||
|
||||
- name: Build with Gradle
|
||||
|
||||
# test against
|
||||
# - Java 8 (minimum requirement)
|
||||
# - Java LTS versions (11, 17, ...)
|
||||
# - latest Java version(s)
|
||||
|
||||
- name: Build with Java 11 LTS
|
||||
if: github.repository == 'JFormDesigner/FlatLaf'
|
||||
run: ./gradlew build clean -Dtoolchain=11
|
||||
|
||||
- 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 8 for snapshot
|
||||
|
||||
- name: Build with Java 8
|
||||
run: ./gradlew build
|
||||
|
||||
- name: Upload artifacts
|
||||
- name: Upload artifacts to GitHub Actions
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlatLaf-build-artifacts
|
||||
@@ -52,59 +76,11 @@ 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
|
||||
toolchain: [""]
|
||||
include:
|
||||
- java: 21
|
||||
toolchain: 25 # LTS
|
||||
|
||||
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 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:
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
@@ -113,7 +89,7 @@ jobs:
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-on
|
||||
needs: build
|
||||
if: |
|
||||
github.event_name == 'push' &&
|
||||
startsWith( github.ref, 'refs/tags/' ) &&
|
||||
@@ -122,14 +98,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Release a new stable version to Maven Central
|
||||
- name: Cache Gradle
|
||||
uses: ./.github/actions/cache-gradle
|
||||
|
||||
- name: Release a new stable version to Maven Central and build demo and theme editor
|
||||
run: ./gradlew publishToSonatype closeSonatypeStagingRepository :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
|
||||
env:
|
||||
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
|
||||
|
||||
40
.github/workflows/error-prone.yml
vendored
Normal file
40
.github/workflows/error-prone.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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-natives/**'
|
||||
- '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 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: ./.github/actions/cache-gradle
|
||||
|
||||
- name: Check with Error Prone
|
||||
run: ./gradlew errorprone
|
||||
8
.github/workflows/fonts.yml
vendored
8
.github/workflows/fonts.yml
vendored
@@ -34,12 +34,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: ./.github/actions/cache-gradle
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build
|
||||
|
||||
87
.github/workflows/natives.yml
vendored
87
.github/workflows/natives.yml
vendored
@@ -33,30 +33,99 @@ 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: Setup Java 11
|
||||
# - 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 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
distribution: temurin
|
||||
cache: gradle
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: ./.github/actions/cache-gradle
|
||||
|
||||
- name: Build with Gradle
|
||||
# --no-daemon is necessary on Windows otherwise caching Gradle would fail with:
|
||||
# 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
|
||||
|
||||
8
.github/workflows/pr-snapshots.yml
vendored
8
.github/workflows/pr-snapshots.yml
vendored
@@ -21,12 +21,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
distribution: temurin # pre-installed on ubuntu-latest
|
||||
cache: gradle
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: ./.github/actions/cache-gradle
|
||||
|
||||
- name: Publish PR snapshot to Sonatype Central
|
||||
run: >
|
||||
|
||||
56
CHANGELOG.md
56
CHANGELOG.md
@@ -1,6 +1,62 @@
|
||||
FlatLaf Change Log
|
||||
==================
|
||||
|
||||
## 3.7.1-SNAPSHOT
|
||||
|
||||
- ComboBox: Added UI property `ComboBox.buttonFocusedEditableBackground`. (issue
|
||||
#1068)
|
||||
- Dialog: Some client properties (e.g. `JRootPane.titleBarShowTitle`) did not
|
||||
work before the dialog was made visible. (issue #1081)
|
||||
- Popup: Fixed scrolling popup painting issue on Windows 10 when a glass pane is
|
||||
visible and frame is maximized. (issue #1071)
|
||||
- Slider: Styling `thumbSize` or `focusWidth` did not update slider size/layout.
|
||||
(PR #1074)
|
||||
- ToolBar: Grip disappeared when switching between Look and Feels. (issue #1075)
|
||||
- macOS: Popups (menus and combobox lists) were not always hidden when window is
|
||||
resized. (issue #1082)
|
||||
- Extras:
|
||||
- UI defaults inspector: Fixed NPE if color of `FlatLineBorder` is null. Also
|
||||
use `FlatLineBorder` line color as cell background color in "Value" column.
|
||||
(PR #1080)
|
||||
- `FlatDesktop`: Avoid unnecessary logging if desktop is not supported (e.g.
|
||||
on NixOS with Plasma/KDE desktop).
|
||||
|
||||
|
||||
## 3.7
|
||||
|
||||
#### New features and improvements
|
||||
|
||||
- System File Chooser allows using **operating system file dialogs** in Java
|
||||
Swing applications. (PR #988)
|
||||
- Zooming API. (PR #1051)
|
||||
- Icons:
|
||||
- Support scaling Laf icons (checkbox, radiobutton, etc). (issue #1061)
|
||||
- Scale checkbox and radiobutton icons when using
|
||||
[text styles](https://www.formdev.com/flatlaf/typography/#text_styles)
|
||||
`large`, `medium`, `small` and `mini`.
|
||||
- TabbedPane: Added icon-only tab mode, which shows tab icons but hides tab
|
||||
titles. Tab titles are used in "Show Hidden Tabs" popup menu. (set client
|
||||
property `JTabbedPane.tabWidthMode` to `"iconOnly"`)
|
||||
- TabbedPane: In scroll tab layout, propagate mouse wheel events to ancestors.
|
||||
This allows mouse wheel scrolling if JTabbedPane is inside a JScrollPane. (PR
|
||||
#1030)
|
||||
|
||||
#### Fixed bugs
|
||||
|
||||
- CheckBox and RadioButton: Fixed styling of custom icon. Also fixed focus width
|
||||
(and preferred size) if using custom icon. (PR #1060)
|
||||
- TabbedPane: In "Show Hidden Tabs" popup menu, do not show text "x. Tab" if tab
|
||||
has icon but no title. (issue #1062)
|
||||
- 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)
|
||||
- IntelliJ Themes: Fixed problem when using theme instance more than once when
|
||||
switching to that theme. (issue #990)
|
||||
|
||||
|
||||
## 3.6.2
|
||||
|
||||
#### New features and improvements
|
||||
|
||||
14
README.md
14
README.md
@@ -76,10 +76,16 @@ Otherwise, download `flatlaf-<version>.jar` here:
|
||||
|
||||
[](https://central.sonatype.com/artifact/com.formdev/flatlaf)
|
||||
|
||||
See also
|
||||
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
|
||||
for instructions on how to redistribute FlatLaf native libraries with your
|
||||
application.
|
||||
- See
|
||||
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
|
||||
for instructions on how to redistribute FlatLaf native libraries with your
|
||||
application.
|
||||
- If repackaging FlatLaf (and other) JARs into a single fat/uber JAR:
|
||||
- add `Multi-Release: true` to `META-INF/MANIFEST.MF`
|
||||
- keep `META-INF/versions/` and `META-INF/services/` directories
|
||||
- merge content of equally named files in `META-INF/services/`
|
||||
- If using obfuscation/minimizing/shrinking tools (e.g. **ProGuard** or
|
||||
**Shadow**), exclude package `com.formdev.flatlaf` and all sub-packages.
|
||||
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -14,8 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import io.github.gradlenexus.publishplugin.CloseNexusStagingRepository
|
||||
import net.ltgt.gradle.errorprone.errorprone
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
|
||||
|
||||
// initialize version
|
||||
group = "com.formdev"
|
||||
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
|
||||
|
||||
@@ -24,18 +28,16 @@ val pullRequestNumber = findProperty( "github.event.pull_request.number" )
|
||||
if( pullRequestNumber != null )
|
||||
version = "PR-${pullRequestNumber}-SNAPSHOT"
|
||||
|
||||
|
||||
allprojects {
|
||||
// apply version to all subprojects
|
||||
subprojects {
|
||||
version = rootProject.version
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
// check required Java version
|
||||
if( JavaVersion.current() < JavaVersion.VERSION_1_8 )
|
||||
throw RuntimeException( "Java 8 or later required (running ${System.getProperty( "java.version" )})" )
|
||||
|
||||
// initialize toolchain version (default is Java 8)
|
||||
val toolchainJavaVersion: String by extra {
|
||||
System.getProperty( "toolchain", "8" )
|
||||
}
|
||||
|
||||
// log version, Gradle and Java versions
|
||||
println()
|
||||
@@ -43,9 +45,7 @@ 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 ${toolchainJavaVersion}" )
|
||||
println()
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ plugins {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<JavaCompile>().configureEach {
|
||||
sourceCompatibility = "1.8"
|
||||
@@ -78,6 +82,10 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
withType<AbstractArchiveTask>().configureEach {
|
||||
isPreserveFileTimestamps = true
|
||||
}
|
||||
|
||||
withType<Javadoc>().configureEach {
|
||||
options {
|
||||
this as StandardJavadocDocletOptions
|
||||
@@ -90,6 +98,23 @@ allprojects {
|
||||
links( "https://docs.oracle.com/en/java/javase/11/docs/api/" )
|
||||
}
|
||||
isFailOnError = false
|
||||
|
||||
// use Java 25 to generate javadoc
|
||||
val javaToolchains = (project as ExtensionAware).extensions.getByName("javaToolchains") as JavaToolchainService
|
||||
javadocTool.set( javaToolchains.javadocToolFor {
|
||||
languageVersion.set( JavaLanguageVersion.of( 25 ) )
|
||||
} )
|
||||
}
|
||||
|
||||
// mark some publishing related tasks as not compatible with configuration cache
|
||||
withType<Sign>().configureEach {
|
||||
notCompatibleWithConfigurationCache( "not compatible" )
|
||||
}
|
||||
withType<PublishToMavenRepository>().configureEach {
|
||||
notCompatibleWithConfigurationCache( "not compatible" )
|
||||
}
|
||||
withType<CloseNexusStagingRepository>().configureEach {
|
||||
notCompatibleWithConfigurationCache( "not compatible" )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +166,15 @@ allprojects {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Error Prone requires at lease Java 11
|
||||
val java = (project as ExtensionAware).extensions.getByName("java") as JavaPluginExtension
|
||||
val javaToolchains = (project as ExtensionAware).extensions.getByName("javaToolchains") as JavaToolchainService
|
||||
if( java.toolchain.languageVersion.get().asInt() < 11 ) {
|
||||
javaCompiler.set( javaToolchains.compilerFor {
|
||||
languageVersion.set( JavaLanguageVersion.of( 11 ) )
|
||||
} )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.gradle.kotlin.dsl.support.serviceOf
|
||||
|
||||
plugins {
|
||||
`cpp-library`
|
||||
}
|
||||
@@ -37,9 +39,14 @@ tasks {
|
||||
doFirst {
|
||||
println( "Used Tool Chain:" )
|
||||
println( " - ${toolChain.get()}" )
|
||||
println( "Available Tool Chains:" )
|
||||
toolChains.forEach {
|
||||
println( " - $it" )
|
||||
}
|
||||
|
||||
if( !project.gradle.serviceOf<BuildFeatures>().configurationCache.active.get() ) {
|
||||
doFirst {
|
||||
println( "Available Tool Chains:" )
|
||||
toolChains.forEach {
|
||||
println( " - $it" )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,14 @@ plugins {
|
||||
java
|
||||
}
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
// for Eclipse IDE project import, exclude if:
|
||||
// - plugin "eclipse" is applied; e.g. if running in Eclipse IDE with buildship plugin
|
||||
// - no taskNames specified at command line; e.g. if buildship synchronizes projects
|
||||
val exclude =
|
||||
rootProject.plugins.hasPlugin( "eclipse" ) &&
|
||||
gradle.startParameter.taskNames.isEmpty()
|
||||
|
||||
if( !exclude ) {
|
||||
sourceSets {
|
||||
create( "java9" ) {
|
||||
java {
|
||||
@@ -35,6 +42,13 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
named<JavaCompile>( "compileJava9Java" ) {
|
||||
sourceCompatibility = "9"
|
||||
targetCompatibility = "9"
|
||||
|
||||
// if global toolchain is Java 8, then use Java 11 to build
|
||||
if( java.toolchain.languageVersion.get().asInt() < 9 ) {
|
||||
javaCompiler.set( javaToolchains.compilerFor {
|
||||
languageVersion.set( JavaLanguageVersion.of( 11 ) )
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
|
||||
@@ -29,7 +29,14 @@ plugins {
|
||||
java
|
||||
}
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
// for Eclipse IDE project import, exclude if:
|
||||
// - plugin "eclipse" is applied; e.g. if running in Eclipse IDE with buildship plugin
|
||||
// - no taskNames specified at command line; e.g. if buildship synchronizes projects
|
||||
val exclude =
|
||||
rootProject.plugins.hasPlugin( "eclipse" ) &&
|
||||
gradle.startParameter.taskNames.isEmpty()
|
||||
|
||||
if( !exclude ) {
|
||||
sourceSets {
|
||||
create( "module-info" ) {
|
||||
java {
|
||||
@@ -38,10 +45,11 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
setSrcDirs( listOf( "src/main/module-info", "src/main/java", "src/main/java9" ) )
|
||||
|
||||
// exclude Java 8 source file if an equally named Java 9+ source file exists
|
||||
val projectDir = projectDir // necessary for configuration cache
|
||||
exclude {
|
||||
if( it.isDirectory )
|
||||
return@exclude false
|
||||
val java9file = file( "${projectDir}/src/main/java9/${it.path}" )
|
||||
val java9file = File( "${projectDir}/src/main/java9/${it.path}" )
|
||||
java9file.exists() && java9file != it.file
|
||||
}
|
||||
}
|
||||
@@ -58,6 +66,13 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
|
||||
options.compilerArgs.add( "--module-path" )
|
||||
options.compilerArgs.add( configurations.runtimeClasspath.get().asPath
|
||||
+ File.pathSeparator + configurations.compileClasspath.get().asPath )
|
||||
|
||||
// if global toolchain is Java 8, then use Java 11 to build
|
||||
if( java.toolchain.languageVersion.get().asInt() < 9 ) {
|
||||
javaCompiler.set( javaToolchains.compilerFor {
|
||||
languageVersion.set( JavaLanguageVersion.of( 11 ) )
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
|
||||
open class NativeArtifact( val fileName: String, val classifier: String, val type: String ) {}
|
||||
open class NativeArtifact( val fileName: String, val classifier: String, val extension: String ) {}
|
||||
|
||||
open class PublishExtension {
|
||||
var artifactId: String? = null
|
||||
@@ -77,35 +77,14 @@ publishing {
|
||||
|
||||
afterEvaluate {
|
||||
extension.nativeArtifacts?.forEach {
|
||||
artifact( artifacts.add( "archives", file( it.fileName ) ) {
|
||||
artifact( file( it.fileName ) ) {
|
||||
classifier = it.classifier
|
||||
type = it.type
|
||||
} )
|
||||
extension = it.extension
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
repositories {
|
||||
maven {
|
||||
name = "MavenCentral"
|
||||
|
||||
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 sonatypeUsername: String? by project
|
||||
val sonatypePassword: String? by project
|
||||
|
||||
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
|
||||
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
signing {
|
||||
@@ -125,10 +104,22 @@ tasks.withType<Sign>().configureEach {
|
||||
onlyIf { rootProject.hasProperty( "release" ) }
|
||||
}
|
||||
|
||||
// check whether parallel build is enabled
|
||||
tasks.withType<AbstractPublishToMaven>().configureEach {
|
||||
doFirst {
|
||||
if( System.getProperty( "org.gradle.parallel" ) == "true" )
|
||||
throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )
|
||||
tasks {
|
||||
// check whether parallel build is enabled
|
||||
withType<AbstractPublishToMaven>().configureEach {
|
||||
doFirst {
|
||||
if( System.getProperty( "org.gradle.parallel" ) == "true" )
|
||||
throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )
|
||||
}
|
||||
}
|
||||
|
||||
register( "publishToSonatypeAndCloseStagingRepo" ) {
|
||||
group = "publishing"
|
||||
description = "Publish to Sonatype Maven Central and close staging repository"
|
||||
|
||||
dependsOn(
|
||||
"publishToSonatype",
|
||||
":closeSonatypeStagingRepository"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,8 @@ plugins {
|
||||
java
|
||||
}
|
||||
|
||||
val toolchainJavaVersion = System.getProperty( "toolchain" )
|
||||
if( !toolchainJavaVersion.isNullOrEmpty() ) {
|
||||
java.toolchain {
|
||||
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
|
||||
}
|
||||
val toolchainJavaVersion: String by rootProject.extra
|
||||
|
||||
java.toolchain {
|
||||
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
|
||||
}
|
||||
|
||||
@@ -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" ) )
|
||||
}
|
||||
|
||||
@@ -98,21 +98,31 @@ tasks {
|
||||
group = "verification"
|
||||
dependsOn( "jar" )
|
||||
|
||||
// necessary for configuration cache
|
||||
val classpath = sigtest.asPath
|
||||
val signatureFile = "${project.name}-sigtest.txt"
|
||||
val jarPath = jar.get().outputs.files.asPath
|
||||
val version = version
|
||||
|
||||
doLast {
|
||||
ant.withGroovyBuilder {
|
||||
"taskdef"(
|
||||
"name" to "sigtest",
|
||||
"classname" to "org.netbeans.apitest.Sigtest",
|
||||
"classpath" to sigtest.asPath )
|
||||
"classpath" to classpath )
|
||||
|
||||
"sigtest"(
|
||||
"action" to "generate",
|
||||
"fileName" to "${project.name}-sigtest.txt",
|
||||
"classpath" to jar.get().outputs.files.asPath,
|
||||
"fileName" to signatureFile,
|
||||
"classpath" to jarPath,
|
||||
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.themes,com.formdev.flatlaf.util",
|
||||
"version" to version,
|
||||
"release" to "1.8", // Java version
|
||||
"failonerror" to "true" )
|
||||
|
||||
"fixcrlf"(
|
||||
"file" to signatureFile,
|
||||
"eol" to "lf" )
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,17 +131,23 @@ tasks {
|
||||
group = "verification"
|
||||
dependsOn( "jar" )
|
||||
|
||||
// necessary for configuration cache
|
||||
val classpath = sigtest.asPath
|
||||
val signatureFile = "${project.name}-sigtest.txt"
|
||||
val jarPath = jar.get().outputs.files.asPath
|
||||
val version = version
|
||||
|
||||
doLast {
|
||||
ant.withGroovyBuilder {
|
||||
"taskdef"(
|
||||
"name" to "sigtest",
|
||||
"classname" to "org.netbeans.apitest.Sigtest",
|
||||
"classpath" to sigtest.asPath )
|
||||
"classpath" to classpath )
|
||||
|
||||
"sigtest"(
|
||||
"action" to "check",
|
||||
"fileName" to "${project.name}-sigtest.txt",
|
||||
"classpath" to jar.get().outputs.files.asPath,
|
||||
"fileName" to signatureFile,
|
||||
"classpath" to jarPath,
|
||||
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
|
||||
"version" to version,
|
||||
"release" to "1.8", // Java version
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#Signature file v4.1
|
||||
#Version 3.6.2
|
||||
#Version 3.7
|
||||
|
||||
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
|
||||
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
|
||||
@@ -87,6 +87,7 @@ fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "unde
|
||||
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode"
|
||||
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact"
|
||||
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_EQUAL = "equal"
|
||||
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY = "iconOnly"
|
||||
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_PREFERRED = "preferred"
|
||||
fld public final static java.lang.String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent"
|
||||
fld public final static java.lang.String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"
|
||||
@@ -307,6 +308,7 @@ fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "fla
|
||||
fld public final static java.lang.String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
|
||||
fld public final static java.lang.String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder"
|
||||
fld public final static java.lang.String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle"
|
||||
fld public final static java.lang.String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser"
|
||||
fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection"
|
||||
fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"
|
||||
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"
|
||||
@@ -770,6 +772,112 @@ cons public init()
|
||||
meth public static <%0 extends java.awt.Component> {%%0} getComponentByName(java.awt.Container,java.lang.String)
|
||||
supr java.lang.Object
|
||||
|
||||
CLSS public com.formdev.flatlaf.util.SystemFileChooser
|
||||
cons public init()
|
||||
cons public init(java.io.File)
|
||||
cons public init(java.lang.String)
|
||||
fld public final static int APPROVE_OPTION = 0
|
||||
fld public final static int CANCEL_OPTION = 1
|
||||
fld public final static int DIRECTORIES_ONLY = 1
|
||||
fld public final static int FILES_ONLY = 0
|
||||
fld public final static int OPEN_DIALOG = 0
|
||||
fld public final static int SAVE_DIALOG = 1
|
||||
fld public final static java.lang.String LINUX_OPTIONS_CLEAR = "linux.optionsClear"
|
||||
fld public final static java.lang.String LINUX_OPTIONS_SET = "linux.optionsSet"
|
||||
fld public final static java.lang.String MAC_FILTER_FIELD_LABEL = "mac.filterFieldLabel"
|
||||
fld public final static java.lang.String MAC_MESSAGE = "mac.message"
|
||||
fld public final static java.lang.String MAC_NAME_FIELD_LABEL = "mac.nameFieldLabel"
|
||||
fld public final static java.lang.String MAC_OPTIONS_CLEAR = "mac.optionsClear"
|
||||
fld public final static java.lang.String MAC_OPTIONS_SET = "mac.optionsSet"
|
||||
fld public final static java.lang.String MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES = "mac.treatsFilePackagesAsDirectories"
|
||||
fld public final static java.lang.String WINDOWS_DEFAULT_EXTENSION = "windows.defaultExtension"
|
||||
fld public final static java.lang.String WINDOWS_DEFAULT_FOLDER = "windows.defaultFolder"
|
||||
fld public final static java.lang.String WINDOWS_FILE_NAME_LABEL = "windows.fileNameLabel"
|
||||
fld public final static java.lang.String WINDOWS_OPTIONS_CLEAR = "windows.optionsClear"
|
||||
fld public final static java.lang.String WINDOWS_OPTIONS_SET = "windows.optionsSet"
|
||||
innr public abstract interface static ApproveCallback
|
||||
innr public abstract interface static StateStore
|
||||
innr public abstract static ApproveContext
|
||||
innr public abstract static FileFilter
|
||||
innr public final static FileNameExtensionFilter
|
||||
meth public <%0 extends java.lang.Object> {%%0} getPlatformProperty(java.lang.String)
|
||||
meth public boolean isAcceptAllFileFilterUsed()
|
||||
meth public boolean isDirectorySelectionEnabled()
|
||||
meth public boolean isFileHidingEnabled()
|
||||
meth public boolean isFileSelectionEnabled()
|
||||
meth public boolean isMultiSelectionEnabled()
|
||||
meth public boolean removeChoosableFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
|
||||
meth public com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback getApproveCallback()
|
||||
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter getAcceptAllFileFilter()
|
||||
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter getFileFilter()
|
||||
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter[] getChoosableFileFilters()
|
||||
meth public int getApproveButtonMnemonic()
|
||||
meth public int getDialogType()
|
||||
meth public int getFileSelectionMode()
|
||||
meth public int showDialog(java.awt.Component,java.lang.String)
|
||||
meth public int showOpenDialog(java.awt.Component)
|
||||
meth public int showSaveDialog(java.awt.Component)
|
||||
meth public java.io.File getCurrentDirectory()
|
||||
meth public java.io.File getSelectedFile()
|
||||
meth public java.io.File[] getSelectedFiles()
|
||||
meth public java.lang.String getApproveButtonText()
|
||||
meth public java.lang.String getDialogTitle()
|
||||
meth public java.lang.String getStateStoreID()
|
||||
meth public static com.formdev.flatlaf.util.SystemFileChooser$StateStore getStateStore()
|
||||
meth public static void setStateStore(com.formdev.flatlaf.util.SystemFileChooser$StateStore)
|
||||
meth public void addChoosableFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
|
||||
meth public void putPlatformProperty(java.lang.String,java.lang.Object)
|
||||
meth public void resetChoosableFileFilters()
|
||||
meth public void setAcceptAllFileFilterUsed(boolean)
|
||||
meth public void setApproveButtonMnemonic(char)
|
||||
meth public void setApproveButtonMnemonic(int)
|
||||
meth public void setApproveButtonText(java.lang.String)
|
||||
meth public void setApproveCallback(com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback)
|
||||
meth public void setCurrentDirectory(java.io.File)
|
||||
meth public void setDialogTitle(java.lang.String)
|
||||
meth public void setDialogType(int)
|
||||
meth public void setFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
|
||||
meth public void setFileHidingEnabled(boolean)
|
||||
meth public void setFileSelectionMode(int)
|
||||
meth public void setMultiSelectionEnabled(boolean)
|
||||
meth public void setSelectedFile(java.io.File)
|
||||
meth public void setSelectedFiles(java.io.File[])
|
||||
meth public void setStateStoreID(java.lang.String)
|
||||
supr java.lang.Object
|
||||
hfds acceptAllFileFilter,approveButtonMnemonic,approveButtonText,approveCallback,approveResult,currentDirectory,dialogTitle,dialogType,fileFilter,fileSelectionMode,filters,inMemoryStateStore,keepAcceptAllAtEnd,multiSelection,platformProperties,selectedFile,selectedFiles,stateStore,stateStoreID,useAcceptAllFileFilter,useFileHiding
|
||||
hcls AcceptAllFileFilter,FileChooserProvider,LinuxFileChooserProvider,MacFileChooserProvider,SwingFileChooserProvider,SystemFileChooserProvider,WindowsFileChooserProvider
|
||||
|
||||
CLSS public abstract interface static com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback
|
||||
outer com.formdev.flatlaf.util.SystemFileChooser
|
||||
meth public abstract int approve(java.io.File[],com.formdev.flatlaf.util.SystemFileChooser$ApproveContext)
|
||||
|
||||
CLSS public abstract static com.formdev.flatlaf.util.SystemFileChooser$ApproveContext
|
||||
outer com.formdev.flatlaf.util.SystemFileChooser
|
||||
cons public init()
|
||||
meth public abstract !varargs int showMessageDialog(int,java.lang.String,java.lang.String,int,java.lang.String[])
|
||||
supr java.lang.Object
|
||||
|
||||
CLSS public abstract static com.formdev.flatlaf.util.SystemFileChooser$FileFilter
|
||||
outer com.formdev.flatlaf.util.SystemFileChooser
|
||||
cons public init()
|
||||
meth public abstract java.lang.String getDescription()
|
||||
supr java.lang.Object
|
||||
|
||||
CLSS public final static com.formdev.flatlaf.util.SystemFileChooser$FileNameExtensionFilter
|
||||
outer com.formdev.flatlaf.util.SystemFileChooser
|
||||
cons public !varargs init(java.lang.String,java.lang.String[])
|
||||
meth public java.lang.String getDescription()
|
||||
meth public java.lang.String toString()
|
||||
meth public java.lang.String[] getExtensions()
|
||||
supr com.formdev.flatlaf.util.SystemFileChooser$FileFilter
|
||||
hfds description,extensions
|
||||
|
||||
CLSS public abstract interface static com.formdev.flatlaf.util.SystemFileChooser$StateStore
|
||||
outer com.formdev.flatlaf.util.SystemFileChooser
|
||||
fld public final static java.lang.String KEY_CURRENT_DIRECTORY = "currentDirectory"
|
||||
meth public abstract java.lang.String get(java.lang.String,java.lang.String)
|
||||
meth public abstract void put(java.lang.String,java.lang.String)
|
||||
|
||||
CLSS public com.formdev.flatlaf.util.SystemInfo
|
||||
cons public init()
|
||||
fld public final static boolean isAARCH64
|
||||
@@ -806,13 +914,21 @@ supr java.lang.Object
|
||||
|
||||
CLSS public com.formdev.flatlaf.util.UIScale
|
||||
cons public init()
|
||||
fld public final static java.lang.String PROP_USER_SCALE_FACTOR = "userScaleFactor"
|
||||
fld public final static java.lang.String PROP_ZOOM_FACTOR = "zoomFactor"
|
||||
meth public static boolean isSystemScalingEnabled()
|
||||
meth public static boolean setZoomFactor(float)
|
||||
meth public static boolean zoomIn()
|
||||
meth public static boolean zoomOut()
|
||||
meth public static boolean zoomReset()
|
||||
meth public static double getSystemScaleFactor(java.awt.Graphics2D)
|
||||
meth public static double getSystemScaleFactor(java.awt.GraphicsConfiguration)
|
||||
meth public static float computeFontScaleFactor(java.awt.Font)
|
||||
meth public static float getUserScaleFactor()
|
||||
meth public static float getZoomFactor()
|
||||
meth public static float scale(float)
|
||||
meth public static float unscale(float)
|
||||
meth public static float[] getSupportedZoomFactors()
|
||||
meth public static int scale(int)
|
||||
meth public static int scale2(int)
|
||||
meth public static int unscale(int)
|
||||
@@ -822,8 +938,9 @@ meth public static javax.swing.plaf.FontUIResource applyCustomScaleFactor(javax.
|
||||
meth public static void addPropertyChangeListener(java.beans.PropertyChangeListener)
|
||||
meth public static void removePropertyChangeListener(java.beans.PropertyChangeListener)
|
||||
meth public static void scaleGraphics(java.awt.Graphics2D)
|
||||
meth public static void setSupportedZoomFactors(float[])
|
||||
supr java.lang.Object
|
||||
hfds DEBUG,changeSupport,initialized,jreHiDPI,scaleFactor
|
||||
hfds DEBUG,changeSupport,ignoreFontChange,inUnitTests,initialized,jreHiDPI,listenerInitialized,scaleFactor,supportedZoomFactors,unzoomedScaleFactor,zoomFactor
|
||||
|
||||
CLSS public java.awt.Color
|
||||
cons public init(float,float,float)
|
||||
|
||||
@@ -1086,8 +1086,9 @@ public interface FlatClientProperties
|
||||
* <strong>Value type</strong> {@link java.lang.String}<br>
|
||||
* <strong>Allowed Values</strong>
|
||||
* {@link #TABBED_PANE_TAB_WIDTH_MODE_PREFERRED} (default),
|
||||
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or
|
||||
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
|
||||
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL},
|
||||
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT} or
|
||||
* {@link #TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY}
|
||||
*/
|
||||
String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode";
|
||||
|
||||
@@ -1113,6 +1114,14 @@ public interface FlatClientProperties
|
||||
*/
|
||||
String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact";
|
||||
|
||||
/**
|
||||
* All tabs are smaller because they show only the tab icon, but no tab title.
|
||||
*
|
||||
* @see #TABBED_PANE_TAB_WIDTH_MODE
|
||||
* @since 3.7
|
||||
*/
|
||||
String TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY = "iconOnly";
|
||||
|
||||
/**
|
||||
* Specifies the tab icon placement (relative to tab title).
|
||||
* <p>
|
||||
|
||||
@@ -310,8 +310,8 @@ public abstract class FlatLaf
|
||||
// install submenu usability helper
|
||||
subMenuUsabilityHelperInstalled = SubMenuUsabilityHelper.install();
|
||||
|
||||
// install Linux popup menu canceler
|
||||
if( SystemInfo.isLinux )
|
||||
// install Linux/macOS popup menu canceler
|
||||
if( SystemInfo.isLinux || SystemInfo.isMacOS )
|
||||
linuxPopupMenuCanceler = new LinuxPopupMenuCanceler();
|
||||
|
||||
// listen to desktop property changes to update UI if system font or scaling changes
|
||||
@@ -368,6 +368,22 @@ public abstract class FlatLaf
|
||||
String.format( "a, address { color: #%06x; }", linkColor.getRGB() & 0xffffff ) );
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize UIScale user scale factor immediately after FlatLaf was activated,
|
||||
// which is necessary to ensure that UIScale.setZoomFactor(float)
|
||||
// scales FlatLaf defaultDont correctly even if UIScale.scale() was not yet used.
|
||||
// In other words: Without this, UIScale.setZoomFactor(float) would
|
||||
// not work correctly if invoked between FlatLaf.setup() and crating UI.
|
||||
PropertyChangeListener listener = new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange( PropertyChangeEvent e ) {
|
||||
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
|
||||
UIManager.removePropertyChangeListener( this );
|
||||
UIScale.getUserScaleFactor();
|
||||
}
|
||||
}
|
||||
};
|
||||
UIManager.addPropertyChangeListener( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -707,11 +723,22 @@ public abstract class FlatLaf
|
||||
uiFont = ((ActiveFont)defaultFont).derive( baseFont, fontSize -> {
|
||||
return Math.round( fontSize * UIScale.computeFontScaleFactor( baseFont ) );
|
||||
} );
|
||||
}
|
||||
} else if( defaultFont instanceof LazyValue )
|
||||
uiFont = ActiveFont.toUIResource( (Font) ((LazyValue)defaultFont).createValue( defaults ) );
|
||||
|
||||
// increase font size if system property "flatlaf.uiScale" is set
|
||||
uiFont = UIScale.applyCustomScaleFactor( uiFont );
|
||||
|
||||
// apply zoom factor to font size
|
||||
float zoomFactor = UIScale.getZoomFactor();
|
||||
if( zoomFactor != 1 ) {
|
||||
// see also UIScale.setZoomFactor()
|
||||
int unzoomedFontSize = uiFont.getSize();
|
||||
defaults.put( "defaultFont.unzoomedSize", unzoomedFontSize );
|
||||
int newFontSize = Math.max( Math.round( unzoomedFontSize * zoomFactor ), 1 );
|
||||
uiFont = new FontUIResource( uiFont.deriveFont( (float) newFontSize ) );
|
||||
}
|
||||
|
||||
// set default font
|
||||
defaults.put( "defaultFont", uiFont );
|
||||
}
|
||||
@@ -1768,7 +1795,7 @@ public abstract class FlatLaf
|
||||
return toUIResource( baseFont );
|
||||
}
|
||||
|
||||
private FontUIResource toUIResource( Font font ) {
|
||||
private static FontUIResource toUIResource( Font font ) {
|
||||
// make sure that font is a UIResource for LaF switching
|
||||
return (font instanceof FontUIResource)
|
||||
? (FontUIResource) font
|
||||
|
||||
@@ -16,8 +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;
|
||||
|
||||
/**
|
||||
@@ -246,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}.
|
||||
|
||||
@@ -58,9 +58,9 @@ public class IntelliJTheme
|
||||
public final boolean dark;
|
||||
public final String author;
|
||||
|
||||
private Map<String, String> jsonColors;
|
||||
private Map<String, Object> jsonUI;
|
||||
private Map<String, Object> jsonIcons;
|
||||
private final Map<String, String> jsonColors;
|
||||
private final Map<String, Object> jsonUI;
|
||||
private final Map<String, Object> jsonIcons;
|
||||
|
||||
private Map<String, String> namedColors = Collections.emptyMap();
|
||||
|
||||
@@ -276,11 +276,6 @@ public class IntelliJTheme
|
||||
|
||||
put( properties, key, value );
|
||||
}
|
||||
|
||||
// let Java release memory
|
||||
jsonColors = null;
|
||||
jsonUI = null;
|
||||
jsonIcons = null;
|
||||
}
|
||||
|
||||
private String get( Properties properties, Map<String, String> themeSpecificProps, String key ) {
|
||||
|
||||
@@ -30,7 +30,7 @@ import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
/**
|
||||
* Cancels (hides) popup menus on Linux.
|
||||
* Cancels (hides) popup menus on Linux and macOS.
|
||||
* <p>
|
||||
* On Linux, popups are not hidden under following conditions, which results in
|
||||
* misplaced popups:
|
||||
@@ -41,7 +41,13 @@ import javax.swing.event.ChangeListener;
|
||||
* <li>window deactivated (e.g. activated other application)
|
||||
* </ul>
|
||||
*
|
||||
* On Windows and macOS, popups are automatically hidden.
|
||||
* On macOS, popups are usually automatically hidden, but not always.
|
||||
* When resizing a window, then it depends where clicking to start resizing (and on the Java version).
|
||||
* E.g. with Java 25, clicking at bottom-right corner inside of the window does not hide the popups.
|
||||
* But clicking on same corner outside of the window, hides the popup.
|
||||
*
|
||||
* <p>
|
||||
* On Windows, popups are automatically hidden.
|
||||
* <p>
|
||||
* The implementation is similar to what's done in
|
||||
* {@code javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber},
|
||||
|
||||
@@ -45,6 +45,7 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import javax.swing.Icon;
|
||||
@@ -431,7 +432,7 @@ class UIDefaultsLoader
|
||||
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, INTEGERORFLOAT, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, FONT,
|
||||
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
|
||||
|
||||
private static final ValueType[] tempResultValueType = new ValueType[1];
|
||||
private static final AtomicReference<ValueType> tempResultValueType = new AtomicReference<>();
|
||||
private static Map<Class<?>, ValueType> javaValueTypes;
|
||||
private static Map<String, ValueType> knownValueTypes;
|
||||
|
||||
@@ -441,7 +442,7 @@ class UIDefaultsLoader
|
||||
return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() );
|
||||
}
|
||||
|
||||
static Object parseValue( String key, String value, Class<?> javaValueType, ValueType[] resultValueType,
|
||||
static Object parseValue( String key, String value, Class<?> javaValueType, AtomicReference<ValueType> resultValueType,
|
||||
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
@@ -450,7 +451,7 @@ class UIDefaultsLoader
|
||||
|
||||
// do not parse styles here
|
||||
if( key.startsWith( "[style]" ) ) {
|
||||
resultValueType[0] = ValueType.STRING;
|
||||
resultValueType.set( ValueType.STRING );
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -458,7 +459,7 @@ class UIDefaultsLoader
|
||||
|
||||
// null
|
||||
if( value.equals( "null" ) || value.isEmpty() ) {
|
||||
resultValueType[0] = ValueType.NULL;
|
||||
resultValueType.set( ValueType.NULL );
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -514,14 +515,14 @@ class UIDefaultsLoader
|
||||
} else {
|
||||
// false, true
|
||||
switch( value ) {
|
||||
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
|
||||
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
|
||||
case "false": resultValueType.set( ValueType.BOOLEAN ); return false;
|
||||
case "true": resultValueType.set( ValueType.BOOLEAN ); return true;
|
||||
}
|
||||
|
||||
// check for function "lazy"
|
||||
// Syntax: lazy(uiKey)
|
||||
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
|
||||
resultValueType[0] = ValueType.LAZY;
|
||||
resultValueType.set( ValueType.LAZY );
|
||||
String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 );
|
||||
return (LazyValue) t -> {
|
||||
return lazyUIManagerGet( uiKey );
|
||||
@@ -602,7 +603,7 @@ class UIDefaultsLoader
|
||||
}
|
||||
}
|
||||
|
||||
resultValueType[0] = valueType;
|
||||
resultValueType.set( valueType );
|
||||
|
||||
// parse value
|
||||
switch( valueType ) {
|
||||
@@ -629,14 +630,14 @@ class UIDefaultsLoader
|
||||
default:
|
||||
// string
|
||||
if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
|
||||
resultValueType[0] = ValueType.STRING;
|
||||
resultValueType.set( ValueType.STRING );
|
||||
return value.substring( 1, value.length() - 1 );
|
||||
}
|
||||
|
||||
// colors
|
||||
if( value.startsWith( "#" ) || value.endsWith( ")" ) ) {
|
||||
Object color = parseColorOrFunction( value, resolver );
|
||||
resultValueType[0] = (color != null) ? ValueType.COLOR : ValueType.NULL;
|
||||
resultValueType.set( (color != null) ? ValueType.COLOR : ValueType.NULL );
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -648,7 +649,7 @@ class UIDefaultsLoader
|
||||
// integer
|
||||
try {
|
||||
Integer integer = parseInteger( value );
|
||||
resultValueType[0] = ValueType.INTEGER;
|
||||
resultValueType.set( ValueType.INTEGER );
|
||||
return integer;
|
||||
} catch( NumberFormatException ex ) {
|
||||
// ignore
|
||||
@@ -657,7 +658,7 @@ class UIDefaultsLoader
|
||||
// float
|
||||
try {
|
||||
Float f = parseFloat( value );
|
||||
resultValueType[0] = ValueType.FLOAT;
|
||||
resultValueType.set( ValueType.FLOAT );
|
||||
return f;
|
||||
} catch( NumberFormatException ex ) {
|
||||
// ignore
|
||||
@@ -665,7 +666,7 @@ class UIDefaultsLoader
|
||||
}
|
||||
|
||||
// string
|
||||
resultValueType[0] = ValueType.STRING;
|
||||
resultValueType.set( ValueType.STRING );
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.formdev.flatlaf.icons;
|
||||
|
||||
import static com.formdev.flatlaf.util.UIScale.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
@@ -29,7 +28,7 @@ import com.formdev.flatlaf.util.UIScale;
|
||||
/**
|
||||
* Base class for icons that scales width and height, creates and initializes
|
||||
* a scaled graphics context for icon painting.
|
||||
*
|
||||
* <p>
|
||||
* Subclasses do not need to scale icon painting.
|
||||
*
|
||||
* @author Karl Tauber
|
||||
@@ -37,10 +36,15 @@ import com.formdev.flatlaf.util.UIScale;
|
||||
public abstract class FlatAbstractIcon
|
||||
implements Icon, UIResource
|
||||
{
|
||||
/** Unscaled icon width. */
|
||||
protected final int width;
|
||||
/** Unscaled icon height. */
|
||||
protected final int height;
|
||||
protected Color color;
|
||||
|
||||
/** Additional icon scale factor. */
|
||||
private float scale = 1;
|
||||
|
||||
public FlatAbstractIcon( int width, int height, Color color ) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
@@ -61,6 +65,9 @@ public abstract class FlatAbstractIcon
|
||||
|
||||
g2.translate( x, y );
|
||||
UIScale.scaleGraphics( g2 );
|
||||
float scale = getScale();
|
||||
if( scale != 1 )
|
||||
g2.scale( scale, scale );
|
||||
|
||||
if( color != null )
|
||||
g2.setColor( color );
|
||||
@@ -71,19 +78,71 @@ public abstract class FlatAbstractIcon
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.5.2 */
|
||||
/**
|
||||
* Paints icon background. Default implementation does nothing.
|
||||
* Can be overridden to paint specific icon background.
|
||||
* <p>
|
||||
* The bounds of the area to be filled are:
|
||||
* x, y, {@link #getIconWidth()}, {@link #getIconHeight()}.
|
||||
* <p>
|
||||
* In contrast to {@link #paintIcon(Component, Graphics2D)},
|
||||
* the graphics context {@code g} is not translated and not scaled.
|
||||
*
|
||||
* @since 3.5.2
|
||||
*/
|
||||
protected void paintBackground( Component c, Graphics2D g, int x, int y ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints icon.
|
||||
* <p>
|
||||
* The graphics context is translated and scaled.
|
||||
* This means that icon x,y coordinates are {@code 0,0}
|
||||
* and it is not necessary to scale coordinates within this method.
|
||||
* <p>
|
||||
* The bounds to be used for icon painting are:
|
||||
* 0, 0, {@link #width}, {@link #height}.
|
||||
*/
|
||||
protected abstract void paintIcon( Component c, Graphics2D g );
|
||||
|
||||
/**
|
||||
* Returns the scaled icon width.
|
||||
*/
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return scale( width );
|
||||
return scale( UIScale.scale( width ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scaled icon height.
|
||||
*/
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return scale( height );
|
||||
return scale( UIScale.scale( height ) );
|
||||
}
|
||||
|
||||
/** @since 3.7 */
|
||||
public float getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
/** @since 3.7 */
|
||||
public void setScale( float scale ) {
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the given value by the icon scale factor {@link #getScale()} and rounds the result.
|
||||
* <p>
|
||||
* If you want scale a {@code float} or {@code double} value,
|
||||
* simply use: {@code myFloatValue * }{@link #getScale()}.
|
||||
* <p>
|
||||
* Do not use this method when painting icon in {@link #paintIcon(Component, Graphics2D)}.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
protected int scale( int size ) {
|
||||
float scale = getScale();
|
||||
return (scale == 1) ? size : Math.round( size * scale );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.formdev.flatlaf.icons;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
@@ -26,7 +25,8 @@ import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
|
||||
/**
|
||||
@@ -36,8 +36,11 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="capsLockIconScale", fieldName="scale" )
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="capsLockIconColor", fieldName="color" )
|
||||
public class FlatCapsLockIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
private Path2D path;
|
||||
|
||||
@@ -45,23 +48,6 @@ public class FlatCapsLockIcon
|
||||
super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
Object oldValue;
|
||||
switch( key ) {
|
||||
case "capsLockIconColor": oldValue = color; color = (Color) value; return oldValue;
|
||||
default: throw new UnknownStyleException( key );
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
switch( key ) {
|
||||
case "capsLockIconColor": return color;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
/*
|
||||
|
||||
@@ -24,13 +24,13 @@ import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatButtonUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
|
||||
/**
|
||||
@@ -101,8 +101,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
|
||||
public class FlatCheckBoxIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
protected final String style = UIManager.getString( getPropertyPrefix() + "icon.style" );
|
||||
@Styleable protected float focusWidth = getUIFloat( "CheckBox.icon.focusWidth", UIManager.getInt( "Component.focusWidth" ), style );
|
||||
@@ -197,21 +199,6 @@ public class FlatCheckBoxIcon
|
||||
super( ICON_SIZE, ICON_SIZE, null );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
boolean indeterminate = isIndeterminate( c );
|
||||
@@ -306,7 +293,7 @@ public class FlatCheckBoxIcon
|
||||
|
||||
/** @since 2 */
|
||||
public float getFocusWidth() {
|
||||
return focusWidth;
|
||||
return focusWidth * getScale();
|
||||
}
|
||||
|
||||
protected Color getFocusColor( Component c ) {
|
||||
|
||||
@@ -21,12 +21,12 @@ import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
|
||||
/**
|
||||
* Icon for {@link javax.swing.JCheckBoxMenuItem}.
|
||||
@@ -38,8 +38,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
|
||||
public class FlatCheckBoxMenuItemIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Color checkmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.checkmarkColor" );
|
||||
@Styleable protected Color disabledCheckmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.disabledCheckmarkColor" );
|
||||
@@ -49,21 +51,6 @@ public class FlatCheckBoxMenuItemIcon
|
||||
super( 15, 15, null );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g2 ) {
|
||||
boolean selected = (c instanceof AbstractButton) && ((AbstractButton)c).isSelected();
|
||||
|
||||
@@ -21,12 +21,12 @@ import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.ButtonModel;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
|
||||
/**
|
||||
@@ -39,8 +39,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
* @author Karl Tauber
|
||||
* @since 1.5
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="clearIconScale", fieldName="scale" )
|
||||
public class FlatClearIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" );
|
||||
@Styleable protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" );
|
||||
@@ -58,21 +60,6 @@ public class FlatClearIcon
|
||||
this.ignoreButtonState = ignoreButtonState;
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
if( !ignoreButtonState && c instanceof AbstractButton ) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.formdev.flatlaf.icons;
|
||||
|
||||
import static com.formdev.flatlaf.util.UIScale.*;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
@@ -24,11 +23,12 @@ import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatButtonUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
|
||||
/**
|
||||
@@ -52,8 +52,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
|
||||
public class FlatHelpButtonIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
|
||||
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
|
||||
@@ -76,21 +78,6 @@ public class FlatHelpButtonIcon
|
||||
super( 0, 0, null );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g2 ) {
|
||||
/*
|
||||
@@ -167,12 +154,12 @@ public class FlatHelpButtonIcon
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return scale( iconSize() );
|
||||
return scale( UIScale.scale( iconSize() ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return scale( iconSize() );
|
||||
return scale( UIScale.scale( iconSize() ) );
|
||||
}
|
||||
|
||||
private int iconSize() {
|
||||
|
||||
@@ -21,12 +21,12 @@ import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
|
||||
/**
|
||||
* "arrow" icon for {@link javax.swing.JMenu}.
|
||||
@@ -39,8 +39,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
|
||||
public class FlatMenuArrowIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected String arrowType = UIManager.getString( "Component.arrowType" );
|
||||
@Styleable protected Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
|
||||
@@ -51,21 +53,6 @@ public class FlatMenuArrowIcon
|
||||
super( 6, 10, null );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
if( c != null && !c.getComponentOrientation().isLeftToRight() )
|
||||
|
||||
@@ -21,11 +21,11 @@ import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatButtonUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
|
||||
/**
|
||||
@@ -38,8 +38,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
* @author Karl Tauber
|
||||
* @since 1.5
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="searchIconScale", fieldName="scale" )
|
||||
public class FlatSearchIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" );
|
||||
@Styleable protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" );
|
||||
@@ -58,21 +60,6 @@ public class FlatSearchIcon
|
||||
this.ignoreButtonState = ignoreButtonState;
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
/*
|
||||
|
||||
@@ -22,11 +22,11 @@ import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.Map;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatButtonUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
|
||||
/**
|
||||
@@ -46,8 +46,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
@StyleableField( cls=FlatAbstractIcon.class, key="closeScale", fieldName="scale" )
|
||||
public class FlatTabbedPaneCloseIcon
|
||||
extends FlatAbstractIcon
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Dimension closeSize = UIManager.getDimension( "TabbedPane.closeSize" );
|
||||
@Styleable protected int closeArc = UIManager.getInt( "TabbedPane.closeArc" );
|
||||
@@ -65,21 +67,6 @@ public class FlatTabbedPaneCloseIcon
|
||||
super( 16, 16, null );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
// paint background
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Paint;
|
||||
import java.util.Map;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JScrollPane;
|
||||
@@ -32,7 +31,7 @@ import javax.swing.UIManager;
|
||||
import javax.swing.plaf.basic.BasicBorders;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.util.DerivedColor;
|
||||
|
||||
/**
|
||||
@@ -67,7 +66,7 @@ import com.formdev.flatlaf.util.DerivedColor;
|
||||
*/
|
||||
public class FlatBorder
|
||||
extends BasicBorders.MarginBorder
|
||||
implements StyleableBorder
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
|
||||
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
|
||||
@@ -92,24 +91,6 @@ public class FlatBorder
|
||||
/** @since 2 */ @Styleable protected Color outlineColor;
|
||||
/** @since 2 */ @Styleable protected Color outlineFocusedColor;
|
||||
|
||||
/** @since 2 */
|
||||
@Override
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
@Override
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
@Override
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
|
||||
Graphics2D g2 = (Graphics2D) g.create();
|
||||
|
||||
@@ -58,8 +58,8 @@ import javax.swing.plaf.basic.BasicHTML;
|
||||
import javax.swing.text.View;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
@@ -369,8 +369,8 @@ public class FlatButtonUI
|
||||
/** @since 2 */
|
||||
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
|
||||
if( key.startsWith( "help." ) ) {
|
||||
if( !(helpButtonIcon instanceof FlatHelpButtonIcon) )
|
||||
return new UnknownStyleException( key );
|
||||
if( !(helpButtonIcon instanceof StyleableObject) )
|
||||
throw new UnknownStyleException( key );
|
||||
|
||||
if( helpButtonIconShared ) {
|
||||
helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon );
|
||||
@@ -378,7 +378,7 @@ public class FlatButtonUI
|
||||
}
|
||||
|
||||
key = key.substring( "help.".length() );
|
||||
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
|
||||
return ((StyleableObject)helpButtonIcon).applyStyleProperty( key, value );
|
||||
}
|
||||
|
||||
// update internal values; otherwise isCustomBackground() and isCustomForeground() would return wrong results
|
||||
@@ -399,8 +399,8 @@ public class FlatButtonUI
|
||||
@Override
|
||||
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
|
||||
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this, c.getBorder() );
|
||||
if( helpButtonIcon instanceof FlatHelpButtonIcon )
|
||||
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((FlatHelpButtonIcon)helpButtonIcon).getStyleableInfos() );
|
||||
if( helpButtonIcon instanceof StyleableObject )
|
||||
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((StyleableObject)helpButtonIcon).getStyleableInfos() );
|
||||
return infos;
|
||||
}
|
||||
|
||||
@@ -408,8 +408,8 @@ public class FlatButtonUI
|
||||
@Override
|
||||
public Object getStyleableValue( JComponent c, String key ) {
|
||||
if( key.startsWith( "help." ) ) {
|
||||
return (helpButtonIcon instanceof FlatHelpButtonIcon)
|
||||
? ((FlatHelpButtonIcon)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
|
||||
return (helpButtonIcon instanceof StyleableObject)
|
||||
? ((StyleableObject)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ import com.formdev.flatlaf.util.SystemInfo;
|
||||
* @uiDefault ComboBox.buttonBackground Color optional
|
||||
* @uiDefault ComboBox.buttonEditableBackground Color optional
|
||||
* @uiDefault ComboBox.buttonFocusedBackground Color optional; defaults to ComboBox.focusedBackground
|
||||
* @uiDefault ComboBox.buttonFocusedEditableBackground Color optional; defaults to ComboBox.buttonEditableBackground
|
||||
* @uiDefault ComboBox.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
|
||||
* @uiDefault ComboBox.buttonSeparatorColor Color optional
|
||||
* @uiDefault ComboBox.buttonDisabledSeparatorColor Color optional
|
||||
@@ -147,6 +148,7 @@ public class FlatComboBoxUI
|
||||
@Styleable protected Color buttonBackground;
|
||||
@Styleable protected Color buttonEditableBackground;
|
||||
@Styleable protected Color buttonFocusedBackground;
|
||||
/** @since 3.7.1 */ @Styleable protected Color buttonFocusedEditableBackground;
|
||||
/** @since 2 */ @Styleable protected float buttonSeparatorWidth;
|
||||
/** @since 2 */ @Styleable protected Color buttonSeparatorColor;
|
||||
/** @since 2 */ @Styleable protected Color buttonDisabledSeparatorColor;
|
||||
@@ -258,6 +260,7 @@ public class FlatComboBoxUI
|
||||
|
||||
buttonBackground = UIManager.getColor( "ComboBox.buttonBackground" );
|
||||
buttonFocusedBackground = UIManager.getColor( "ComboBox.buttonFocusedBackground" );
|
||||
buttonFocusedEditableBackground = UIManager.getColor( "ComboBox.buttonFocusedEditableBackground" );
|
||||
buttonEditableBackground = UIManager.getColor( "ComboBox.buttonEditableBackground" );
|
||||
buttonSeparatorWidth = FlatUIUtils.getUIFloat( "ComboBox.buttonSeparatorWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ) );
|
||||
buttonSeparatorColor = UIManager.getColor( "ComboBox.buttonSeparatorColor" );
|
||||
@@ -293,6 +296,7 @@ public class FlatComboBoxUI
|
||||
buttonBackground = null;
|
||||
buttonEditableBackground = null;
|
||||
buttonFocusedBackground = null;
|
||||
buttonFocusedEditableBackground = null;
|
||||
buttonSeparatorColor = null;
|
||||
buttonDisabledSeparatorColor = null;
|
||||
buttonArrowColor = null;
|
||||
@@ -587,7 +591,7 @@ public class FlatComboBoxUI
|
||||
// paint arrow button background
|
||||
if( enabled && !isCellRenderer && arrowButton.isVisible() ) {
|
||||
Color buttonColor = paintButton
|
||||
? buttonEditableBackground
|
||||
? (buttonFocusedEditableBackground != null && isPermanentFocusOwner( comboBox ) ? buttonFocusedEditableBackground : buttonEditableBackground)
|
||||
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
|
||||
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
|
||||
: buttonBackground;
|
||||
|
||||
@@ -24,9 +24,8 @@ import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.RadialGradientPaint;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Map;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
|
||||
@@ -43,7 +42,7 @@ import com.formdev.flatlaf.util.UIScale;
|
||||
*/
|
||||
public class FlatDropShadowBorder
|
||||
extends FlatEmptyBorder
|
||||
implements StyleableBorder
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Color shadowColor;
|
||||
@Styleable protected Insets shadowInsets;
|
||||
@@ -93,7 +92,7 @@ public class FlatDropShadowBorder
|
||||
/** @since 2 */
|
||||
@Override
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
Object oldValue = FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
Object oldValue = StyleableObject.super.applyStyleProperty( key, value );
|
||||
if( key.equals( "shadowInsets" ) ) {
|
||||
applyStyleProperty( nonNegativeInsets( shadowInsets ) );
|
||||
shadowSize = maxInset( shadowInsets );
|
||||
@@ -101,18 +100,6 @@ public class FlatDropShadowBorder
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
@Override
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
@Override
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
|
||||
if( shadowSize <= 0 )
|
||||
|
||||
@@ -35,7 +35,7 @@ import javax.swing.event.MouseInputAdapter;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.basic.BasicInternalFrameUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
|
||||
@@ -209,7 +209,7 @@ public class FlatInternalFrameUI
|
||||
|
||||
public static class FlatInternalFrameBorder
|
||||
extends FlatEmptyBorder
|
||||
implements StyleableBorder
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
|
||||
@Styleable protected Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );
|
||||
|
||||
@@ -21,11 +21,10 @@ import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.util.Map;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
|
||||
/**
|
||||
* Border for {@link javax.swing.JMenuBar}.
|
||||
@@ -36,27 +35,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
|
||||
*/
|
||||
public class FlatMenuBarBorder
|
||||
extends FlatMarginBorder
|
||||
implements StyleableBorder
|
||||
implements StyleableObject
|
||||
{
|
||||
@Styleable protected Color borderColor = UIManager.getColor( "MenuBar.borderColor" );
|
||||
|
||||
/** @since 2 */
|
||||
@Override
|
||||
public Object applyStyleProperty( String key, Object value ) {
|
||||
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Class<?>> getStyleableInfos() {
|
||||
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
@Override
|
||||
public Object getStyleableValue( String key ) {
|
||||
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
|
||||
if( !showBottomSeparator( c ) )
|
||||
|
||||
@@ -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 = 3003;
|
||||
|
||||
/**
|
||||
* 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,110 @@ 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 dark preferred appearance of the file dialog: {@code 1} = prefer dark, {@code 0} = prefer light, {@code -1} = default
|
||||
* @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, int dark, 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 );
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Toolkit;
|
||||
@@ -41,8 +40,8 @@ import javax.swing.text.JTextComponent;
|
||||
import javax.swing.text.PasswordView;
|
||||
import javax.swing.text.View;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.icons.FlatCapsLockIcon;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
|
||||
@@ -215,12 +214,12 @@ public class FlatPasswordFieldUI
|
||||
/** @since 2 */
|
||||
@Override
|
||||
protected Object applyStyleProperty( String key, Object value ) {
|
||||
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) {
|
||||
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject ) {
|
||||
if( capsLockIconShared ) {
|
||||
capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon );
|
||||
capsLockIconShared = false;
|
||||
}
|
||||
return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value );
|
||||
return ((StyleableObject)capsLockIcon).applyStyleProperty( key, value );
|
||||
}
|
||||
|
||||
return super.applyStyleProperty( key, value );
|
||||
@@ -230,14 +229,15 @@ public class FlatPasswordFieldUI
|
||||
@Override
|
||||
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
|
||||
Map<String, Class<?>> infos = super.getStyleableInfos( c );
|
||||
infos.put( "capsLockIconColor", Color.class );
|
||||
if( capsLockIcon instanceof StyleableObject )
|
||||
infos.putAll( ((StyleableObject)capsLockIcon).getStyleableInfos() );
|
||||
return infos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getStyleableValue( JComponent c, String key ) {
|
||||
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon )
|
||||
return ((FlatCapsLockIcon)capsLockIcon).getStyleableValue( key );
|
||||
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject )
|
||||
return ((StyleableObject)capsLockIcon).getStyleableValue( key );
|
||||
|
||||
return super.getStyleableValue( c, key );
|
||||
}
|
||||
|
||||
@@ -108,10 +108,8 @@ public class FlatPopupFactory
|
||||
}
|
||||
}
|
||||
|
||||
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
|
||||
|
||||
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
|
||||
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
|
||||
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, isForceHeavyWeight( owner, contents, x, y ) ), owner, contents );
|
||||
|
||||
// macOS and Linux adds drop shadow to heavy weight popups
|
||||
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
|
||||
@@ -131,12 +129,8 @@ public class FlatPopupFactory
|
||||
return popup;
|
||||
}
|
||||
|
||||
// check whether popup overlaps a heavy weight component
|
||||
if( !forceHeavyWeight && overlapsHeavyWeightComponent( owner, contents, x, y ) )
|
||||
forceHeavyWeight = true;
|
||||
|
||||
// create drop shadow popup
|
||||
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight );
|
||||
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, isForceHeavyWeight( owner, contents, x, y ) );
|
||||
GraphicsConfiguration gc = (owner != null) ? owner.getGraphicsConfiguration() : null;
|
||||
return (gc != null && gc.isTranslucencyCapable())
|
||||
? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
|
||||
@@ -226,6 +220,11 @@ public class FlatPopupFactory
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isForceHeavyWeight( Component owner, Component contents, int x, int y ) {
|
||||
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
|
||||
return forceHeavyWeight || hasVisibleGlassPane( owner ) || overlapsHeavyWeightComponent( owner, contents, x, y );
|
||||
}
|
||||
|
||||
private static boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
|
||||
Object value = getOption( owner, contents, clientKey, uiKey );
|
||||
return (value instanceof Boolean) ? (Boolean) value : false;
|
||||
@@ -469,6 +468,18 @@ public class FlatPopupFactory
|
||||
|
||||
//---- fixes --------------------------------------------------------------
|
||||
|
||||
private static boolean hasVisibleGlassPane( Component owner ) {
|
||||
if( owner == null )
|
||||
return false;
|
||||
|
||||
Window window = SwingUtilities.windowForComponent( owner );
|
||||
if( !(window instanceof RootPaneContainer) )
|
||||
return false;
|
||||
|
||||
Component glassPane = ((RootPaneContainer)window).getGlassPane();
|
||||
return (glassPane != null && glassPane.isVisible());
|
||||
}
|
||||
|
||||
private static boolean overlapsHeavyWeightComponent( Component owner, Component contents, int x, int y ) {
|
||||
if( owner == null )
|
||||
return false;
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.awt.Insets;
|
||||
import java.util.Map;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.UIManager;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.formdev.flatlaf.util.UIScale;
|
||||
*/
|
||||
public class FlatPopupMenuBorder
|
||||
extends FlatLineBorder
|
||||
implements StyleableBorder
|
||||
implements StyleableObject
|
||||
{
|
||||
private Color borderColor;
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import javax.swing.plaf.basic.BasicRadioButtonUI;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
@@ -203,16 +204,17 @@ public class FlatRadioButtonUI
|
||||
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
|
||||
// style icon
|
||||
if( key.startsWith( "icon." ) ) {
|
||||
if( !(icon instanceof FlatCheckBoxIcon) )
|
||||
return new UnknownStyleException( key );
|
||||
Icon icon = getRealIcon( b );
|
||||
if( !(icon instanceof StyleableObject) )
|
||||
throw new UnknownStyleException( key );
|
||||
|
||||
if( iconShared ) {
|
||||
icon = FlatStylingSupport.cloneIcon( icon );
|
||||
if( icon == this.icon && iconShared ) {
|
||||
this.icon = icon = FlatStylingSupport.cloneIcon( icon );
|
||||
iconShared = false;
|
||||
}
|
||||
|
||||
key = key.substring( "icon.".length() );
|
||||
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
|
||||
return ((StyleableObject)icon).applyStyleProperty( key, value );
|
||||
}
|
||||
|
||||
if( "iconTextGap".equals( key ) && value instanceof Integer )
|
||||
@@ -225,10 +227,9 @@ public class FlatRadioButtonUI
|
||||
@Override
|
||||
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
|
||||
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
|
||||
if( icon instanceof FlatCheckBoxIcon ) {
|
||||
for( Map.Entry<String, Class<?>> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() )
|
||||
infos.put( "icon.".concat( e.getKey() ), e.getValue() );
|
||||
}
|
||||
Icon icon = getRealIcon( c );
|
||||
if( icon instanceof StyleableObject )
|
||||
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((StyleableObject)icon).getStyleableInfos() );
|
||||
return infos;
|
||||
}
|
||||
|
||||
@@ -237,8 +238,9 @@ public class FlatRadioButtonUI
|
||||
public Object getStyleableValue( JComponent c, String key ) {
|
||||
// style icon
|
||||
if( key.startsWith( "icon." ) ) {
|
||||
return (icon instanceof FlatCheckBoxIcon)
|
||||
? ((FlatCheckBoxIcon)icon).getStyleableValue( key.substring( "icon.".length() ) )
|
||||
Icon icon = getRealIcon( c );
|
||||
return (icon instanceof StyleableObject)
|
||||
? ((StyleableObject)icon).getStyleableValue( key.substring( "icon.".length() ) )
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -332,16 +334,18 @@ public class FlatRadioButtonUI
|
||||
}
|
||||
|
||||
private int getIconFocusWidth( JComponent c ) {
|
||||
AbstractButton b = (AbstractButton) c;
|
||||
Icon icon = b.getIcon();
|
||||
if( icon == null )
|
||||
icon = getDefaultIcon();
|
||||
|
||||
Icon icon = getRealIcon( c );
|
||||
return (icon instanceof FlatCheckBoxIcon)
|
||||
? Math.round( UIScale.scale( ((FlatCheckBoxIcon)icon).getFocusWidth() ) )
|
||||
: 0;
|
||||
}
|
||||
|
||||
private Icon getRealIcon( JComponent c ) {
|
||||
AbstractButton b = (AbstractButton) c;
|
||||
Icon icon = b.getIcon();
|
||||
return (icon != null) ? icon : getDefaultIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseline( JComponent c, int width, int height ) {
|
||||
return FlatButtonUI.getBaselineImpl( c, width, height );
|
||||
|
||||
@@ -227,7 +227,13 @@ public class FlatSliderUI
|
||||
|
||||
/** @since 2 */
|
||||
protected void applyStyle( Object style ) {
|
||||
Dimension oldThumbSize = thumbSize;
|
||||
int oldFocusWidth = focusWidth;
|
||||
|
||||
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
|
||||
|
||||
if( !thumbSize.equals( oldThumbSize ) || focusWidth != oldFocusWidth )
|
||||
calculateGeometry();
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
|
||||
@@ -51,6 +51,9 @@ import com.formdev.flatlaf.util.StringUtils;
|
||||
*/
|
||||
public class FlatStylingSupport
|
||||
{
|
||||
|
||||
//---- annotations --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Indicates that a field is intended to be used by FlatLaf styling support.
|
||||
* <p>
|
||||
@@ -98,17 +101,56 @@ public class FlatStylingSupport
|
||||
}
|
||||
|
||||
|
||||
//---- interfaces ---------------------------------------------------------
|
||||
|
||||
/** @since 2 */
|
||||
public interface StyleableUI {
|
||||
Map<String, Class<?>> getStyleableInfos( JComponent c ) throws IllegalArgumentException;
|
||||
/** @since 2.5 */ Object getStyleableValue( JComponent c, String key ) throws IllegalArgumentException;
|
||||
}
|
||||
|
||||
/** @since 2 */
|
||||
public interface StyleableBorder {
|
||||
Object applyStyleProperty( String key, Object value );
|
||||
Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException;
|
||||
/** @since 2.5 */ Object getStyleableValue( String key ) throws IllegalArgumentException;
|
||||
/**
|
||||
* An object that implements this interface is intended to support FlatLaf styling.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public interface StyleableObject {
|
||||
/**
|
||||
* Applies the given value to this object.
|
||||
* <p>
|
||||
* The default implementation invokes {@link FlatStylingSupport#applyToAnnotatedObject(Object, String, Object)}.
|
||||
*
|
||||
* @param key the name of the property
|
||||
* @param value the new value
|
||||
* @return the old value of the property
|
||||
*/
|
||||
default Object applyStyleProperty( String key, Object value )
|
||||
throws UnknownStyleException, IllegalArgumentException
|
||||
{
|
||||
return applyToAnnotatedObject( this, key, value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all styleable properties.
|
||||
* The key is the name of the property and the value the type of the property.
|
||||
* <p>
|
||||
* The default implementation invokes {@link FlatStylingSupport#getAnnotatedStyleableInfos(Object)}.
|
||||
*/
|
||||
default Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException {
|
||||
return getAnnotatedStyleableInfos( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value for the given property key.
|
||||
* <p>
|
||||
* The default implementation invokes {@link FlatStylingSupport#getAnnotatedStyleableValue(Object, String)}.
|
||||
*
|
||||
* @param key the name of the property
|
||||
* @return the current value of the property
|
||||
*/
|
||||
default Object getStyleableValue( String key ) throws IllegalArgumentException {
|
||||
return getAnnotatedStyleableValue( this, key );
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 2.5 */
|
||||
@@ -117,6 +159,8 @@ public class FlatStylingSupport
|
||||
}
|
||||
|
||||
|
||||
//---- methods ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the style specified in client property {@link FlatClientProperties#STYLE}.
|
||||
*/
|
||||
@@ -675,7 +719,7 @@ public class FlatStylingSupport
|
||||
} catch( UnknownStyleException ex ) {
|
||||
// apply to border
|
||||
Border border = c.getBorder();
|
||||
if( border instanceof StyleableBorder ) {
|
||||
if( border instanceof StyleableObject ) {
|
||||
if( borderShared.get() ) {
|
||||
border = cloneBorder( border );
|
||||
c.setBorder( border );
|
||||
@@ -683,7 +727,7 @@ public class FlatStylingSupport
|
||||
}
|
||||
|
||||
try {
|
||||
return ((StyleableBorder)border).applyStyleProperty( key, value );
|
||||
return ((StyleableObject)border).applyStyleProperty( key, value );
|
||||
} catch( UnknownStyleException ex2 ) {
|
||||
// ignore
|
||||
}
|
||||
@@ -833,8 +877,8 @@ public class FlatStylingSupport
|
||||
}
|
||||
|
||||
public static void collectStyleableInfos( Border border, Map<String, Class<?>> infos ) {
|
||||
if( border instanceof StyleableBorder )
|
||||
infos.putAll( ((StyleableBorder)border).getStyleableInfos() );
|
||||
if( border instanceof StyleableObject )
|
||||
infos.putAll( ((StyleableObject)border).getStyleableInfos() );
|
||||
}
|
||||
|
||||
public static void putAllPrefixKey( Map<String, Class<?>> infos, String keyPrefix, Map<String, Class<?>> infos2 ) {
|
||||
@@ -882,8 +926,8 @@ public class FlatStylingSupport
|
||||
}
|
||||
|
||||
public static Object getAnnotatedStyleableValue( Object obj, Border border, String key ) {
|
||||
if( border instanceof StyleableBorder ) {
|
||||
Object value = ((StyleableBorder)border).getStyleableValue( key );
|
||||
if( border instanceof StyleableObject ) {
|
||||
Object value = ((StyleableObject)border).getStyleableValue( key );
|
||||
if( value != null )
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ import javax.swing.text.JTextComponent;
|
||||
import javax.swing.text.View;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
|
||||
import com.formdev.flatlaf.util.Animator;
|
||||
@@ -205,6 +205,7 @@ public class FlatTabbedPaneUI
|
||||
protected static final int WIDTH_MODE_PREFERRED = 0;
|
||||
protected static final int WIDTH_MODE_EQUAL = 1;
|
||||
protected static final int WIDTH_MODE_COMPACT = 2;
|
||||
/** @since 3.7 */ protected static final int WIDTH_MODE_ICON_ONLY = 3;
|
||||
|
||||
// tab rotation
|
||||
/** @since 3.3 */ protected static final int NONE = -1;
|
||||
@@ -670,15 +671,15 @@ public class FlatTabbedPaneUI
|
||||
protected Object applyStyleProperty( String key, Object value ) {
|
||||
// close icon
|
||||
if( key.startsWith( "close" ) ) {
|
||||
if( !(closeIcon instanceof FlatTabbedPaneCloseIcon) )
|
||||
return new UnknownStyleException( key );
|
||||
if( !(closeIcon instanceof StyleableObject) )
|
||||
throw new UnknownStyleException( key );
|
||||
|
||||
if( closeIconShared ) {
|
||||
closeIcon = FlatStylingSupport.cloneIcon( closeIcon );
|
||||
closeIconShared = false;
|
||||
}
|
||||
|
||||
return ((FlatTabbedPaneCloseIcon)closeIcon).applyStyleProperty( key, value );
|
||||
return ((StyleableObject)closeIcon).applyStyleProperty( key, value );
|
||||
}
|
||||
|
||||
if( value instanceof String ) {
|
||||
@@ -720,8 +721,8 @@ public class FlatTabbedPaneUI
|
||||
infos.put( "tabAreaInsets", Insets.class );
|
||||
infos.put( "textIconGap", int.class );
|
||||
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
|
||||
if( closeIcon instanceof FlatTabbedPaneCloseIcon )
|
||||
infos.putAll( ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableInfos() );
|
||||
if( closeIcon instanceof StyleableObject )
|
||||
infos.putAll( ((StyleableObject)closeIcon).getStyleableInfos() );
|
||||
return infos;
|
||||
}
|
||||
|
||||
@@ -730,8 +731,8 @@ public class FlatTabbedPaneUI
|
||||
public Object getStyleableValue( JComponent c, String key ) {
|
||||
// close icon
|
||||
if( key.startsWith( "close" ) ) {
|
||||
return (closeIcon instanceof FlatTabbedPaneCloseIcon)
|
||||
? ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableValue( key )
|
||||
return (closeIcon instanceof StyleableObject)
|
||||
? ((StyleableObject)closeIcon).getStyleableValue( key )
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -780,6 +781,7 @@ public class FlatTabbedPaneUI
|
||||
case WIDTH_MODE_PREFERRED: return TABBED_PANE_TAB_WIDTH_MODE_PREFERRED;
|
||||
case WIDTH_MODE_EQUAL: return TABBED_PANE_TAB_WIDTH_MODE_EQUAL;
|
||||
case WIDTH_MODE_COMPACT: return TABBED_PANE_TAB_WIDTH_MODE_COMPACT;
|
||||
case WIDTH_MODE_ICON_ONLY: return TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY;
|
||||
}
|
||||
|
||||
case "tabRotation":
|
||||
@@ -926,9 +928,10 @@ public class FlatTabbedPaneUI
|
||||
|
||||
int tabWidth;
|
||||
Icon icon;
|
||||
if( tabWidthMode == WIDTH_MODE_COMPACT &&
|
||||
tabIndex != tabPane.getSelectedIndex() &&
|
||||
isHorizontalOrRotated( tabPlacement ) &&
|
||||
if( ((tabWidthMode == WIDTH_MODE_COMPACT &&
|
||||
tabIndex != tabPane.getSelectedIndex() &&
|
||||
isHorizontalOrRotated( tabPlacement )) ||
|
||||
tabWidthMode == WIDTH_MODE_ICON_ONLY) &&
|
||||
tabPane.getTabComponentAt( tabIndex ) == null &&
|
||||
(icon = getIconForTab( tabIndex )) != null )
|
||||
{
|
||||
@@ -1247,7 +1250,8 @@ public class FlatTabbedPaneUI
|
||||
Font font = tabPane.getFont();
|
||||
FontMetrics metrics = tabPane.getFontMetrics( font );
|
||||
boolean isCompact = (icon != null && !isSelected && getTabWidthMode() == WIDTH_MODE_COMPACT && isHorizontalOrRotated( tabPlacement ));
|
||||
if( isCompact )
|
||||
boolean isIconOnly = (icon != null && getTabWidthMode() == WIDTH_MODE_ICON_ONLY);
|
||||
if( isCompact || isIconOnly )
|
||||
title = null;
|
||||
String clippedTitle = layoutAndClipLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected );
|
||||
|
||||
@@ -1283,7 +1287,7 @@ debug*/
|
||||
}
|
||||
|
||||
// paint title and icon
|
||||
if( !isCompact )
|
||||
if( !isCompact || isIconOnly )
|
||||
paintText( g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected );
|
||||
paintIcon( g, tabPlacement, tabIndex, icon, iconRect, isSelected );
|
||||
}
|
||||
@@ -2160,6 +2164,7 @@ debug*/
|
||||
case TABBED_PANE_TAB_WIDTH_MODE_PREFERRED: return WIDTH_MODE_PREFERRED;
|
||||
case TABBED_PANE_TAB_WIDTH_MODE_EQUAL: return WIDTH_MODE_EQUAL;
|
||||
case TABBED_PANE_TAB_WIDTH_MODE_COMPACT: return WIDTH_MODE_COMPACT;
|
||||
case TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY: return WIDTH_MODE_ICON_ONLY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2508,7 +2513,7 @@ debug*/
|
||||
// 2. text of label or text component in custom tab component (including children)
|
||||
// 3. accessible name of tab
|
||||
// 4. accessible name of custom tab component (including children)
|
||||
// 5. string "n. Tab"
|
||||
// 5. string "n. Tab", if tab does not have an icon
|
||||
String title = tabPane.getTitleAt( tabIndex );
|
||||
if( StringUtils.isEmpty( title ) ) {
|
||||
Component tabComp = tabPane.getTabComponentAt( tabIndex );
|
||||
@@ -2518,7 +2523,7 @@ debug*/
|
||||
title = tabPane.getAccessibleContext().getAccessibleChild( tabIndex ).getAccessibleContext().getAccessibleName();
|
||||
if( StringUtils.isEmpty( title ) && tabComp instanceof Accessible )
|
||||
title = findTabTitleInAccessible( (Accessible) tabComp );
|
||||
if( StringUtils.isEmpty( title ) )
|
||||
if( StringUtils.isEmpty( title ) && tabPane.getIconAt( tabIndex ) == null )
|
||||
title = (tabIndex + 1) + ". Tab";
|
||||
}
|
||||
|
||||
@@ -2539,6 +2544,12 @@ debug*/
|
||||
if( !tabPane.isEnabled() || !tabPane.isEnabledAt( tabIndex ) )
|
||||
menuItem.setEnabled( false );
|
||||
|
||||
// make menu item smaller if it contains icon only
|
||||
if( StringUtils.isEmpty( title ) ) {
|
||||
menuItem.putClientProperty( FlatClientProperties.STYLE,
|
||||
"minimumWidth: 0; textNoAcceleratorGap: 0; acceleratorArrowGap: 0" );
|
||||
}
|
||||
|
||||
menuItem.addActionListener( e -> selectTab( tabIndex ) );
|
||||
return menuItem;
|
||||
}
|
||||
@@ -2707,8 +2718,13 @@ debug*/
|
||||
|
||||
// because this listener receives mouse events for the whole tabbed pane,
|
||||
// we have to check whether the mouse is located over the viewport
|
||||
if( !isInViewport( e.getX(), e.getY() ) )
|
||||
if( !isInViewport( e.getX(), e.getY() ) ) {
|
||||
// if it is not in the viewport, retarget the event to a parent container
|
||||
// which might support scrolling (e.g. a surrounding ScrollPane)
|
||||
Container parent = tabPane.getParent();
|
||||
parent.dispatchEvent( SwingUtilities.convertMouseEvent( tabPane, e, parent ) );
|
||||
return;
|
||||
}
|
||||
|
||||
lastMouseX = e.getX();
|
||||
lastMouseY = e.getY();
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.awt.event.ComponentListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import javax.swing.Action;
|
||||
@@ -66,6 +65,7 @@ import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils.FlatPropertyWatcher;
|
||||
import com.formdev.flatlaf.util.Graphics2DProxy;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
@@ -189,29 +189,26 @@ public class FlatTableUI
|
||||
if( rowHeight > 0 )
|
||||
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
|
||||
|
||||
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
|
||||
if( watcher != null )
|
||||
watcher.enabled = false;
|
||||
|
||||
if( !showHorizontalLines && (watcher == null || !watcher.showHorizontalLinesChanged) ) {
|
||||
oldShowHorizontalLines = table.getShowHorizontalLines();
|
||||
table.setShowHorizontalLines( false );
|
||||
if( !showHorizontalLines ) {
|
||||
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
|
||||
oldShowHorizontalLines = table.getShowHorizontalLines();
|
||||
table.setShowHorizontalLines( false );
|
||||
} );
|
||||
}
|
||||
if( !showVerticalLines && (watcher == null || !watcher.showVerticalLinesChanged) ) {
|
||||
oldShowVerticalLines = table.getShowVerticalLines();
|
||||
table.setShowVerticalLines( false );
|
||||
if( !showVerticalLines ) {
|
||||
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
|
||||
oldShowVerticalLines = table.getShowVerticalLines();
|
||||
table.setShowVerticalLines( false );
|
||||
} );
|
||||
}
|
||||
|
||||
if( intercellSpacing != null && (watcher == null || !watcher.intercellSpacingChanged) ) {
|
||||
oldIntercellSpacing = table.getIntercellSpacing();
|
||||
table.setIntercellSpacing( intercellSpacing );
|
||||
if( intercellSpacing != null ) {
|
||||
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
|
||||
oldIntercellSpacing = table.getIntercellSpacing();
|
||||
table.setIntercellSpacing( intercellSpacing );
|
||||
} );
|
||||
}
|
||||
|
||||
if( watcher != null )
|
||||
watcher.enabled = true;
|
||||
else
|
||||
table.addPropertyChangeListener( new FlatTablePropertyWatcher() );
|
||||
|
||||
// install boolean renderer
|
||||
oldBooleanRenderer = table.getDefaultRenderer( Boolean.class );
|
||||
if( oldBooleanRenderer instanceof UIResource )
|
||||
@@ -231,25 +228,24 @@ public class FlatTableUI
|
||||
|
||||
oldStyleValues = null;
|
||||
|
||||
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
|
||||
if( watcher != null )
|
||||
watcher.enabled = false;
|
||||
|
||||
// restore old show horizontal/vertical lines (if not modified)
|
||||
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() &&
|
||||
(watcher == null || !watcher.showHorizontalLinesChanged) )
|
||||
table.setShowHorizontalLines( true );
|
||||
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() &&
|
||||
(watcher == null || !watcher.showVerticalLinesChanged) )
|
||||
table.setShowVerticalLines( true );
|
||||
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() ) {
|
||||
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
|
||||
table.setShowHorizontalLines( true );
|
||||
} );
|
||||
}
|
||||
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() ) {
|
||||
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
|
||||
table.setShowVerticalLines( true );
|
||||
} );
|
||||
}
|
||||
|
||||
// restore old intercell spacing (if not modified)
|
||||
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) &&
|
||||
(watcher == null || !watcher.intercellSpacingChanged) )
|
||||
table.setIntercellSpacing( oldIntercellSpacing );
|
||||
|
||||
if( watcher != null )
|
||||
watcher.enabled = true;
|
||||
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) ) {
|
||||
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
|
||||
table.setIntercellSpacing( oldIntercellSpacing );
|
||||
} );
|
||||
}
|
||||
|
||||
// uninstall boolean renderer
|
||||
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
|
||||
@@ -938,48 +934,6 @@ public class FlatTableUI
|
||||
}
|
||||
}
|
||||
|
||||
//---- class FlatTablePropertyWatcher -------------------------------------
|
||||
|
||||
/**
|
||||
* Listener that watches for change of some table properties from application code.
|
||||
* This information is used in {@link FlatTableUI#installDefaults()} and
|
||||
* {@link FlatTableUI#uninstallDefaults()} to decide whether FlatLaf modifies those properties.
|
||||
* If they are modified in application code, FlatLaf no longer changes them.
|
||||
*
|
||||
* The listener is added once for each table, but never removed.
|
||||
* So switching Laf/theme reuses existing listener.
|
||||
*/
|
||||
private static class FlatTablePropertyWatcher
|
||||
implements PropertyChangeListener
|
||||
{
|
||||
boolean enabled = true;
|
||||
boolean showHorizontalLinesChanged;
|
||||
boolean showVerticalLinesChanged;
|
||||
boolean intercellSpacingChanged;
|
||||
|
||||
static FlatTablePropertyWatcher get( JTable table ) {
|
||||
for( PropertyChangeListener l : table.getPropertyChangeListeners() ) {
|
||||
if( l instanceof FlatTablePropertyWatcher )
|
||||
return (FlatTablePropertyWatcher) l;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//---- interface PropertyChangeListener ----
|
||||
|
||||
@Override
|
||||
public void propertyChange( PropertyChangeEvent e ) {
|
||||
if( !enabled )
|
||||
return;
|
||||
|
||||
switch( e.getPropertyName() ) {
|
||||
case "showHorizontalLines": showHorizontalLinesChanged = true; break;
|
||||
case "showVerticalLines": showVerticalLinesChanged = true; break;
|
||||
case "rowMargin": intercellSpacingChanged = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---- class FlatBooleanRenderer ------------------------------------------
|
||||
|
||||
private static class FlatBooleanRenderer
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -484,7 +484,7 @@ public class FlatTitlePane
|
||||
}
|
||||
|
||||
protected void frameStateChanged() {
|
||||
if( window == null || rootPane.getWindowDecorationStyle() != JRootPane.FRAME )
|
||||
if( window == null || rootPane.getWindowDecorationStyle() == JRootPane.NONE )
|
||||
return;
|
||||
|
||||
updateVisibility();
|
||||
|
||||
@@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicToolBarUI;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils.FlatPropertyWatcher;
|
||||
import com.formdev.flatlaf.util.HiDPIUtils;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
@@ -144,11 +145,13 @@ public class FlatToolBarUI
|
||||
hoverButtonGroupBackground = UIManager.getColor( "ToolBar.hoverButtonGroupBackground" );
|
||||
|
||||
// floatable
|
||||
oldFloatable = null;
|
||||
if( !UIManager.getBoolean( "ToolBar.floatable" ) ) {
|
||||
oldFloatable = toolBar.isFloatable();
|
||||
toolBar.setFloatable( false );
|
||||
} else
|
||||
oldFloatable = null;
|
||||
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
|
||||
oldFloatable = toolBar.isFloatable();
|
||||
toolBar.setFloatable( false );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,7 +161,9 @@ public class FlatToolBarUI
|
||||
hoverButtonGroupBackground = null;
|
||||
|
||||
if( oldFloatable != null ) {
|
||||
toolBar.setFloatable( oldFloatable );
|
||||
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
|
||||
toolBar.setFloatable( oldFloatable );
|
||||
} );
|
||||
oldFloatable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.function.Predicate;
|
||||
@@ -474,6 +476,9 @@ public class FlatUIUtils
|
||||
* Returns the scaled arc diameter of the border for the given component.
|
||||
*/
|
||||
public static float getBorderArc( JComponent c ) {
|
||||
if( c.getBorder() instanceof FlatLineBorder )
|
||||
return UIScale.scale( ((FlatLineBorder)c.getBorder()).getArc() );
|
||||
|
||||
FlatBorder border = getOutsideFlatBorder( c );
|
||||
return (border != null)
|
||||
? UIScale.scale( (float) border.getArc( c ) )
|
||||
@@ -1410,4 +1415,47 @@ debug*/
|
||||
return delegate.isBorderOpaque();
|
||||
}
|
||||
}
|
||||
|
||||
//---- class FlatPropertyWatcher ------------------------------------------
|
||||
|
||||
/**
|
||||
* Listener that watches for change of a property from application code.
|
||||
* This information can be used to decide whether FlatLaf modifies the property.
|
||||
* If it is modified in application code, FlatLaf no longer changes it.
|
||||
* <p>
|
||||
* The listener is added once for a property, but never removed.
|
||||
* So switching Laf/theme reuses existing listener.
|
||||
*/
|
||||
static class FlatPropertyWatcher
|
||||
implements PropertyChangeListener
|
||||
{
|
||||
private boolean changed;
|
||||
|
||||
static void runIfNotChanged( JComponent c, String propName, Runnable runnable ) {
|
||||
FlatPropertyWatcher watcher = getOrInstall( c, propName );
|
||||
if( watcher.changed )
|
||||
return;
|
||||
|
||||
runnable.run();
|
||||
watcher.changed = false;
|
||||
}
|
||||
|
||||
private static FlatPropertyWatcher getOrInstall( JComponent c, String propName ) {
|
||||
for( PropertyChangeListener l : c.getPropertyChangeListeners( propName ) ) {
|
||||
if( l instanceof FlatPropertyWatcher )
|
||||
return (FlatPropertyWatcher) l;
|
||||
}
|
||||
|
||||
FlatPropertyWatcher watcher = new FlatPropertyWatcher();
|
||||
c.addPropertyChangeListener( propName, watcher );
|
||||
return watcher;
|
||||
}
|
||||
|
||||
//---- interface PropertyChangeListener ----
|
||||
|
||||
@Override
|
||||
public void propertyChange( PropertyChangeEvent e ) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,9 @@ import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import javax.swing.LookAndFeel;
|
||||
import javax.swing.UIDefaults;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.DimensionUIResource;
|
||||
import javax.swing.plaf.FontUIResource;
|
||||
@@ -61,16 +63,28 @@ import com.formdev.flatlaf.FlatSystemProperties;
|
||||
* or if the default font is changed.
|
||||
* The user scale factor is computed based on the used font.
|
||||
* The JRE does not scale anything.
|
||||
* So we have to invoke {@link #scale(float)} where necessary.
|
||||
* So we have to invoke {@link #scale(int)} where necessary.
|
||||
* There is only one user scale factor for all displays.
|
||||
* The user scale factor may change if the active LaF, "defaultFont" or "Label.font" has changed.
|
||||
* If system scaling mode is available the user scale factor is usually 1,
|
||||
* but may be larger on Linux or if the default font is changed.
|
||||
*
|
||||
* <h2>Zooming</h2>
|
||||
*
|
||||
* Zooming allows appliations to easily zoom their UI, if FlatLaf is active Laf.
|
||||
* This is done by changing user scale factor and default font.
|
||||
* There are methods to increase, decrease and reset zoom factor.
|
||||
* <p>
|
||||
* Note: Only standard Swing components are zoomed.
|
||||
* Custom components need to use {@link #scale(int)} to zoom their UI.
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class UIScale
|
||||
{
|
||||
/** @since 3.7 */ public static final String PROP_USER_SCALE_FACTOR = "userScaleFactor";
|
||||
/** @since 3.7 */ public static final String PROP_ZOOM_FACTOR = "zoomFactor";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static PropertyChangeSupport changeSupport;
|
||||
@@ -87,7 +101,7 @@ public class UIScale
|
||||
changeSupport.removePropertyChangeListener( listener );
|
||||
}
|
||||
|
||||
//---- system scaling (Java 9) --------------------------------------------
|
||||
//---- system scaling (Java 9+) -------------------------------------------
|
||||
|
||||
private static Boolean jreHiDPI;
|
||||
|
||||
@@ -135,10 +149,13 @@ public class UIScale
|
||||
return (isSystemScalingEnabled() && gc != null) ? gc.getDefaultTransform().getScaleX() : 1;
|
||||
}
|
||||
|
||||
//---- user scaling (Java 8) ----------------------------------------------
|
||||
//---- user scaling (Java 8 / zooming) ------------------------------------
|
||||
|
||||
private static float unzoomedScaleFactor = 1;
|
||||
private static float scaleFactor = 1;
|
||||
private static boolean initialized;
|
||||
private static boolean listenerInitialized; // use extra flag for unit tests
|
||||
private static boolean ignoreFontChange;
|
||||
|
||||
private static void initialize() {
|
||||
if( initialized )
|
||||
@@ -148,33 +165,43 @@ public class UIScale
|
||||
if( !isUserScalingEnabled() )
|
||||
return;
|
||||
|
||||
initializeListener();
|
||||
|
||||
updateScaleFactor( true );
|
||||
}
|
||||
|
||||
private static void initializeListener() {
|
||||
if( listenerInitialized )
|
||||
return;
|
||||
listenerInitialized = true;
|
||||
|
||||
// listener to update scale factor if LaF changed, "defaultFont" or "Label.font" changed
|
||||
PropertyChangeListener listener = new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange( PropertyChangeEvent e ) {
|
||||
switch( e.getPropertyName() ) {
|
||||
case "lookAndFeel":
|
||||
// it is not necessary (and possible) to remove listener of old LaF defaults
|
||||
// it is not possible (and necessary) to remove listener of old LaF defaults
|
||||
// because it is not possible to access the UIDefault object of the old LaF
|
||||
if( e.getNewValue() instanceof LookAndFeel )
|
||||
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( this );
|
||||
updateScaleFactor();
|
||||
updateScaleFactor( true );
|
||||
break;
|
||||
|
||||
case "defaultFont":
|
||||
case "Label.font":
|
||||
updateScaleFactor();
|
||||
if( !ignoreFontChange )
|
||||
updateScaleFactor( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
UIManager.addPropertyChangeListener( listener );
|
||||
UIManager.getDefaults().addPropertyChangeListener( listener );
|
||||
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( listener );
|
||||
|
||||
updateScaleFactor();
|
||||
UIManager.addPropertyChangeListener( listener );
|
||||
}
|
||||
|
||||
private static void updateScaleFactor() {
|
||||
private static void updateScaleFactor( boolean lafChanged ) {
|
||||
if( !isUserScalingEnabled() )
|
||||
return;
|
||||
|
||||
@@ -185,17 +212,20 @@ public class UIScale
|
||||
return;
|
||||
}
|
||||
|
||||
// use font size to calculate scale factor (instead of DPI)
|
||||
// because even if we are on a HiDPI display it is not sure
|
||||
// that a larger font size is set by the current LaF
|
||||
// (e.g. can avoid large icons with small text)
|
||||
// get font that is used to calculate scale factor
|
||||
Font font = null;
|
||||
if( UIManager.getLookAndFeel() instanceof FlatLaf )
|
||||
font = UIManager.getFont( "defaultFont" );
|
||||
if( font == null )
|
||||
font = UIManager.getFont( "Label.font" );
|
||||
|
||||
setUserScaleFactor( computeFontScaleFactor( font ), true );
|
||||
float fontScaleFactor = computeFontScaleFactor( font );
|
||||
if( lafChanged && UIManager.getLookAndFeel() instanceof FlatLaf ) {
|
||||
// FlatLaf has applied zoom factor in FlatLaf.initDefaultFont() to defaultFont,
|
||||
// so we need to take it into account to get correct user scale factor
|
||||
fontScaleFactor /= zoomFactor;
|
||||
}
|
||||
setUserScaleFactor( fontScaleFactor, true );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +234,7 @@ public class UIScale
|
||||
* @since 2
|
||||
*/
|
||||
public static float computeFontScaleFactor( Font font ) {
|
||||
if( SystemInfo.isWindows ) {
|
||||
if( SystemInfo.isWindows && !inUnitTests ) {
|
||||
// Special handling for Windows to be compatible with OS scaling,
|
||||
// which distinguish between "screen scaling" and "text scaling".
|
||||
// - Windows "screen scaling" scales everything (text, icon, gaps, etc.)
|
||||
@@ -335,7 +365,7 @@ public class UIScale
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user scale factor.
|
||||
* Returns the user scale factor (including zoom factor).
|
||||
*/
|
||||
public static float getUserScaleFactor() {
|
||||
initialize();
|
||||
@@ -345,27 +375,49 @@ public class UIScale
|
||||
/**
|
||||
* Sets the user scale factor.
|
||||
*/
|
||||
private static void setUserScaleFactor( float scaleFactor, boolean normalize ) {
|
||||
if( normalize ) {
|
||||
if( scaleFactor < 1f ) {
|
||||
scaleFactor = FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false )
|
||||
? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10
|
||||
: 1f;
|
||||
} else if( scaleFactor > 1f ) // round scale factor to 1/4
|
||||
scaleFactor = Math.round( scaleFactor * 4f ) / 4f;
|
||||
}
|
||||
private static void setUserScaleFactor( float unzoomedScaleFactor, boolean normalize ) {
|
||||
if( normalize )
|
||||
unzoomedScaleFactor = normalizeScaleFactor( unzoomedScaleFactor );
|
||||
|
||||
// minimum scale factor
|
||||
scaleFactor = Math.max( scaleFactor, 0.1f );
|
||||
unzoomedScaleFactor = Math.max( unzoomedScaleFactor, 0.1f );
|
||||
|
||||
if( unzoomedScaleFactor == UIScale.unzoomedScaleFactor )
|
||||
return;
|
||||
|
||||
if( DEBUG )
|
||||
System.out.println( "Unzoomed scale factor " + UIScale.unzoomedScaleFactor + " --> " + unzoomedScaleFactor );
|
||||
|
||||
UIScale.unzoomedScaleFactor = unzoomedScaleFactor;
|
||||
setScaleFactor( unzoomedScaleFactor * zoomFactor );
|
||||
}
|
||||
|
||||
private static void setScaleFactor( float scaleFactor ) {
|
||||
// round scale factor to 1/100
|
||||
scaleFactor = Math.round( scaleFactor * 100f ) / 100f;
|
||||
|
||||
if( scaleFactor == UIScale.scaleFactor )
|
||||
return;
|
||||
|
||||
float oldScaleFactor = UIScale.scaleFactor;
|
||||
UIScale.scaleFactor = scaleFactor;
|
||||
|
||||
if( DEBUG )
|
||||
System.out.println( "HiDPI scale factor " + scaleFactor );
|
||||
System.out.println( "Scale factor " + oldScaleFactor + " --> " + scaleFactor + " (unzoomed " + UIScale.unzoomedScaleFactor + ")" );
|
||||
|
||||
if( changeSupport != null )
|
||||
changeSupport.firePropertyChange( "userScaleFactor", oldScaleFactor, scaleFactor );
|
||||
changeSupport.firePropertyChange( PROP_USER_SCALE_FACTOR, oldScaleFactor, scaleFactor );
|
||||
}
|
||||
|
||||
private static float normalizeScaleFactor( float scaleFactor ) {
|
||||
if( scaleFactor < 1f ) {
|
||||
return FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false )
|
||||
? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10
|
||||
: 1f;
|
||||
} else if( scaleFactor > 1f ) // round scale factor to 1/4
|
||||
return Math.round( scaleFactor * 4f ) / 4f;
|
||||
else
|
||||
return scaleFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,4 +503,185 @@ public class UIScale
|
||||
? new InsetsUIResource( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) )
|
||||
: new Insets ( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) ));
|
||||
}
|
||||
|
||||
//---- zoom ---------------------------------------------------------------
|
||||
|
||||
private static float zoomFactor = 1;
|
||||
private static float[] supportedZoomFactors = { 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f };
|
||||
|
||||
/**
|
||||
* Returns the current zoom factor. Default is {@code 1}.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public static float getZoomFactor() {
|
||||
return zoomFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the zoom factor.
|
||||
* Also updates user scale factor and default font (if FlatLaf is active Laf).
|
||||
* <p>
|
||||
* UI needs to be updated if zoom factor has changed. E.g.:
|
||||
* <pre>{@code
|
||||
* if( UIScale.setZoomFactor( newZoomFactor ) )
|
||||
* FlatLaf.updateUI();
|
||||
* }</pre>
|
||||
*
|
||||
* @param zoomFactor new zoom factor
|
||||
* @return {@code true} if zoom factor has changed
|
||||
* @since 3.7
|
||||
*/
|
||||
public static boolean setZoomFactor( float zoomFactor ) {
|
||||
// minimum zoom factor
|
||||
zoomFactor = Math.max( zoomFactor, 0.1f );
|
||||
|
||||
if( UIScale.zoomFactor == zoomFactor )
|
||||
return false;
|
||||
|
||||
float oldZoomFactor = UIScale.zoomFactor;
|
||||
UIScale.zoomFactor = zoomFactor;
|
||||
|
||||
if( DEBUG )
|
||||
System.out.println( "Zoom factor " + oldZoomFactor + " --> " + zoomFactor );
|
||||
|
||||
setScaleFactor( UIScale.unzoomedScaleFactor * zoomFactor );
|
||||
|
||||
if( initialized && UIManager.getLookAndFeel() instanceof FlatLaf ) {
|
||||
// see also FlatLaf.initDefaultFont()
|
||||
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
|
||||
Font font = defaults.getFont( "defaultFont" );
|
||||
int unzoomedSize = defaults.getInt( "defaultFont.unzoomedSize" );
|
||||
if( unzoomedSize == 0 ) {
|
||||
unzoomedSize = font.getSize();
|
||||
defaults.put( "defaultFont.unzoomedSize", unzoomedSize );
|
||||
}
|
||||
|
||||
// update "defaultFont"
|
||||
ignoreFontChange = true;
|
||||
try {
|
||||
// get application default font before updating Laf default font
|
||||
Font appFont = UIManager.getFont( "defaultFont" );
|
||||
|
||||
// update Laf default font
|
||||
int newFontSize = Math.max( Math.round( unzoomedSize * zoomFactor ), 1 );
|
||||
defaults.put( "defaultFont", new FontUIResource( font.deriveFont( (float) newFontSize ) ) );
|
||||
|
||||
if( DEBUG )
|
||||
System.out.println( "Zoom Laf font " + font.getSize() + " --> " + newFontSize + " (unzoomed " + unzoomedSize + ")" );
|
||||
|
||||
// check whether application has changed default font
|
||||
if( appFont != font ) {
|
||||
// application has own default font --> also zoom it
|
||||
int newAppFontSize = Math.max( Math.round( (appFont.getSize() / oldZoomFactor) * zoomFactor ), 1 );
|
||||
UIManager.put( "defaultFont", appFont.deriveFont( (float) newAppFontSize ) );
|
||||
|
||||
if( DEBUG )
|
||||
System.out.println( "Zoom app font " + appFont.getSize() + " --> " + newAppFontSize );
|
||||
}
|
||||
} finally {
|
||||
ignoreFontChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
if( changeSupport != null )
|
||||
changeSupport.firePropertyChange( PROP_ZOOM_FACTOR, oldZoomFactor, zoomFactor );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases zoom factor using next greater factor in supported factors array.
|
||||
* <p>
|
||||
* UI needs to be updated if zoom factor has changed. E.g.:
|
||||
* <pre>{@code
|
||||
* if( UIScale.zoomIn() )
|
||||
* FlatLaf.updateUI();
|
||||
* }</pre>
|
||||
*
|
||||
* @return {@code true} if zoom factor has changed
|
||||
* @see #getSupportedZoomFactors()
|
||||
* @since 3.7
|
||||
*/
|
||||
public static boolean zoomIn() {
|
||||
int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor );
|
||||
int next = (i >= 0) ? i + 1 : -i - 1;
|
||||
if( next >= supportedZoomFactors.length )
|
||||
return false;
|
||||
|
||||
return setZoomFactor( supportedZoomFactors[next] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases zoom factor using next smaller factor in supported factors array.
|
||||
* <p>
|
||||
* UI needs to be updated if zoom factor has changed. E.g.:
|
||||
* <pre>{@code
|
||||
* if( UIScale.zoomOut() )
|
||||
* FlatLaf.updateUI();
|
||||
* }</pre>
|
||||
*
|
||||
* @return {@code true} if zoom factor has changed
|
||||
* @see #getSupportedZoomFactors()
|
||||
* @since 3.7
|
||||
*/
|
||||
public static boolean zoomOut() {
|
||||
int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor );
|
||||
int prev = (i >= 0) ? i - 1 : -i - 2;
|
||||
if( prev < 0 )
|
||||
return false;
|
||||
|
||||
return setZoomFactor( supportedZoomFactors[prev] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets zoom factor to {@code 1}.
|
||||
* <p>
|
||||
* UI needs to be updated if zoom factor has changed. E.g.:
|
||||
* <pre>{@code
|
||||
* if( UIScale.zoomReset() )
|
||||
* FlatLaf.updateUI();
|
||||
* }</pre>
|
||||
*
|
||||
* @return {@code true} if zoom factor has changed
|
||||
* @since 3.7
|
||||
*/
|
||||
public static boolean zoomReset() {
|
||||
return setZoomFactor( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}.
|
||||
* <p>
|
||||
* Default is {@code [ 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f ]}.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public static float[] getSupportedZoomFactors() {
|
||||
return supportedZoomFactors.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}.
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public static void setSupportedZoomFactors( float[] supportedZoomFactors ) {
|
||||
UIScale.supportedZoomFactors = supportedZoomFactors.clone();
|
||||
Arrays.sort( UIScale.supportedZoomFactors );
|
||||
|
||||
if( Arrays.binarySearch( UIScale.supportedZoomFactors, 1f ) < 0 )
|
||||
throw new IllegalArgumentException( "supportedZoomFactors array must contain value 1f" );
|
||||
}
|
||||
|
||||
//---- unit testing -------------------------------------------------------
|
||||
|
||||
static boolean inUnitTests;
|
||||
|
||||
static void tests_uninitialize() {
|
||||
initialized = false;
|
||||
unzoomedScaleFactor = 1;
|
||||
scaleFactor = 1;
|
||||
zoomFactor = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +696,7 @@ TabbedPane.hiddenTabsNavigation = moreTabsButton
|
||||
TabbedPane.tabAreaAlignment = leading
|
||||
# allowed values: leading, trailing or center
|
||||
TabbedPane.tabAlignment = center
|
||||
# allowed values: preferred, equal or compact
|
||||
# allowed values: preferred, equal, compact or iconsOnly
|
||||
TabbedPane.tabWidthMode = preferred
|
||||
# allowed values: none, auto, left or right
|
||||
TabbedPane.tabRotation = none
|
||||
@@ -977,6 +977,24 @@ Tree.icon.openColor = @icon
|
||||
|
||||
#---- Styles ------------------------------------------------------------------
|
||||
|
||||
#---- scale icons ----
|
||||
|
||||
@largeScale = 1.125
|
||||
@mediumScale = 0.875
|
||||
@smallScale = 0.8125
|
||||
@miniScale = 0.75
|
||||
|
||||
[style]CheckBox.large = icon.scale: @largeScale; iconTextGap: 5
|
||||
[style]CheckBox.medium = icon.scale: @mediumScale
|
||||
[style]CheckBox.small = icon.scale: @smallScale; iconTextGap: 3
|
||||
[style]CheckBox.mini = icon.scale: @miniScale; iconTextGap: 3
|
||||
|
||||
[style]RadioButton.large = icon.scale: @largeScale; iconTextGap: 5
|
||||
[style]RadioButton.medium = icon.scale: @mediumScale
|
||||
[style]RadioButton.small = icon.scale: @smallScale; iconTextGap: 3
|
||||
[style]RadioButton.mini = icon.scale: @miniScale; iconTextGap: 3
|
||||
|
||||
|
||||
#---- inTextField ----
|
||||
# for leading/trailing components in text fields
|
||||
|
||||
|
||||
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.
@@ -30,6 +30,10 @@ import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.formdev.flatlaf.icons.*;
|
||||
import com.formdev.flatlaf.ui.FlatInternalFrameUI.FlatInternalFrameBorder;
|
||||
import com.formdev.flatlaf.ui.TestFlatStyling.CustomCheckBoxIcon;
|
||||
import com.formdev.flatlaf.ui.TestFlatStyling.CustomIcon;
|
||||
import com.formdev.flatlaf.ui.TestFlatStyling.CustomRadioButtonIcon;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
@@ -70,6 +74,8 @@ public class TestFlatStyleableInfo
|
||||
//---- FlatHelpButtonIcon ----
|
||||
|
||||
expectedMap( expected,
|
||||
"help.scale", float.class,
|
||||
|
||||
"help.focusWidth", int.class,
|
||||
"help.focusColor", Color.class,
|
||||
"help.innerFocusWidth", float.class,
|
||||
@@ -144,7 +150,20 @@ public class TestFlatStyleableInfo
|
||||
|
||||
@Test
|
||||
void checkBox() {
|
||||
JCheckBox c = new JCheckBox();
|
||||
checkBox( new JCheckBox() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkBox2() {
|
||||
checkBox( new JCheckBox( new CustomIcon() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkBox3() {
|
||||
checkBox( new JCheckBox( new CustomCheckBoxIcon() ) );
|
||||
}
|
||||
|
||||
private void checkBox( JCheckBox c ) {
|
||||
FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI();
|
||||
|
||||
assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon );
|
||||
@@ -153,6 +172,11 @@ public class TestFlatStyleableInfo
|
||||
Map<String, Class<?>> expected = new LinkedHashMap<>();
|
||||
radioButton( expected );
|
||||
|
||||
// remove "icon." keys if check box has custom icon
|
||||
Icon icon = c.getIcon();
|
||||
if( icon != null && !(icon instanceof FlatCheckBoxIcon) )
|
||||
expected.keySet().removeIf( key -> key.startsWith( "icon." ) );
|
||||
|
||||
assertMapEquals( expected, ui.getStyleableInfos( c ) );
|
||||
}
|
||||
|
||||
@@ -175,8 +199,9 @@ public class TestFlatStyleableInfo
|
||||
"disabledForeground", Color.class,
|
||||
|
||||
"buttonBackground", Color.class,
|
||||
"buttonFocusedBackground", Color.class,
|
||||
"buttonEditableBackground", Color.class,
|
||||
"buttonFocusedBackground", Color.class,
|
||||
"buttonFocusedEditableBackground", Color.class,
|
||||
"buttonSeparatorWidth", float.class,
|
||||
"buttonSeparatorColor", Color.class,
|
||||
"buttonDisabledSeparatorColor", Color.class,
|
||||
@@ -391,6 +416,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
private void menuItem_checkIcon( Map<String, Class<?>> expected ) {
|
||||
expectedMap( expected,
|
||||
"icon.scale", float.class,
|
||||
|
||||
"icon.checkmarkColor", Color.class,
|
||||
"icon.disabledCheckmarkColor", Color.class,
|
||||
"selectionForeground", Color.class
|
||||
@@ -399,6 +426,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
private void menuItem_arrowIcon( Map<String, Class<?>> expected ) {
|
||||
expectedMap( expected,
|
||||
"icon.scale", float.class,
|
||||
|
||||
"icon.arrowType", String.class,
|
||||
"icon.arrowColor", Color.class,
|
||||
"icon.disabledArrowColor", Color.class,
|
||||
@@ -434,7 +463,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
expectedMap( expected,
|
||||
// capsLockIcon
|
||||
"capsLockIconColor", Color.class
|
||||
"capsLockIconColor", Color.class,
|
||||
"capsLockIconScale", float.class
|
||||
);
|
||||
|
||||
// border
|
||||
@@ -492,7 +522,20 @@ public class TestFlatStyleableInfo
|
||||
|
||||
@Test
|
||||
void radioButton() {
|
||||
JRadioButton c = new JRadioButton();
|
||||
radioButton( new JRadioButton() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void radioButton2() {
|
||||
radioButton( new JRadioButton( new CustomIcon() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void radioButton3() {
|
||||
radioButton( new JRadioButton( new CustomRadioButtonIcon() ) );
|
||||
}
|
||||
|
||||
private void radioButton( JRadioButton c ) {
|
||||
FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI();
|
||||
|
||||
assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon );
|
||||
@@ -504,6 +547,11 @@ public class TestFlatStyleableInfo
|
||||
"icon.centerDiameter", float.class
|
||||
);
|
||||
|
||||
// remove "icon." keys if radio button has custom icon
|
||||
Icon icon = c.getIcon();
|
||||
if( icon != null && !(icon instanceof FlatRadioButtonIcon) )
|
||||
expected.keySet().removeIf( key -> key.startsWith( "icon." ) );
|
||||
|
||||
assertMapEquals( expected, ui.getStyleableInfos( c ) );
|
||||
}
|
||||
|
||||
@@ -513,6 +561,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
//---- icon ----
|
||||
|
||||
"icon.scale", float.class,
|
||||
|
||||
"icon.focusWidth", float.class,
|
||||
"icon.focusColor", Color.class,
|
||||
"icon.borderWidth", float.class,
|
||||
@@ -791,6 +841,7 @@ public class TestFlatStyleableInfo
|
||||
"tabIconPlacement", int.class,
|
||||
|
||||
// FlatTabbedPaneCloseIcon
|
||||
"closeScale", float.class,
|
||||
"closeSize", Dimension.class,
|
||||
"closeArc", int.class,
|
||||
"closeCrossPlainSize", float.class,
|
||||
@@ -1112,6 +1163,16 @@ public class TestFlatStyleableInfo
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatScrollPaneBorder() {
|
||||
FlatScrollPaneBorder border = new FlatScrollPaneBorder();
|
||||
|
||||
Map<String, Class<?>> expected = new LinkedHashMap<>();
|
||||
flatScrollPaneBorder( expected );
|
||||
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatTextBorder() {
|
||||
FlatTextBorder border = new FlatTextBorder();
|
||||
@@ -1132,6 +1193,64 @@ public class TestFlatStyleableInfo
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatDropShadowBorder() {
|
||||
FlatDropShadowBorder border = new FlatDropShadowBorder();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"shadowColor", Color.class,
|
||||
"shadowInsets", Insets.class,
|
||||
"shadowOpacity", float.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatMenuBarBorder() {
|
||||
FlatMenuBarBorder border = new FlatMenuBarBorder();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"borderColor", Color.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatPopupMenuBorder() {
|
||||
FlatPopupMenuBorder border = new FlatPopupMenuBorder();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"borderInsets", Insets.class,
|
||||
"borderColor", Color.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatInternalFrameBorder() {
|
||||
FlatInternalFrameBorder border = new FlatInternalFrameBorder();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"activeBorderColor", Color.class,
|
||||
"inactiveBorderColor", Color.class,
|
||||
"borderLineWidth", int.class,
|
||||
"dropShadowPainted", boolean.class,
|
||||
"borderMargins", Insets.class,
|
||||
|
||||
"activeDropShadowColor", Color.class,
|
||||
"activeDropShadowInsets", Insets.class,
|
||||
"activeDropShadowOpacity", float.class,
|
||||
"inactiveDropShadowColor", Color.class,
|
||||
"inactiveDropShadowInsets", Insets.class,
|
||||
"inactiveDropShadowOpacity", float.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, border.getStyleableInfos() );
|
||||
}
|
||||
|
||||
//---- icons --------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@@ -1161,6 +1280,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
private void flatCheckBoxIcon( Map<String, Class<?>> expected ) {
|
||||
expectedMap( expected,
|
||||
"scale", float.class,
|
||||
|
||||
"focusWidth", float.class,
|
||||
"focusColor", Color.class,
|
||||
"borderWidth", float.class,
|
||||
@@ -1245,6 +1366,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
private void flatCheckBoxMenuItemIcon( Map<String, Class<?>> expected ) {
|
||||
expectedMap( expected,
|
||||
"scale", float.class,
|
||||
|
||||
"checkmarkColor", Color.class,
|
||||
"disabledCheckmarkColor", Color.class,
|
||||
"selectionForeground", Color.class
|
||||
@@ -1263,6 +1386,8 @@ public class TestFlatStyleableInfo
|
||||
|
||||
private void flatMenuArrowIcon( Map<String, Class<?>> expected ) {
|
||||
expectedMap( expected,
|
||||
"scale", float.class,
|
||||
|
||||
"arrowType", String.class,
|
||||
"arrowColor", Color.class,
|
||||
"disabledArrowColor", Color.class,
|
||||
@@ -1275,6 +1400,8 @@ public class TestFlatStyleableInfo
|
||||
FlatHelpButtonIcon icon = new FlatHelpButtonIcon();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"scale", float.class,
|
||||
|
||||
"focusWidth", int.class,
|
||||
"focusColor", Color.class,
|
||||
"innerFocusWidth", float.class,
|
||||
@@ -1295,4 +1422,87 @@ public class TestFlatStyleableInfo
|
||||
|
||||
assertMapEquals( expected, icon.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatClearIcon() {
|
||||
FlatClearIcon icon = new FlatClearIcon();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"clearIconScale", float.class,
|
||||
|
||||
"clearIconColor", Color.class,
|
||||
"clearIconHoverColor", Color.class,
|
||||
"clearIconPressedColor", Color.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, icon.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatSearchIcon() {
|
||||
FlatSearchIcon icon = new FlatSearchIcon();
|
||||
|
||||
Map<String, Class<?>> expected = new LinkedHashMap<>();
|
||||
flatSearchIcon( expected );
|
||||
|
||||
assertMapEquals( expected, icon.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatSearchWithHistoryIcon() {
|
||||
FlatSearchWithHistoryIcon icon = new FlatSearchWithHistoryIcon();
|
||||
|
||||
Map<String, Class<?>> expected = new LinkedHashMap<>();
|
||||
flatSearchIcon( expected );
|
||||
|
||||
assertMapEquals( expected, icon.getStyleableInfos() );
|
||||
}
|
||||
|
||||
private void flatSearchIcon( Map<String, Class<?>> expected ) {
|
||||
expectedMap( expected,
|
||||
"searchIconScale", float.class,
|
||||
|
||||
"searchIconColor", Color.class,
|
||||
"searchIconHoverColor", Color.class,
|
||||
"searchIconPressedColor", Color.class
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatCapsLockIcon() {
|
||||
FlatCapsLockIcon icon = new FlatCapsLockIcon();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
"capsLockIconScale", float.class,
|
||||
|
||||
"capsLockIconColor", Color.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, icon.getStyleableInfos() );
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatTabbedPaneCloseIcon() {
|
||||
FlatTabbedPaneCloseIcon icon = new FlatTabbedPaneCloseIcon();
|
||||
|
||||
Map<String, Class<?>> expected = expectedMap(
|
||||
//TODO closeScale ?
|
||||
// "scale", float.class,
|
||||
"closeScale", float.class,
|
||||
|
||||
"closeSize", Dimension.class,
|
||||
"closeArc", int.class,
|
||||
"closeCrossPlainSize", float.class,
|
||||
"closeCrossFilledSize", float.class,
|
||||
"closeCrossLineWidth", float.class,
|
||||
"closeBackground", Color.class,
|
||||
"closeForeground", Color.class,
|
||||
"closeHoverBackground", Color.class,
|
||||
"closeHoverForeground", Color.class,
|
||||
"closePressedBackground", Color.class,
|
||||
"closePressedForeground", Color.class
|
||||
);
|
||||
|
||||
assertMapEquals( expected, icon.getStyleableInfos() );
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -17,9 +17,15 @@
|
||||
package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import javax.swing.UIManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
import com.formdev.flatlaf.FlatIntelliJLaf;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
@@ -57,12 +63,57 @@ public class TestUtils
|
||||
|
||||
public static void assertMapEquals( Map<?, ?> expected, Map<?, ?> actual ) {
|
||||
if( !Objects.equals( expected, actual ) ) {
|
||||
String expectedStr = String.valueOf( expected ).replace( ", ", ",\n" );
|
||||
String actualStr = String.valueOf( actual ).replace( ", ", ",\n" );
|
||||
String expectedStr = String.valueOf( new TreeMap<>( expected ) ).replace( ", ", ",\n" );
|
||||
String actualStr = String.valueOf( new TreeMap<>( actual ) ).replace( ", ", ",\n" );
|
||||
String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr );
|
||||
|
||||
// pass expected/actual strings to exception for nice diff in IDE
|
||||
throw new AssertionFailedError( msg, expectedStr, actualStr );
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertSetEquals( Set<?> expected, Set<?> actual, String message ) {
|
||||
if( !Objects.equals( expected, actual ) ) {
|
||||
String expectedStr = String.valueOf( new TreeSet<>( expected ) ).replace( ", ", ",\n" );
|
||||
String actualStr = String.valueOf( new TreeSet<>( actual ) ).replace( ", ", ",\n" );
|
||||
String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr );
|
||||
if( message != null )
|
||||
msg = message + " ==> " + msg;
|
||||
|
||||
// pass expected/actual strings to exception for nice diff in IDE
|
||||
throw new AssertionFailedError( msg, expectedStr, actualStr );
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkImplementedTests( Set<String> excludes, Class<?> baseClass, Class<?>... classes ) {
|
||||
Set<String> expected = getTestMethods( baseClass );
|
||||
|
||||
for( Class<?> cls : classes ) {
|
||||
Set<String> actual = getTestMethods( cls );
|
||||
|
||||
for( String methodName : expected ) {
|
||||
if( !actual.contains( methodName ) && !excludes.contains( methodName ) ) {
|
||||
throw new AssertionFailedError( "missing " + cls.getSimpleName() + '.' + methodName
|
||||
+ "() for " + baseClass.getSimpleName() + '.' + methodName + "()" );
|
||||
}
|
||||
}
|
||||
|
||||
for( String methodName : actual ) {
|
||||
if( !expected.contains( methodName ) && !excludes.contains( methodName ) ) {
|
||||
throw new AssertionFailedError( "missing " + baseClass.getSimpleName() + '.' + methodName
|
||||
+ "() for " + cls.getSimpleName() + '.' + methodName + "()" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> getTestMethods( Class<?> cls ) {
|
||||
HashSet<String> tests = new HashSet<>();
|
||||
Method[] methods = cls.getDeclaredMethods();
|
||||
for( Method m : methods ) {
|
||||
if( m.isAnnotationPresent( Test.class ) )
|
||||
tests.add( m.getName() );
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import java.awt.Font;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.UnsupportedLookAndFeelException;
|
||||
import javax.swing.plaf.metal.MetalLookAndFeel;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.formdev.flatlaf.FlatDarkLaf;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import com.formdev.flatlaf.FlatSystemProperties;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class TestUIScale
|
||||
{
|
||||
private static Map<String, String> FONT_EXTRA_DEFAULTS_1x = Collections.singletonMap(
|
||||
"defaultFont", "{instance}java.awt.Font,Dialog,0,12" );
|
||||
private static Map<String, String> FONT_EXTRA_DEFAULTS_1_5x = Collections.singletonMap(
|
||||
"defaultFont", "{instance}java.awt.Font,Dialog,0,18" );
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
UIScale.inUnitTests = true;
|
||||
|
||||
// disable platform specific fonts
|
||||
System.setProperty( "flatlaf.uiScale.fontSizeDivider", "12" );
|
||||
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void cleanup() throws UnsupportedLookAndFeelException {
|
||||
System.clearProperty( "flatlaf.uiScale.fontSizeDivider" );
|
||||
FlatLaf.setGlobalExtraDefaults( null );
|
||||
|
||||
UIScale.inUnitTests = false;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() throws UnsupportedLookAndFeelException {
|
||||
UIManager.setLookAndFeel( new MetalLookAndFeel() );
|
||||
UIManager.put( "defaultFont", null );
|
||||
UIManager.put( "Label.font", null );
|
||||
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x );
|
||||
|
||||
UIScale.tests_uninitialize();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomScaleFactor() {
|
||||
System.setProperty( FlatSystemProperties.UI_SCALE, "1.25x" );
|
||||
assertScaleFactor( 1.25f );
|
||||
|
||||
System.setProperty( FlatSystemProperties.UI_SCALE, "2x" );
|
||||
UIScale.tests_uninitialize();
|
||||
assertScaleFactor( 2f );
|
||||
|
||||
System.clearProperty( FlatSystemProperties.UI_SCALE );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLabelFontScaling() {
|
||||
assertInstanceOf( MetalLookAndFeel.class, UIManager.getLookAndFeel() );
|
||||
|
||||
testLabelFont( 8, 1f );
|
||||
testLabelFont( 9, 1f );
|
||||
testLabelFont( 10, 1f );
|
||||
testLabelFont( 11, 1f );
|
||||
testLabelFont( 12, 1f );
|
||||
testLabelFont( 13, 1f );
|
||||
testLabelFont( 14, 1.25f );
|
||||
testLabelFont( 15, 1.25f );
|
||||
testLabelFont( 16, 1.25f );
|
||||
testLabelFont( 17, 1.5f );
|
||||
testLabelFont( 18, 1.5f );
|
||||
testLabelFont( 19, 1.5f );
|
||||
testLabelFont( 20, 1.75f );
|
||||
testLabelFont( 21, 1.75f );
|
||||
testLabelFont( 22, 1.75f );
|
||||
testLabelFont( 23, 2f );
|
||||
testLabelFont( 24, 2f );
|
||||
testLabelFont( 25, 2f );
|
||||
testLabelFont( 26, 2.25f );
|
||||
}
|
||||
|
||||
private void testLabelFont( int fontSize, float expectedScaleFactor ) {
|
||||
UIManager.put( "Label.font", new Font( Font.DIALOG, Font.PLAIN, fontSize ) );
|
||||
assertScaleFactor( expectedScaleFactor );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultFontScaling() {
|
||||
FlatLightLaf.setup();
|
||||
|
||||
testDefaultFont( 8, 1f );
|
||||
testDefaultFont( 9, 1f );
|
||||
testDefaultFont( 10, 1f );
|
||||
testDefaultFont( 11, 1f );
|
||||
testDefaultFont( 12, 1f );
|
||||
testDefaultFont( 13, 1f );
|
||||
testDefaultFont( 14, 1.25f );
|
||||
testDefaultFont( 15, 1.25f );
|
||||
testDefaultFont( 16, 1.25f );
|
||||
testDefaultFont( 17, 1.5f );
|
||||
testDefaultFont( 18, 1.5f );
|
||||
testDefaultFont( 19, 1.5f );
|
||||
testDefaultFont( 20, 1.75f );
|
||||
testDefaultFont( 21, 1.75f );
|
||||
testDefaultFont( 22, 1.75f );
|
||||
testDefaultFont( 23, 2f );
|
||||
testDefaultFont( 24, 2f );
|
||||
testDefaultFont( 25, 2f );
|
||||
testDefaultFont( 26, 2.25f );
|
||||
}
|
||||
|
||||
private void testDefaultFont( int fontSize, float expectedScaleFactor ) {
|
||||
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, fontSize ) );
|
||||
assertScaleFactor( expectedScaleFactor );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitialScaleFactorAndFontSizes() {
|
||||
FlatLightLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1f, 12, -1 );
|
||||
|
||||
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
|
||||
FlatDarkLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1.5f, 18, -1 );
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoom_Metal() {
|
||||
UIScale.setZoomFactor( 1.1f );
|
||||
assertScaleFactor( 1.1f );
|
||||
|
||||
UIScale.setZoomFactor( 1.3f );
|
||||
assertScaleFactor( 1.3f );
|
||||
|
||||
UIScale.setZoomFactor( 2.3f );
|
||||
assertScaleFactor( 2.3f );
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoom_1x() {
|
||||
FlatLightLaf.setup();
|
||||
testZoom( 0.7f, 0.7f, 8, -1 );
|
||||
testZoom( 0.75f, 0.75f, 9, -1 );
|
||||
testZoom( 0.8f, 0.8f, 10, -1 );
|
||||
testZoom( 0.9f, 0.9f, 11, -1 );
|
||||
testZoom( 1f, 1f, 12, -1 );
|
||||
testZoom( 1.1f, 1.1f, 13, -1 );
|
||||
testZoom( 1.2f, 1.2f, 14, -1 );
|
||||
testZoom( 1.25f, 1.25f, 15, -1 );
|
||||
testZoom( 1.3f, 1.3f, 16, -1 );
|
||||
testZoom( 1.4f, 1.4f, 17, -1 );
|
||||
testZoom( 1.5f, 1.5f, 18, -1 );
|
||||
testZoom( 1.6f, 1.6f, 19, -1 );
|
||||
testZoom( 1.7f, 1.7f, 20, -1 );
|
||||
testZoom( 1.75f, 1.75f, 21, -1 );
|
||||
testZoom( 1.8f, 1.8f, 22, -1 );
|
||||
testZoom( 1.9f, 1.9f, 23, -1 );
|
||||
testZoom( 2f, 2f, 24, -1 );
|
||||
testZoom( 2.25f, 2.25f, 27, -1 );
|
||||
testZoom( 2.5f, 2.5f, 30, -1 );
|
||||
testZoom( 2.75f, 2.75f, 33, -1 );
|
||||
testZoom( 3f, 3f, 36, -1 );
|
||||
testZoom( 4f, 4f, 48, -1 );
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoom_1_5x() {
|
||||
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
|
||||
FlatLightLaf.setup();
|
||||
|
||||
testZoom( 0.7f, 1.05f, 13, -1 );
|
||||
testZoom( 0.75f, 1.13f, 14, -1 );
|
||||
testZoom( 0.8f, 1.2f, 14, -1 );
|
||||
testZoom( 0.9f, 1.35f, 16, -1 );
|
||||
testZoom( 1f, 1.5f, 18, -1 );
|
||||
testZoom( 1.1f, 1.65f, 20, -1 );
|
||||
testZoom( 1.2f, 1.8f, 22, -1 );
|
||||
testZoom( 1.25f, 1.88f, 23, -1 );
|
||||
testZoom( 1.3f, 1.95f, 23, -1 );
|
||||
testZoom( 1.4f, 2.1f, 25, -1 );
|
||||
testZoom( 1.5f, 2.25f, 27, -1 );
|
||||
testZoom( 1.6f, 2.4f, 29, -1 );
|
||||
testZoom( 1.7f, 2.55f, 31, -1 );
|
||||
testZoom( 1.75f, 2.63f, 32, -1 );
|
||||
testZoom( 1.8f, 2.7f, 32, -1 );
|
||||
testZoom( 1.9f, 2.85f, 34, -1 );
|
||||
testZoom( 2f, 3f, 36, -1 );
|
||||
testZoom( 2.25f, 3.38f, 41, -1 );
|
||||
testZoom( 2.5f, 3.75f, 45, -1 );
|
||||
testZoom( 2.75f, 4.13f, 50, -1 );
|
||||
testZoom( 3f, 4.5f, 54, -1 );
|
||||
testZoom( 4f, 6f, 72, -1 );
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoomAppFont_1x() {
|
||||
FlatLightLaf.setup();
|
||||
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, 14 ) );
|
||||
|
||||
testZoom( 1f, 1.25f, 12, 14 );
|
||||
testZoom( 1.1f, 1.38f, 13, 15 );
|
||||
testZoom( 1.25f, 1.56f, 15, 17 );
|
||||
testZoom( 1.5f, 1.88f, 18, 20 );
|
||||
testZoom( 1.75f, 2.19f, 21, 23 );
|
||||
testZoom( 2f, 2.5f, 24, 26 );
|
||||
testZoom( 1f, 1.25f, 12, 13 );
|
||||
testZoom( 2f, 2.5f, 24, 26 );
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoomWithLafChange() {
|
||||
FlatLightLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1f, 12, -1 );
|
||||
testZoom( 1.1f, 1.1f, 13, -1 );
|
||||
|
||||
FlatDarkLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1.1f, 13, -1 );
|
||||
testZoom( 1.2f, 1.2f, 14, -1 );
|
||||
|
||||
FlatLightLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1.2f, 14, -1 );
|
||||
testZoom( 1.3f, 1.3f, 16, -1 );
|
||||
|
||||
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
|
||||
FlatDarkLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1.95f, 23, -1 );
|
||||
testZoom( 1.4f, 2.1f, 25, -1 );
|
||||
|
||||
FlatLightLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 2.1f, 25, -1 );
|
||||
testZoom( 1.5f, 2.25f, 27, -1 );
|
||||
}
|
||||
|
||||
@Test
|
||||
void zoomWithDefaultFontChange() {
|
||||
FlatLightLaf.setup();
|
||||
assertScaleFactorAndFontSizes( 1f, 12, -1 );
|
||||
|
||||
float zoom1 = 1.4f;
|
||||
testZoom( zoom1, zoom1, 17, -1 );
|
||||
testDefaultFont( 8, z( zoom1, 1f ) );
|
||||
testDefaultFont( 9, z( zoom1, 1f ) );
|
||||
testDefaultFont( 10, z( zoom1, 1f ) );
|
||||
testDefaultFont( 11, z( zoom1, 1f ) );
|
||||
testDefaultFont( 12, z( zoom1, 1f ) );
|
||||
testDefaultFont( 13, z( zoom1, 1f ) );
|
||||
testDefaultFont( 14, z( zoom1, 1.25f ) );
|
||||
testDefaultFont( 15, z( zoom1, 1.25f ) );
|
||||
testDefaultFont( 16, z( zoom1, 1.25f ) );
|
||||
testDefaultFont( 17, z( zoom1, 1.5f ) );
|
||||
testDefaultFont( 18, z( zoom1, 1.5f ) );
|
||||
testDefaultFont( 19, z( zoom1, 1.5f ) );
|
||||
testDefaultFont( 20, z( zoom1, 1.75f ) );
|
||||
testDefaultFont( 21, z( zoom1, 1.75f ) );
|
||||
testDefaultFont( 22, z( zoom1, 1.75f ) );
|
||||
testDefaultFont( 23, z( zoom1, 2f ) );
|
||||
testDefaultFont( 24, z( zoom1, 2f ) );
|
||||
testDefaultFont( 25, z( zoom1, 2f ) );
|
||||
testDefaultFont( 26, z( zoom1, 2.25f ) );
|
||||
|
||||
float zoom2 = 1.8f;
|
||||
testZoom( zoom2, 4.05f, 22, 33 );
|
||||
testDefaultFont( 8, z( zoom2, 1f ) );
|
||||
testDefaultFont( 9, z( zoom2, 1f ) );
|
||||
testDefaultFont( 10, z( zoom2, 1f ) );
|
||||
testDefaultFont( 11, z( zoom2, 1f ) );
|
||||
testDefaultFont( 12, z( zoom2, 1f ) );
|
||||
testDefaultFont( 13, z( zoom2, 1f ) );
|
||||
testDefaultFont( 14, z( zoom2, 1.25f ) );
|
||||
testDefaultFont( 15, z( zoom2, 1.25f ) );
|
||||
testDefaultFont( 16, z( zoom2, 1.25f ) );
|
||||
testDefaultFont( 17, z( zoom2, 1.5f ) );
|
||||
testDefaultFont( 18, z( zoom2, 1.5f ) );
|
||||
testDefaultFont( 19, z( zoom2, 1.5f ) );
|
||||
testDefaultFont( 20, z( zoom2, 1.75f ) );
|
||||
testDefaultFont( 21, z( zoom2, 1.75f ) );
|
||||
testDefaultFont( 22, z( zoom2, 1.75f ) );
|
||||
testDefaultFont( 23, z( zoom2, 2f ) );
|
||||
testDefaultFont( 24, z( zoom2, 2f ) );
|
||||
testDefaultFont( 25, z( zoom2, 2f ) );
|
||||
testDefaultFont( 26, z( zoom2, 2.25f ) );
|
||||
}
|
||||
|
||||
private static float z( float zoom, float scale ) {
|
||||
// round scale factor to 1/100
|
||||
return Math.round( (zoom * scale) * 100f ) / 100f;
|
||||
}
|
||||
|
||||
private static void testZoom( float zoomFactor, float expectedScaleFactor,
|
||||
int expectedLafFontSize, int expectedAppFontSize )
|
||||
{
|
||||
UIScale.setZoomFactor( zoomFactor );
|
||||
assertScaleFactorAndFontSizes( expectedScaleFactor, expectedLafFontSize, expectedAppFontSize );
|
||||
}
|
||||
|
||||
private static void assertScaleFactorAndFontSizes( float expectedScaleFactor,
|
||||
int expectedLafFontSize, int expectedAppFontSize )
|
||||
{
|
||||
assertScaleFactor( expectedScaleFactor );
|
||||
|
||||
Font lafFont = UIManager.getLookAndFeelDefaults().getFont( "defaultFont" );
|
||||
Font appFont = UIManager.getFont( "defaultFont" );
|
||||
assertEquals( expectedLafFontSize, lafFont.getSize() );
|
||||
if( expectedAppFontSize > 0 ) {
|
||||
assertNotEquals( lafFont, appFont );
|
||||
assertEquals( expectedAppFontSize, appFont.getSize() );
|
||||
} else
|
||||
assertEquals( lafFont, appFont );
|
||||
}
|
||||
|
||||
private static void assertScaleFactor( float expectedScaleFactor ) {
|
||||
assertEquals( expectedScaleFactor, UIScale.getUserScaleFactor() );
|
||||
}
|
||||
}
|
||||
@@ -43,15 +43,13 @@ tasks {
|
||||
dependsOn( ":flatlaf-intellij-themes:jar" )
|
||||
// dependsOn( ":flatlaf-natives-jna:jar" )
|
||||
|
||||
manifest {
|
||||
attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" )
|
||||
|
||||
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
|
||||
attributes( "Multi-Release" to "true" )
|
||||
manifest.attributes(
|
||||
"Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo",
|
||||
"Multi-Release" to "true",
|
||||
|
||||
// allow loading FlatLaf native library in Java 24+ (see https://openjdk.org/jeps/472)
|
||||
attributes( "Enable-Native-Access" to "ALL-UNNAMED" )
|
||||
}
|
||||
"Enable-Native-Access" to "ALL-UNNAMED",
|
||||
)
|
||||
|
||||
exclude( "module-info.class" )
|
||||
exclude( "META-INF/versions/*/module-info.class" )
|
||||
|
||||
@@ -111,6 +111,10 @@ class ControlBar
|
||||
UIScale.addPropertyChangeListener( e -> {
|
||||
// update info label because user scale factor may change
|
||||
updateInfoLabel();
|
||||
|
||||
// update "Font" menu (e.g. if zoom factor changed)
|
||||
if( UIScale.PROP_ZOOM_FACTOR.equals( e.getPropertyName() ) )
|
||||
frame.updateFontMenuItems();
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -192,13 +196,15 @@ class ControlBar
|
||||
String javaVendor = System.getProperty( "java.vendor" );
|
||||
if( "Oracle Corporation".equals( javaVendor ) )
|
||||
javaVendor = null;
|
||||
float zoomFactor = UIScale.getZoomFactor();
|
||||
double systemScaleFactor = UIScale.getSystemScaleFactor( getGraphicsConfiguration() );
|
||||
float userScaleFactor = UIScale.getUserScaleFactor();
|
||||
Font font = UIManager.getFont( "Label.font" );
|
||||
String newInfo = "(Java " + System.getProperty( "java.version" )
|
||||
+ (javaVendor != null ? ("; " + javaVendor) : "")
|
||||
+ (systemScaleFactor != 1 ? ("; system scale factor " + systemScaleFactor) : "")
|
||||
+ (userScaleFactor != 1 ? ("; user scale factor " + userScaleFactor) : "")
|
||||
+ (zoomFactor != 1 ? ("; zoom " + zoomFactor) : "")
|
||||
+ (systemScaleFactor != 1 ? ("; system scale " + systemScaleFactor) : "")
|
||||
+ (userScaleFactor != 1 ? ("; user scale " + userScaleFactor) : "")
|
||||
+ (systemScaleFactor == 1 && userScaleFactor == 1 ? "; no scaling" : "")
|
||||
+ "; " + font.getFamily() + " " + font.getSize()
|
||||
+ (font.isBold() ? " BOLD" : "")
|
||||
|
||||
@@ -18,12 +18,14 @@ 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;
|
||||
import java.time.Year;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.prefs.Preferences;
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.DefaultEditorKit;
|
||||
@@ -45,11 +47,14 @@ import com.formdev.flatlaf.extras.components.FlatButton.ButtonType;
|
||||
import com.formdev.flatlaf.icons.FlatAbstractIcon;
|
||||
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
|
||||
import com.formdev.flatlaf.themes.FlatMacLightLaf;
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||
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 com.formdev.flatlaf.util.UIScale;
|
||||
import net.miginfocom.layout.ConstraintParser;
|
||||
import net.miginfocom.layout.LC;
|
||||
import net.miginfocom.layout.UnitValue;
|
||||
@@ -71,6 +76,7 @@ class DemoFrame
|
||||
Arrays.sort( availableFontFamilyNames );
|
||||
|
||||
initComponents();
|
||||
initZommMenuItems();
|
||||
updateFontMenuItems();
|
||||
initAccentColors();
|
||||
initFullWindowContent();
|
||||
@@ -172,6 +178,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();
|
||||
}
|
||||
@@ -286,6 +334,92 @@ class DemoFrame
|
||||
showHints();
|
||||
}
|
||||
|
||||
private void initZommMenuItems() {
|
||||
float currentZoomFactor = UIScale.getZoomFactor();
|
||||
UIScale.setSupportedZoomFactors( new float[] { 0.7f, 0.8f, 0.9f, 1f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.75f, 2f } );
|
||||
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
HashMap<Float, JCheckBoxMenuItem> items = new HashMap<>();
|
||||
|
||||
// add supported zoom factors to "Zoom" menu
|
||||
zoomMenu.addSeparator();
|
||||
for( float zoomFactor : UIScale.getSupportedZoomFactors() ) {
|
||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem( (int)(zoomFactor * 100) + "%" );
|
||||
item.setSelected( zoomFactor == currentZoomFactor );
|
||||
item.addActionListener( this::zoomFactorChanged );
|
||||
zoomMenu.add( item );
|
||||
|
||||
group.add( item );
|
||||
items.put( zoomFactor, item );
|
||||
}
|
||||
|
||||
// update menu item selection if zoom factor changed
|
||||
UIScale.addPropertyChangeListener( e -> {
|
||||
if( UIScale.PROP_ZOOM_FACTOR.equals( e.getPropertyName() ) ) {
|
||||
float newZoomFactor = UIScale.getZoomFactor();
|
||||
JCheckBoxMenuItem item = items.get( newZoomFactor );
|
||||
if( item != null )
|
||||
item.setSelected( true );
|
||||
|
||||
zoomWindowBounds( this, (float) e.getOldValue(), (float) e.getNewValue() );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private static void zoomWindowBounds( Window window, float oldZoomFactor, float newZoomFactor ) {
|
||||
if( window instanceof Frame && ((Frame)window).getExtendedState() != Frame.NORMAL )
|
||||
return;
|
||||
|
||||
Rectangle oldBounds = window.getBounds();
|
||||
|
||||
// zoom window bounds
|
||||
float factor = (1f / oldZoomFactor) * newZoomFactor;
|
||||
int newWidth = (int) (oldBounds.width * factor);
|
||||
int newHeight = (int) (oldBounds.height * factor);
|
||||
int newX = oldBounds.x - ((newWidth - oldBounds.width) / 2);
|
||||
int newY = oldBounds.y - ((newHeight - oldBounds.height) / 2);
|
||||
|
||||
// get maximum window bounds (screen bounds minus screen insets)
|
||||
GraphicsConfiguration gc = window.getGraphicsConfiguration();
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
|
||||
Rectangle maxBounds = FlatUIUtils.subtractInsets( screenBounds, screenInsets );
|
||||
|
||||
// limit new window width/height
|
||||
newWidth = Math.min( newWidth, maxBounds.width );
|
||||
newHeight = Math.min( newHeight, maxBounds.height );
|
||||
|
||||
// move window into screen bounds
|
||||
newX = Math.max( Math.min( newX, maxBounds.width - newWidth ), maxBounds.x );
|
||||
newY = Math.max( Math.min( newY, maxBounds.height - newHeight ), maxBounds.y );
|
||||
|
||||
// set new window bounds
|
||||
window.setBounds( newX, newY, newWidth, newHeight );
|
||||
}
|
||||
|
||||
private void zoomFactorChanged( ActionEvent e ) {
|
||||
String zoomFactor = e.getActionCommand();
|
||||
float zoom = Integer.parseInt( zoomFactor.substring( 0, zoomFactor.length() - 1 ) ) / 100f;
|
||||
|
||||
if( UIScale.setZoomFactor( zoom ) )
|
||||
FlatLaf.updateUI();
|
||||
}
|
||||
|
||||
private void zoomReset() {
|
||||
if( UIScale.zoomReset() )
|
||||
FlatLaf.updateUI();
|
||||
}
|
||||
|
||||
private void zoomIn() {
|
||||
if( UIScale.zoomIn() )
|
||||
FlatLaf.updateUI();
|
||||
}
|
||||
|
||||
private void zoomOut() {
|
||||
if( UIScale.zoomOut() )
|
||||
FlatLaf.updateUI();
|
||||
}
|
||||
|
||||
private void fontFamilyChanged( ActionEvent e ) {
|
||||
String fontFamily = e.getActionCommand();
|
||||
|
||||
@@ -508,6 +642,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();
|
||||
@@ -533,6 +670,10 @@ class DemoFrame
|
||||
JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem();
|
||||
JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem();
|
||||
JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem();
|
||||
zoomMenu = new JMenu();
|
||||
JMenuItem resetZoomMenuItem = new JMenuItem();
|
||||
JMenuItem incrZoomMenuItem = new JMenuItem();
|
||||
JMenuItem decrZoomMenuItem = new JMenuItem();
|
||||
fontMenu = new JMenu();
|
||||
JMenuItem restoreFontMenuItem = new JMenuItem();
|
||||
JMenuItem incrFontMenuItem = new JMenuItem();
|
||||
@@ -608,6 +749,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()));
|
||||
@@ -778,25 +938,49 @@ class DemoFrame
|
||||
}
|
||||
menuBar.add(viewMenu);
|
||||
|
||||
//======== zoomMenu ========
|
||||
{
|
||||
zoomMenu.setText("Zoom");
|
||||
|
||||
//---- resetZoomMenuItem ----
|
||||
resetZoomMenuItem.setText("Reset Zoom");
|
||||
resetZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
resetZoomMenuItem.addActionListener(e -> zoomReset());
|
||||
zoomMenu.add(resetZoomMenuItem);
|
||||
|
||||
//---- incrZoomMenuItem ----
|
||||
incrZoomMenuItem.setText("Zoom In");
|
||||
incrZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
incrZoomMenuItem.addActionListener(e -> zoomIn());
|
||||
zoomMenu.add(incrZoomMenuItem);
|
||||
|
||||
//---- decrZoomMenuItem ----
|
||||
decrZoomMenuItem.setText("Zoom Out");
|
||||
decrZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
decrZoomMenuItem.addActionListener(e -> zoomOut());
|
||||
zoomMenu.add(decrZoomMenuItem);
|
||||
}
|
||||
menuBar.add(zoomMenu);
|
||||
|
||||
//======== fontMenu ========
|
||||
{
|
||||
fontMenu.setText("Font");
|
||||
|
||||
//---- restoreFontMenuItem ----
|
||||
restoreFontMenuItem.setText("Restore Font");
|
||||
restoreFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
restoreFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK));
|
||||
restoreFontMenuItem.addActionListener(e -> restoreFont());
|
||||
fontMenu.add(restoreFontMenuItem);
|
||||
|
||||
//---- incrFontMenuItem ----
|
||||
incrFontMenuItem.setText("Increase Font Size");
|
||||
incrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
incrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK));
|
||||
incrFontMenuItem.addActionListener(e -> incrFont());
|
||||
fontMenu.add(incrFontMenuItem);
|
||||
|
||||
//---- decrFontMenuItem ----
|
||||
decrFontMenuItem.setText("Decrease Font Size");
|
||||
decrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
decrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK));
|
||||
decrFontMenuItem.addActionListener(e -> decrFont());
|
||||
fontMenu.add(decrFontMenuItem);
|
||||
}
|
||||
@@ -1045,6 +1229,7 @@ class DemoFrame
|
||||
private JMenuItem exitMenuItem;
|
||||
private JMenu scrollingPopupMenu;
|
||||
private JMenuItem htmlMenuItem;
|
||||
private JMenu zoomMenu;
|
||||
private JMenu fontMenu;
|
||||
private JMenu optionsMenu;
|
||||
private JCheckBoxMenuItem windowDecorationsCheckBoxMenuItem;
|
||||
|
||||
@@ -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"
|
||||
} )
|
||||
@@ -362,6 +383,31 @@ new FormModel {
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
|
||||
} )
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
|
||||
name: "zoomMenu"
|
||||
"text": "Zoom"
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "resetZoomMenuItem"
|
||||
"text": "Reset Zoom"
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4226, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomReset", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "incrZoomMenuItem"
|
||||
"text": "Zoom In"
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4226, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomIn", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "decrZoomMenuItem"
|
||||
"text": "Zoom Out"
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4226, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomOut", false ) )
|
||||
} )
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
|
||||
name: "fontMenu"
|
||||
"text": "Font"
|
||||
@@ -371,19 +417,19 @@ new FormModel {
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "restoreFontMenuItem"
|
||||
"text": "Restore Font"
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4226, false )
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4746, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "restoreFont", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "incrFontMenuItem"
|
||||
"text": "Increase Font Size"
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4226, false )
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4746, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "incrFont", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "decrFontMenuItem"
|
||||
"text": "Decrease Font Size"
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4226, false )
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4746, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decrFont", false ) )
|
||||
} )
|
||||
} )
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -69,6 +69,7 @@ class TabsPanel
|
||||
initTabWidthMode( widthPreferredTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_PREFERRED );
|
||||
initTabWidthMode( widthEqualTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_EQUAL );
|
||||
initTabWidthMode( widthCompactTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_COMPACT );
|
||||
initTabWidthMode( widthIconOnlyTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY );
|
||||
}
|
||||
|
||||
private void initTabPlacementTabs( JTabbedPane tabbedPane ) {
|
||||
@@ -262,7 +263,9 @@ class TabsPanel
|
||||
|
||||
private void initTabWidthMode( JTabbedPane tabbedPane, String tabWidthMode ) {
|
||||
tabbedPane.putClientProperty( TABBED_PANE_TAB_WIDTH_MODE, tabWidthMode );
|
||||
if( tabWidthMode.equals( TABBED_PANE_TAB_WIDTH_MODE_COMPACT ) ) {
|
||||
if( tabWidthMode.equals( TABBED_PANE_TAB_WIDTH_MODE_COMPACT ) ||
|
||||
tabWidthMode.equals( TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY ) )
|
||||
{
|
||||
tabbedPane.addTab( "Search", new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/search.svg", 16, 16 ), null );
|
||||
tabbedPane.addTab( "Recents", new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/RecentlyUsed.svg", 16, 16 ), null );
|
||||
tabbedPane.addTab( "Favorites", new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/favorite.svg", 16, 16 ), null );
|
||||
@@ -390,6 +393,7 @@ class TabsPanel
|
||||
widthPreferredTabbedPane = new JTabbedPane();
|
||||
widthEqualTabbedPane = new JTabbedPane();
|
||||
widthCompactTabbedPane = new JTabbedPane();
|
||||
widthIconOnlyTabbedPane = new JTabbedPane();
|
||||
JLabel minMaxTabWidthLabel = new JLabel();
|
||||
minimumTabWidthTabbedPane = new JTabbedPane();
|
||||
maximumTabWidthTabbedPane = new JTabbedPane();
|
||||
@@ -684,6 +688,7 @@ class TabsPanel
|
||||
"[]" +
|
||||
"[]" +
|
||||
"[]" +
|
||||
"[]" +
|
||||
"[]para" +
|
||||
"[]" +
|
||||
"[]" +
|
||||
@@ -697,25 +702,56 @@ class TabsPanel
|
||||
panel3.add(tabWidthModeLabel, "cell 0 0");
|
||||
|
||||
//---- tabWidthModeNoteLabel ----
|
||||
tabWidthModeNoteLabel.setText("(preferred/equal/compact)");
|
||||
tabWidthModeNoteLabel.setText("(preferred/equal/compact/iconOnly)");
|
||||
tabWidthModeNoteLabel.setEnabled(false);
|
||||
tabWidthModeNoteLabel.putClientProperty(FlatClientProperties.STYLE_CLASS, "small");
|
||||
panel3.add(tabWidthModeNoteLabel, "cell 0 1");
|
||||
|
||||
//======== widthPreferredTabbedPane ========
|
||||
{
|
||||
widthPreferredTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel3.add(widthPreferredTabbedPane, "cell 0 2");
|
||||
|
||||
//======== widthEqualTabbedPane ========
|
||||
{
|
||||
widthEqualTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel3.add(widthEqualTabbedPane, "cell 0 3");
|
||||
|
||||
//======== widthCompactTabbedPane ========
|
||||
{
|
||||
widthCompactTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel3.add(widthCompactTabbedPane, "cell 0 4");
|
||||
|
||||
//======== widthIconOnlyTabbedPane ========
|
||||
{
|
||||
widthIconOnlyTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel3.add(widthIconOnlyTabbedPane, "cell 0 5");
|
||||
|
||||
//---- minMaxTabWidthLabel ----
|
||||
minMaxTabWidthLabel.setText("Minimum/maximum tab width");
|
||||
minMaxTabWidthLabel.putClientProperty(FlatClientProperties.STYLE_CLASS, "h3");
|
||||
panel3.add(minMaxTabWidthLabel, "cell 0 5");
|
||||
panel3.add(minimumTabWidthTabbedPane, "cell 0 6");
|
||||
panel3.add(maximumTabWidthTabbedPane, "cell 0 7");
|
||||
panel3.add(minMaxTabWidthLabel, "cell 0 6");
|
||||
|
||||
//======== minimumTabWidthTabbedPane ========
|
||||
{
|
||||
minimumTabWidthTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel3.add(minimumTabWidthTabbedPane, "cell 0 7");
|
||||
|
||||
//======== maximumTabWidthTabbedPane ========
|
||||
{
|
||||
maximumTabWidthTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel3.add(maximumTabWidthTabbedPane, "cell 0 8");
|
||||
|
||||
//---- tabAlignmentLabel ----
|
||||
tabAlignmentLabel.setText("Tab title alignment");
|
||||
tabAlignmentLabel.putClientProperty(FlatClientProperties.STYLE_CLASS, "h3");
|
||||
panel3.add(tabAlignmentLabel, "cell 0 8");
|
||||
panel3.add(tabAlignmentLabel, "cell 0 9");
|
||||
|
||||
//======== panel5 ========
|
||||
{
|
||||
@@ -742,17 +778,33 @@ class TabsPanel
|
||||
tabAlignmentNoteLabel2.setEnabled(false);
|
||||
tabAlignmentNoteLabel2.putClientProperty(FlatClientProperties.STYLE_CLASS, "small");
|
||||
panel5.add(tabAlignmentNoteLabel2, "cell 1 0,alignx right,growx 0");
|
||||
|
||||
//======== tabAlignLeadingTabbedPane ========
|
||||
{
|
||||
tabAlignLeadingTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel5.add(tabAlignLeadingTabbedPane, "cell 0 1");
|
||||
|
||||
//======== tabAlignVerticalTabbedPane ========
|
||||
{
|
||||
tabAlignVerticalTabbedPane.setTabPlacement(SwingConstants.LEFT);
|
||||
tabAlignVerticalTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel5.add(tabAlignVerticalTabbedPane, "cell 1 1 1 4,growy");
|
||||
|
||||
//======== tabAlignCenterTabbedPane ========
|
||||
{
|
||||
tabAlignCenterTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel5.add(tabAlignCenterTabbedPane, "cell 0 2");
|
||||
|
||||
//======== tabAlignTrailingTabbedPane ========
|
||||
{
|
||||
tabAlignTrailingTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
|
||||
}
|
||||
panel5.add(tabAlignTrailingTabbedPane, "cell 0 3");
|
||||
}
|
||||
panel3.add(panel5, "cell 0 9");
|
||||
panel3.add(panel5, "cell 0 10");
|
||||
}
|
||||
panel6.add(panel3, "cell 2 0");
|
||||
}
|
||||
@@ -1027,6 +1079,7 @@ class TabsPanel
|
||||
private JTabbedPane widthPreferredTabbedPane;
|
||||
private JTabbedPane widthEqualTabbedPane;
|
||||
private JTabbedPane widthCompactTabbedPane;
|
||||
private JTabbedPane widthIconOnlyTabbedPane;
|
||||
private JTabbedPane minimumTabWidthTabbedPane;
|
||||
private JTabbedPane maximumTabWidthTabbedPane;
|
||||
private JPanel panel5;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
JFDML JFormDesigner: "8.2.0.0.331" Java: "21" encoding: "UTF-8"
|
||||
JFDML JFormDesigner: "8.3" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
@@ -337,7 +337,7 @@ new FormModel {
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$layoutConstraints": "insets 0,hidemode 3"
|
||||
"$columnConstraints": "[grow,fill]"
|
||||
"$rowConstraints": "[]0[][][][]para[][][]para[]0[]"
|
||||
"$rowConstraints": "[]0[][][][][]para[][][]para[]0[]"
|
||||
} ) {
|
||||
name: "panel3"
|
||||
auxiliary() {
|
||||
@@ -355,7 +355,7 @@ new FormModel {
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "tabWidthModeNoteLabel"
|
||||
"text": "(preferred/equal/compact)"
|
||||
"text": "(preferred/equal/compact/iconOnly)"
|
||||
"enabled": false
|
||||
"$client.FlatLaf.styleClass": "small"
|
||||
auxiliary() {
|
||||
@@ -366,19 +366,28 @@ new FormModel {
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "widthPreferredTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 2"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "widthEqualTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 3"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "widthCompactTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "widthIconOnlyTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 5"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "minMaxTabWidthLabel"
|
||||
"text": "Minimum/maximum tab width"
|
||||
@@ -386,19 +395,21 @@ new FormModel {
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 5"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "minimumTabWidthTabbedPane"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 6"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "maximumTabWidthTabbedPane"
|
||||
name: "minimumTabWidthTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 7"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "maximumTabWidthTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 8"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "tabAlignmentLabel"
|
||||
"text": "Tab title alignment"
|
||||
@@ -407,7 +418,7 @@ new FormModel {
|
||||
"JavaCodeGenerator.variableLocal": true
|
||||
}
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 8"
|
||||
"value": "cell 0 9"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$columnConstraints": "[grow,fill]para[fill]"
|
||||
@@ -439,27 +450,31 @@ new FormModel {
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "tabAlignLeadingTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "tabAlignVerticalTabbedPane"
|
||||
"tabPlacement": 2
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 1 1 4,growy"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "tabAlignCenterTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 2"
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
|
||||
name: "tabAlignTrailingTabbedPane"
|
||||
"$client.JTabbedPane.tabHeight": 28
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 3"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 9"
|
||||
"value": "cell 0 10"
|
||||
} )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 2 0"
|
||||
|
||||
@@ -43,7 +43,8 @@ public class FlatDesktop
|
||||
public static boolean isSupported( Action action ) {
|
||||
if( SystemInfo.isJava_9_orLater ) {
|
||||
try {
|
||||
return Desktop.getDesktop().isSupported( Enum.valueOf( Desktop.Action.class, action.name() ) );
|
||||
return Desktop.isDesktopSupported() &&
|
||||
Desktop.getDesktop().isSupported( Enum.valueOf( Desktop.Action.class, action.name() ) );
|
||||
} catch( Exception ex ) {
|
||||
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||
return false;
|
||||
|
||||
@@ -524,7 +524,9 @@ public class FlatSVGIcon
|
||||
private URL getIconURL( String name, boolean dark ) {
|
||||
if( dark ) {
|
||||
int dotIndex = name.lastIndexOf( '.' );
|
||||
name = name.substring( 0, dotIndex ) + "_dark" + name.substring( dotIndex );
|
||||
name = (dotIndex > 0)
|
||||
? name.substring( 0, dotIndex ) + "_dark" + name.substring( dotIndex )
|
||||
: name + "_dark";
|
||||
}
|
||||
|
||||
ClassLoader cl = (classLoader != null) ? classLoader : FlatSVGIcon.class.getClassLoader();
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.prefs.Preferences;
|
||||
@@ -202,6 +203,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 ) {
|
||||
@@ -309,7 +311,7 @@ public class FlatUIDefaultsInspector
|
||||
Set<Entry<Object, Object>> defaultsSet = defaults.entrySet();
|
||||
ArrayList<Item> items = new ArrayList<>( defaultsSet.size() );
|
||||
HashSet<Object> keys = new HashSet<>( defaultsSet.size() );
|
||||
Color[] pBaseColor = new Color[1];
|
||||
AtomicReference<Color> pBaseColor = new AtomicReference<>();
|
||||
for( Entry<Object,Object> e : defaultsSet ) {
|
||||
Object key = e.getKey();
|
||||
|
||||
@@ -335,7 +337,7 @@ public class FlatUIDefaultsInspector
|
||||
if( value instanceof DerivedColor ) {
|
||||
Color resolvedColor = resolveDerivedColor( defaults, (String) key, (DerivedColor) value, pBaseColor );
|
||||
if( resolvedColor != value )
|
||||
info = new Color[] { resolvedColor, pBaseColor[0] };
|
||||
info = new Color[] { resolvedColor, pBaseColor.get() };
|
||||
}
|
||||
|
||||
// check whether key was overridden using UIManager.put(key,value)
|
||||
@@ -350,9 +352,9 @@ public class FlatUIDefaultsInspector
|
||||
return items.toArray( new Item[items.size()] );
|
||||
}
|
||||
|
||||
private Color resolveDerivedColor( UIDefaults defaults, String key, Color color, Color[] pBaseColor ) {
|
||||
private Color resolveDerivedColor( UIDefaults defaults, String key, Color color, AtomicReference<Color> pBaseColor ) {
|
||||
if( pBaseColor != null )
|
||||
pBaseColor[0] = null;
|
||||
pBaseColor.set( null );
|
||||
|
||||
if( !(color instanceof DerivedColor) )
|
||||
return color;
|
||||
@@ -376,7 +378,7 @@ public class FlatUIDefaultsInspector
|
||||
baseColor = resolveDerivedColor( defaults, (String) baseKey, baseColor, null );
|
||||
|
||||
if( pBaseColor != null )
|
||||
pBaseColor[0] = baseColor;
|
||||
pBaseColor.set( baseColor );
|
||||
|
||||
Color newColor = FlatUIUtils.deriveColor( color, baseColor );
|
||||
|
||||
@@ -775,6 +777,9 @@ public class FlatUIDefaultsInspector
|
||||
|
||||
@SuppressWarnings( "FormatString" ) // Error Prone
|
||||
private static String color2hex( Color color ) {
|
||||
if( color == null )
|
||||
return "";
|
||||
|
||||
int rgb = color.getRGB();
|
||||
boolean hasAlpha = color.getAlpha() != 255;
|
||||
|
||||
@@ -1016,28 +1021,36 @@ public class FlatUIDefaultsInspector
|
||||
item = (Item) value;
|
||||
init( table, item.key, isSelected, row );
|
||||
|
||||
// reset background, foreground and icon
|
||||
if( !(item.value instanceof Color) ) {
|
||||
// get color of value
|
||||
valueColor = null;
|
||||
if( item.value instanceof Color )
|
||||
valueColor = (item.info instanceof Color[]) ? ((Color[])item.info)[0] : (Color) item.value;
|
||||
else if( item.value instanceof FlatLineBorder )
|
||||
valueColor = ((FlatLineBorder)item.value).getLineColor();
|
||||
|
||||
// reset background and foreground
|
||||
if( valueColor == null ) {
|
||||
setBackground( null );
|
||||
setForeground( null );
|
||||
}
|
||||
if( !(item.value instanceof Icon) )
|
||||
setIcon( null );
|
||||
|
||||
// value to string
|
||||
value = item.getValueAsString();
|
||||
|
||||
super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
|
||||
|
||||
if( item.value instanceof Color ) {
|
||||
Color color = (item.info instanceof Color[]) ? ((Color[])item.info)[0] : (Color) item.value;
|
||||
boolean isDark = new HSLColor( color ).getLuminance() < 70 && color.getAlpha() >= 128;
|
||||
valueColor = color;
|
||||
// set foreground, if value has color
|
||||
if( valueColor != null ) {
|
||||
boolean isDark = new HSLColor( valueColor ).getLuminance() < 70 && valueColor.getAlpha() >= 128;
|
||||
setForeground( isDark ? Color.white : Color.black );
|
||||
} else if( item.value instanceof Icon ) {
|
||||
}
|
||||
|
||||
// set icon
|
||||
if( item.value instanceof Icon ) {
|
||||
Icon icon = (Icon) item.value;
|
||||
setIcon( new SafeIcon( icon ) );
|
||||
}
|
||||
} else
|
||||
setIcon( null );
|
||||
|
||||
// set tooltip
|
||||
String toolTipText = (item.value instanceof Object[])
|
||||
@@ -1054,7 +1067,7 @@ public class FlatUIDefaultsInspector
|
||||
|
||||
@Override
|
||||
protected void paintComponent( Graphics g ) {
|
||||
if( item.value instanceof Color ) {
|
||||
if( valueColor != null ) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
Color background = valueColor;
|
||||
|
||||
@@ -484,7 +484,7 @@ public class FlatTabbedPane
|
||||
|
||||
|
||||
// NOTE: enum names must be equal to allowed strings
|
||||
public enum TabWidthMode { preferred, equal, compact }
|
||||
public enum TabWidthMode { preferred, equal, compact, /** @since 3.7 */ iconOnly }
|
||||
|
||||
/**
|
||||
* Returns how the tabs should be sized.
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`flatlaf-toolchain`
|
||||
`flatlaf-module-info`
|
||||
`flatlaf-publish`
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
~~~
|
||||
|
||||
@@ -40,6 +40,11 @@ var javaHome = System.getProperty( "java.home" )
|
||||
if( javaHome.endsWith( "jre" ) && !file( "${javaHome}/include" ).exists() )
|
||||
javaHome += "/.."
|
||||
|
||||
interface InjectedOps {
|
||||
@get:Inject val fs: FileSystemOperations
|
||||
@get:Inject val e: ExecOperations
|
||||
}
|
||||
|
||||
tasks {
|
||||
register( "build-natives" ) {
|
||||
group = "build"
|
||||
@@ -65,15 +70,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,21 +115,23 @@ 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()
|
||||
}
|
||||
} )
|
||||
|
||||
val injectedOps = project.objects.newInstance<InjectedOps>()
|
||||
|
||||
doLast {
|
||||
// copy shared library to flatlaf-core resources
|
||||
copy {
|
||||
injectedOps.fs.copy {
|
||||
from( linkedFile )
|
||||
into( nativesDir )
|
||||
rename( linkedFile.get().asFile.name, libraryName )
|
||||
}
|
||||
|
||||
// dump( linkedFile.asFile.get(), true )
|
||||
}
|
||||
|
||||
// dump( linkedFile, true, injectedOps )
|
||||
}
|
||||
|
||||
if( org.gradle.internal.os.OperatingSystem.current().isLinux &&
|
||||
@@ -128,7 +157,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,49 +194,55 @@ 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",
|
||||
)
|
||||
|
||||
val injectedOps = project.objects.newInstance<InjectedOps>()
|
||||
|
||||
doLast {
|
||||
// copy shared library to flatlaf-core resources
|
||||
copy {
|
||||
injectedOps.fs.copy {
|
||||
from( "$outDir/$libraryName" )
|
||||
into( nativesDir )
|
||||
}
|
||||
|
||||
// dump( file( "$outDir/$libraryName" ), false )
|
||||
}
|
||||
|
||||
// dump( file( "$outDir/$libraryName" ), false, injectedOps )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*dump
|
||||
interface InjectedExecOps { @get:Inject val execOps: ExecOperations }
|
||||
val injected = project.objects.newInstance<InjectedExecOps>()
|
||||
|
||||
fun dump( dylib: File, disassemble: Boolean ) {
|
||||
|
||||
val dylibDir = dylib.parent
|
||||
injected.execOps.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
|
||||
injected.execOps.exec {
|
||||
commandLine( "objdump",
|
||||
// commands
|
||||
"--archive-headers",
|
||||
"--section-headers",
|
||||
"--private-headers",
|
||||
"--reloc",
|
||||
"--dynamic-reloc",
|
||||
"--syms",
|
||||
// files
|
||||
dylib )
|
||||
standardOutput = FileOutputStream( "$dylibDir/objdump.txt" )
|
||||
fun Task.dump( f: Any, disassemble: Boolean, injectedOps: InjectedOps ) {
|
||||
doLast {
|
||||
val dylib = if( f is RegularFileProperty) f.get().asFile else f as File
|
||||
val dylibDir = dylib.parent
|
||||
injectedOps.e.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
|
||||
injectedOps.e.exec {
|
||||
commandLine( "objdump",
|
||||
// commands
|
||||
"--archive-headers",
|
||||
"--section-headers",
|
||||
"--private-headers",
|
||||
"--reloc",
|
||||
"--dynamic-reloc",
|
||||
"--syms",
|
||||
// files
|
||||
dylib )
|
||||
standardOutput = FileOutputStream( "$dylibDir/objdump.txt" )
|
||||
}
|
||||
if( disassemble )
|
||||
injectedOps.e.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
|
||||
injectedOps.e.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
|
||||
injectedOps.e.exec { commandLine( "hexdump", dylib ); standardOutput = FileOutputStream( "$dylibDir/hexdump.txt" ) }
|
||||
}
|
||||
if( disassemble )
|
||||
injected.execOps.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
|
||||
injected.execOps.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
|
||||
injected.execOps.exec { commandLine( "hexdump", dylib ); standardOutput = FileOutputStream( "$dylibDir/hexdump.txt" ) }
|
||||
}
|
||||
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.FlatNativeLinuxLibrary
|
||||
#define API_VERSION_LINUX 3001
|
||||
#define API_VERSION_LINUX 3003
|
||||
|
||||
|
||||
//---- JNI methods ------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* 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, jint dark, 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
|
||||
GdkScreen* screen = gdk_window_get_screen( gdkOwner );
|
||||
gtk_window_set_screen( GTK_WINDOW( dialog ), screen );
|
||||
|
||||
// set the transient when the file dialog is realized
|
||||
g_signal_connect( dialog, "realize", G_CALLBACK( handle_realize ), gdkOwner );
|
||||
|
||||
// set light/dark appearance
|
||||
if( dark >= 0 ) {
|
||||
GtkSettings *settings = gtk_settings_get_for_screen( screen );
|
||||
|
||||
// get current GTK theme
|
||||
gchar* currentGtkTheme;
|
||||
g_object_get( settings, "gtk-theme-name", ¤tGtkTheme, NULL );
|
||||
|
||||
const char* darkSuffix = "-dark";
|
||||
bool isDarkGtkTheme = g_str_has_suffix( currentGtkTheme, darkSuffix );
|
||||
if( isDarkGtkTheme && dark == 0 ) {
|
||||
// current GTK theme is dark, but FlatLaf theme is light
|
||||
// in this case, "gtk-application-prefer-dark-theme" does not work
|
||||
// and there is no "gtk-application-prefer-light-theme" setting
|
||||
// --> try to switch to light GTK theme (if available)
|
||||
gchar* lightGtkTheme = g_strndup( currentGtkTheme, strlen( currentGtkTheme ) - strlen( darkSuffix ) );
|
||||
gchar* themeDir = g_strdup_printf( "/usr/share/themes/%s", lightGtkTheme );
|
||||
if( g_file_test( themeDir, G_FILE_TEST_IS_DIR ) )
|
||||
g_object_set( settings, "gtk-theme-name", lightGtkTheme, NULL );
|
||||
g_free( themeDir );
|
||||
g_free( lightGtkTheme );
|
||||
}
|
||||
|
||||
g_free( currentGtkTheme );
|
||||
|
||||
// let GTK know whether we prefer a dark theme
|
||||
g_object_set( settings, "gtk-application-prefer-dark-theme", (dark == 1), NULL );
|
||||
}
|
||||
}
|
||||
|
||||
// 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; }
|
||||
};
|
||||
@@ -7,8 +7,36 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPLEFT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPLEFT 0L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOP
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOP 1L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPRIGHT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPRIGHT 2L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_RIGHT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_RIGHT 3L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMRIGHT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMRIGHT 4L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOM
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOM 5L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMLEFT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMLEFT 6L
|
||||
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_LEFT
|
||||
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_LEFT 7L
|
||||
#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 +53,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;IZLjava/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, jint, 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
|
||||
|
||||
@@ -43,8 +43,10 @@ var javaHome = System.getProperty( "java.home" )
|
||||
if( javaHome.endsWith( "jre" ) )
|
||||
javaHome += "/.."
|
||||
|
||||
interface InjectedExecOps { @get:Inject val execOps: ExecOperations }
|
||||
val injected = project.objects.newInstance<InjectedExecOps>()
|
||||
interface InjectedOps {
|
||||
@get:Inject val fs: FileSystemOperations
|
||||
@get:Inject val e: ExecOperations
|
||||
}
|
||||
|
||||
tasks {
|
||||
register( "build-natives" ) {
|
||||
@@ -75,7 +77,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()
|
||||
}
|
||||
} )
|
||||
@@ -96,12 +98,14 @@ tasks {
|
||||
}
|
||||
} )
|
||||
|
||||
val injectedOps = project.objects.newInstance<InjectedOps>()
|
||||
|
||||
doLast {
|
||||
// sign shared library
|
||||
// injected.execOps.exec { commandLine( "codesign", "-s", "FormDev Software GmbH", "--timestamp", "${linkedFile.asFile.get()}" ) }
|
||||
// injectedOps.e.exec { commandLine( "codesign", "-s", "FormDev Software GmbH", "--timestamp", "${linkedFile.asFile.get()}" ) }
|
||||
|
||||
// copy shared library to flatlaf-core resources
|
||||
copy {
|
||||
injectedOps.fs.copy {
|
||||
from( linkedFile )
|
||||
into( nativesDir )
|
||||
rename( linkedFile.get().asFile.name, libraryName )
|
||||
@@ -110,9 +114,9 @@ tasks {
|
||||
/*dump
|
||||
val dylib = linkedFile.asFile.get()
|
||||
val dylibDir = dylib.parent
|
||||
injected.execOps.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
|
||||
injected.execOps.exec { commandLine( "size", "-m", dylib ); standardOutput = FileOutputStream( "$dylibDir/size-m.txt" ) }
|
||||
injected.execOps.exec {
|
||||
injectedOps.e.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
|
||||
injectedOps.e.exec { commandLine( "size", "-m", dylib ); standardOutput = FileOutputStream( "$dylibDir/size-m.txt" ) }
|
||||
injectedOps.e.exec {
|
||||
commandLine( "objdump",
|
||||
// commands
|
||||
"--archive-headers",
|
||||
@@ -130,8 +134,8 @@ tasks {
|
||||
dylib )
|
||||
standardOutput = FileOutputStream( "$dylibDir/objdump.txt" )
|
||||
}
|
||||
injected.execOps.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
|
||||
injected.execOps.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
|
||||
injectedOps.e.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
|
||||
injectedOps.e.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
|
||||
dump*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user