Compare commits

..

30 Commits

Author SHA1 Message Date
Karl Tauber
44a04cca2c FlatSmoothScrollingTest:
- better list/tree/etc items for easier recognizing jittery scrolling
- sliders to modify animation duration and resolution
- slider to invoke `scrollRectToVisible()`
- option to show row header for table
- use viewport.viewPosition for chart (instead of scrollbar.value)
- highlight methods in stack of tooltip (e.g. JViewport.setViewPosition())
2023-09-02 17:56:11 +02:00
Karl Tauber
b32b8db97a FlatSmoothScrollingTest: refactored line chart panel into own class for easier use in other test apps 2023-08-30 00:20:45 +02:00
Karl Tauber
c529dcb747 Smooth Scrolling:
- fixed jittery repeating-scrolling with PageUp/Down keys when reaching the top/bottom/left/right of the viewport (see FlatScrollBarUI.setValueAnimated())
- temporary change viewport scroll mode only if it is JViewport.BLIT_SCROLL_MODE
- use JViewport.SIMPLE_SCROLL_MODE when temporary disabling blitting
2023-08-27 14:31:30 +02:00
Karl Tauber
04658c2ef0 SmoothScrollingTest: fixed error reported by Error Prone 2023-08-25 17:43:58 +02:00
Karl Tauber
5cdef5409b Smooth Scrolling: fixed jittery scrolling with trackpad or Magic Mouse (if smooth scrolling is enabled) 2023-08-25 15:24:28 +02:00
Karl Tauber
6dfc204e40 SmoothScrollingTest added (from https://github.com/JFormDesigner/FlatLaf/pull/683#issuecomment-1585667066) 2023-08-25 15:23:59 +02:00
Karl Tauber
542e7d5f60 Smooth Scrolling: fixes too slow repeating block (page) scrolling (e.g. hold down PageUp key) for Tree, TextArea, TextPane and EditorPane 2023-08-24 22:38:52 +02:00
Karl Tauber
3628a03c9d introduced FlatUIAction 2023-08-24 11:54:32 +02:00
Karl Tauber
6ce2198cd6 FlatSmoothScrollingTest:
- added slider to horizontally scale chart
- improved chart legend
- record stack for points in chart and show in tooltip on hover
2023-08-23 15:53:55 +02:00
Karl Tauber
e2e3fd31e9 FlatSmoothScrollingTest:
- added small vertical line to indicate data points in chart
- added split pane to allow changing height of components
- Alt+C clears chart without moving focus to "Clear" button
- separate chart lines for smooth and non-smooth scrolling
2023-08-23 09:51:57 +02:00
Karl Tauber
cf70cfb50c ScrollBar: fixed temporary painting at wrong location during smooth scrolling when using mouse-wheel or scroll bar
(still occurs when scrolling by moving selection via keyboard)

many thanks to @Chrriis for the idea to temporary disable blitting mode on viewport
2023-08-23 09:51:57 +02:00
Karl Tauber
29f6c5fae9 FlatAnimatorTest: added test for precise scrolling with trackpad 2023-08-23 09:51:57 +02:00
Karl Tauber
419a689ca4 FlatAnimatorTest: added test for wheel scrolling (including chart) 2023-08-23 09:51:57 +02:00
Karl Tauber
865a56875f FlatSmoothScrollingTest: added "custom" scroll pane for testing smooth scrolling in case that scroll view does not implement Scrollable interface 2023-08-23 09:51:57 +02:00
Karl Tauber
3573188025 ScrollBar: support smooth scrolling via keyboard 2023-08-23 09:51:57 +02:00
Karl Tauber
1f2622819a FlatSmoothScrollingTest: support dark themes and added "Show table grid" and "Auto-resize mode" check boxes 2023-08-23 09:51:57 +02:00
Karl Tauber
305e9e602e ScrollBar: fixed jittery scrolling when in repeating mode (hold down mouse button) and smooth scrolling enabled 2023-08-23 09:51:57 +02:00
Karl Tauber
1ae31588c4 FlatSmoothScrollingTest: paint "temporary" scrollbar values in line chart using a lighter color 2023-08-23 09:51:56 +02:00
Karl Tauber
d64a8e93e1 FlatSmoothScrollingTest:
- use ChangeListener instead of AdjustmentListener because this is invoked before all other scrollbar listeners (which may run 20-30ms) and avoids a delay in the line chart
- use System.nanoTime() instead of System.currentTimeMillis() for better precision
- paint vertical lines in chart at every 200ms (was 1sec)
- print elapsed time between scrollbar events
2023-08-23 09:51:56 +02:00
Karl Tauber
e603bd81a1 FlatSmoothScrollingTest: added simple line chart that shows changes to scrollbar values 2023-08-23 09:51:56 +02:00
Karl Tauber
522ebb6fa3 FlatSmoothScrollingTest: allow enabling/disabling smooth scrolling with Alt+S without moving focus to checkbox; removed unused tree model 2023-08-23 09:51:56 +02:00
Karl Tauber
7a582c2d1f ScrollBar: fixed issue with updating thumb location (regressing since commit 2c3ef226692fa39b7e6eca3192d197c0b0753aa1) 2023-08-23 09:51:56 +02:00
Karl Tauber
762fe89867 FlatSmoothScrollingTest: added JTree, JTable, JTextArea, JTextPane and JEditorPane for testing smooth scrolling 2023-08-23 09:51:56 +02:00
Karl Tauber
1ebfe00f3c added system properties "flatlaf.animation" and "flatlaf.smoothScrolling" to disable all animations or smooth scrolling via command line (without modifying the application) 2023-08-23 09:51:56 +02:00
Karl Tauber
fdabca99b2 ScrollBar: fixed NPE when switching LaF while smooth scrolling animation is running (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
736305849a ScrollBar: set valueIsAdjusting property to true while smooth scrolling animation is running (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
889b5ea56a ScrollBar: fixed smooth scrolling issues when continuously scrolling (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
82514ccbfc Demo: added "Options > Smooth Scrolling" to menu (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
b67b701d1e ScrollPane: use smooth scrolling when rotating the mouse wheel (issue #50) 2023-08-23 09:51:56 +02:00
Karl Tauber
7f226a2742 ScrollBar: use smooth scrolling when clicking on track or on arrow button (issue #50) 2023-08-23 09:51:56 +02:00
467 changed files with 15570 additions and 56729 deletions

View File

@@ -7,11 +7,6 @@ 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

View File

@@ -1,32 +0,0 @@
# 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

View File

@@ -9,64 +9,50 @@ on:
- '*'
tags:
- '[0-9]*'
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-natives/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@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
strategy:
matrix:
# test against
# - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...)
# - latest Java version(s)
# - lastest Java version(s)
java:
- 8
- 11 # LTS
- 17 # LTS
- 19
toolchain: [""]
include:
- java: 17
toolchain: 20 # latest
- name: Build with Java 11 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=11
steps:
- uses: actions/checkout@v3
- name: Build with Java 17 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=17
- uses: gradle/wrapper-validation-action@v1
if: matrix.java == '8'
- name: Build with Java 21 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=21
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
distribution: temurin # Java 8, 11 and 17 are pre-installed on ubuntu-latest
cache: gradle
- name: Build with Java 25 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=25
- name: Check with Error Prone
if: matrix.java == '11'
run: ./gradlew errorprone clean -Dtoolchain=${{ matrix.toolchain }}
- name: Build with Gradle
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
# build with Java 8 for snapshot
- name: Build with Java 8
run: ./gradlew build
- name: Upload artifacts to GitHub Actions
uses: actions/upload-artifact@v4
- name: Upload artifacts
uses: actions/upload-artifact@v3
if: matrix.java == '11'
with:
name: FlatLaf-build-artifacts
path: |
@@ -76,15 +62,40 @@ jobs:
!**/*-sources.jar
- 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
snapshot:
runs-on: ubuntu-latest
needs: build
if: |
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) &&
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v3
- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Publish snapshot to oss.sonatype.org
run: ./gradlew publish :flatlaf-theme-editor:build -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
- name: Upload theme editor
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-theme-editor/build/libs"
remoteDir: "snapshots"
options: "--only-newer --no-recursion --verbose=1"
release:
@@ -96,31 +107,41 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup Java 21
uses: actions/setup-java@v4
- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 21
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- 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
- name: Release a new stable version to Maven Central
run: ./gradlew publish :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
- name: Install lftp
run: sudo apt-get -y install lftp
- name: Upload demo
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-demo/build/libs"
remoteDir: "."
options: "--only-newer --no-recursion --verbose=1"
- name: Upload demo and theme editor
run: >
lftp -c "set ftp:ssl-force true;
open -u ${{ secrets.FTP_USERNAME }},${{ secrets.FTP_PASSWORD }} ${{ secrets.FTP_SERVER }};
mput flatlaf-demo/build/libs/flatlaf-demo-*.jar;
mput flatlaf-theme-editor/build/libs/flatlaf-theme-editor-*.jar"
- name: Upload theme editor
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-theme-editor/build/libs"
remoteDir: "."
options: "--only-newer --no-recursion --verbose=1"

View File

@@ -1,40 +0,0 @@
# 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

View File

@@ -13,8 +13,6 @@ on:
- 'flatlaf-fonts/**'
- '.github/workflows/fonts.yml'
- 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs:
Fonts:
@@ -32,33 +30,31 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup Java 21
uses: actions/setup-java@v4
- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 21
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
cache: gradle
- name: Build with Gradle
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) ) != true
- name: Publish snapshot to Sonatype Central
- name: Publish snapshot to oss.sonatype.org
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:publish -Dorg.gradle.internal.publish.checksums.insecure=true
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
if: github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )
- name: Release a new stable version to Maven Central
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build :flatlaf-fonts-${{ matrix.font }}:publish -Prelease
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )

View File

@@ -13,141 +13,38 @@ on:
- 'flatlaf-natives/**'
- '.github/workflows/natives.yml'
- 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs:
Natives:
strategy:
matrix:
os:
- windows-latest
- macos-latest
- ubuntu-latest
- ubuntu-24.04-arm
- windows
- ubuntu
runs-on: ${{ matrix.os }}
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: gradle/actions/wrapper-validation@v4
- uses: gradle/wrapper-validation-action@v1
- name: apt update (Linux)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: sudo apt-get update
- name: install libxt-dev and libgtk-3-dev (Linux)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: sudo apt-get install libxt-dev libgtk-3-dev
# - name: Download libgtk-3.so for arm64 (Linux)
# if: matrix.os == 'ubuntu-latest'
# working-directory: flatlaf-natives/flatlaf-natives-linux/lib/aarch64
# run: |
# pwd
# ls -l /usr/lib/x86_64-linux-gnu/libgtk*
# wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
# ls -l
# ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
# tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
# rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
# ls -l
# - name: install g++-aarch64-linux-gnu (Linux)
# if: matrix.os == 'ubuntu-latest'
# run: sudo apt-get install g++-aarch64-linux-gnu
- name: Setup Java 21
uses: actions/setup-java@v4
- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 21
java-version: 11
distribution: temurin
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
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: Upload unsigned Windows DLLs for signing by SignPath.org
if: matrix.os == 'windows-latest' && github.repository == 'JFormDesigner/FlatLaf'
id: windows-unsigned
uses: actions/upload-artifact@v4
with:
name: FlatLaf-natives-windows-unsigned
path: flatlaf-natives/flatlaf-natives-windows/build/lib/main/release/**/*.dll
- name: Sign Windows DLLs using SignPath.org
if: matrix.os == 'windows-latest' && github.repository == 'JFormDesigner/FlatLaf'
uses: signpath/github-action-submit-signing-request@v2
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
project-slug: FlatLaf
signing-policy-slug: release-signing
artifact-configuration-slug: windows-dlls
github-artifact-id: ${{ steps.windows-unsigned.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: flatlaf-natives/flatlaf-natives-windows/build/lib/signed
- name: Copy signed Windows DLLs to flatlaf-core
if: matrix.os == 'windows-latest' && github.repository == 'JFormDesigner/FlatLaf'
shell: bash
run: |
SRC=flatlaf-natives/flatlaf-natives-windows/build/lib/signed
DEST=flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
cp $SRC/aarch64/flatlaf-natives-windows.dll $DEST/flatlaf-windows-arm64.dll
cp $SRC/x86/flatlaf-natives-windows.dll $DEST/flatlaf-windows-x86.dll
cp $SRC/x86-64/flatlaf-natives-windows.dll $DEST/flatlaf-windows-x86_64.dll
- 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 for upload step
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
uses: actions/upload-artifact@v3
with:
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: |
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/${{ env.artifactPattern }}
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
flatlaf-natives/flatlaf-natives-*/build

View File

@@ -1,39 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: PR Snapshots
on:
pull_request:
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
snapshot:
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: Publish PR snapshot to Sonatype Central
run: >
./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
-Pgithub.event.pull_request.number=${{ github.event.pull_request.number }}
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}

1
.gitignore vendored
View File

@@ -5,7 +5,6 @@ build/
.project
.settings/
.idea/
.consulo/
out/
*.iml
*.ipr

View File

@@ -1,542 +1,6 @@
FlatLaf Change Log
==================
## 3.7.1-SNAPSHOT
- ComboBox: Added UI property `ComboBox.buttonFocusedEditableBackground`. (issue
#1068)
- 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)
- 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
- If using `FlatLaf.registerCustomDefaultsSource( "com.myapp.themes" )` and
named Java modules, it is no longer necessary to add `opens com.myapp.themes;`
to `module-info.java`. (issue #1026)
- Extras: Made animated theme change (class `FlatAnimatedLafChange`) smoother.
#### Fixed bugs
- Tree and List: Fixed painting of rounded drop backgrounds. (issue #1023)
- Popup: Showing tooltip in inactive window brought that window to front (made
it active) and potentially hid the previously active window. (issue #1037)
- Popup: No longer reuse popup windows for menus to avoid immediately closing
dialogs on ChromeOS. (issue #1029)
- macOS: Fixed window "flashing" when switching from a light to a dark theme (or
vice versa). Especially when using animated theme changer (see
[FlatLaf Extras](flatlaf-extras)).
#### Incompatibilities
- FlatLaf properties files are now loaded using the UTF-8 character encoding
instead of ISO 8859-1. In usual properties files you will not notice any
difference because they use only ASCII characters, but if you've put localized
(non-English) texts (e.g. German umlauts) into your properties files, you need
to convert them to UTF-8. Properties files created with the FlatLaf Theme
Editor already use UTF-8, including in older versions. (issue #1031)
## 3.6.1
- Extras: Support JSVG 2.0.0. Minimum JSVG version is now 1.6.0. (issue #997)
- FlatLaf window decorations (Windows 10/11 only): Improved diagonal window
resizing on top-left and top-right window corners. Top window resize area now
also covers iconify/maximize/close buttons. (issue #1015)
- ToggleButton: Styling `selectedForeground` did not work if `foreground` is
also styled. (issue #1017)
- JideSplitButton: Fixed updating popup when switching theme. (issue #1000)
- IntelliJ Themes: Fixed logging false errors when loading 3rd party
`.theme.json` files. (issue #990)
- Linux: Popups appeared in wrong position on multi-screen setup if primary
display is located below or right to secondary display. (see
[NetBeans issue #8532](https://github.com/apache/netbeans/issues/8532))
- macOS: Fixed popup flickering after theme change. (issue #1009)
- macOS with JetBrains Runtime: Fixed sometimes empty popups. (issue #1019)
## 3.6
#### New features and improvements
- macOS: Re-enabled rounded popup border (see PR #772) on macOS 14.4+ (was
disabled in 3.5.x).
- Increased contrast of text for better readability: (PR #972; issue #762)
- In **FlatLaf Dark**, **FlatLaf Darcula** and many dark IntelliJ themes, made
all text colors brighter.
- In **FlatLaf Light**, **FlatLaf IntelliJ** and many light IntelliJ themes,
made disabled text colors slightly darker.
- In **FlatLaf macOS Light**, made disabled text colors darker.
- In **FlatLaf macOS Dark**, made text colors of "default" button and selected
ToggleButton lighter.
- CheckBox: Support styling indeterminate state of
[tri-state check boxes](https://www.javadoc.io/doc/com.formdev/flatlaf-extras/latest/com/formdev/flatlaf/extras/components/FlatTriStateCheckBox.html).
(PR #936; issue #919)
- List: Support for alternate row highlighting. (PR #939)
- Tree: Support for alternate row highlighting. (PR #903)
- Tree: Support wide cell renderer. (issue #922)
- ScrollBar: Use rounded thumb also on Windows (as on macOS and Linux) and made
thumb slightly darker/lighter. (issue #918)
- Extras: `FlatSVGIcon` color filters now can access painting component to
implement component state based color mappings. (issue #906)
- Linux:
- Rounded iconify/maximize/close buttons if using FlatLaf window decorations.
(PR #971)
- Added `libflatlaf-linux-arm64.so` for Linux on ARM64. (issue #899)
- Use X11 window manager events to resize window, if FlatLaf window
decorations are enabled. This gives FlatLaf windows a more "native" feeling.
(issue #866)
- IntelliJ Themes:
- Updated to latest versions and fixed various issues.
- Support customizing through properties files. (issue #824)
- SwingX: Support `JXTipOfTheDay` component. (issue #980)
- Support key prefixes for Linux desktop environments (e.g. `[gnome]`, `[kde]`
or `[xfce]`) in properties files. (issue #974)
- Support custom key prefixes (e.g. `[win10]` or `[test]`) in properties files.
(issue #649)
- Support multi-prefixed keys (e.g. `[dark][gnome]TitlePane.buttonBackground`).
The value is only used if all prefixes match current platform/theme.
- Support new component border color to indicate success state (set client
property `JComponent.outline` to `success`). (PR #982, issue #945)
- Fonts: Updated **Inter** to
[v4.1](https://github.com/rsms/inter/releases/tag/v4.1).
#### Fixed bugs
- Button: Fixed background and foreground colors for `borderless` and
`toolBarButton` style default buttons (`JButton.isDefaultButton()` is `true`).
(issue #947)
- FileChooser: Improved performance when navigating to large directories with
thousands of files. (issue #953)
- PopupFactory: Fixed NPE on Windows 10 when `owner` is `null`. (issue #952)
- Popup: On Windows 10, drop shadow of heavy-weight popup was not updated if
popup moved/resized. (issue #942)
- FlatLaf window decorations:
- Minimize and maximize icons were not shown for custom scale factors less
than 100% (e.g. `-Dflatlaf.uiScale=75%`). (issue #951)
- Linux: Fixed occasional maximizing of window when single-clicking the
window's title bar. (issue #637)
- Styling: MigLayout visual padding was not updated after applying style to
Button, ComboBox, Spinner, TextField (and subclasses) and ToggleButton. (issue
#965)
- Linux: Popups (menus and combobox lists) were not hidden when window is moved,
resized, maximized, restored, iconified or switched to another window. (issue
#962)
- Fixed loading FlatLaf UI delegate classes when using FlatLaf in special
application where multiple class loaders are involved. E.g. in Eclipse plugin
or in LibreOffice extension. (issues #955 and #851)
- HTML: Fixed rendering of `<hr noshade>` in dark themes. (issue #932)
- TextComponents: `selectAllOnFocusPolicy` related changes:
- No longer select all text if selection (or caret position) was changed by
application and `selectAllOnFocusPolicy` is `once` (the default). (issue
#983)
- FormattedTextField and Spinner: `selectAllOnFocusPolicy = once` behaves now
as `always` (was `never` before), which means that all text is selected when
component gains focus. This is because of special behavior of
`JFormattedTextField` that did not allow implementation of `once`.
- Client property `JTextField.selectAllOnFocusPolicy` now also works on
(editable) `JComboBox` and on `JSpinner`.
- Added client property `JTextField.selectAllOnMouseClick` to override UI
property `TextComponent.selectAllOnMouseClick`. (issue #961)
- For `selectAllOnMouseClick = true`, clicking with the mouse into the text
field, to focus it, now always selects all text, even if
`selectAllOnFocusPolicy` is `once`.
#### Incompatibilities
- IntelliJ Themes:
- Theme prefix in `IntelliJTheme$ThemeLaf.properties` changed from
`[theme-name]` to `{theme-name}`.
- Renamed classes in package
`com.formdev.flatlaf.intellijthemes.materialthemeuilite` from `Flat<theme>`
to `FlatMT<theme>`.
- Removed `Gruvbox Dark Medium` and `Gruvbox Dark Soft` themes.
- Prefixed keys in properties files (e.g. `[dark]Button.background` or
`[win]Button.arc`) are now handled earlier than before. In previous versions,
prefixed keys always had higher priority than unprefixed keys and did always
overwrite unprefixed keys. Now prefixed keys are handled in same order as
unprefixed keys, which means that if a key is prefixed and unprefixed (e.g.
`[win]Button.arc` and `Button.arc`), the one which is last specified in
properties file is used.\
Following worked in previous versions, but now `Button.arc` is always `6`:
~~~properties
[win]Button.arc = 12
Button.arc = 6
~~~
This works in new (and old) versions:
~~~properties
Button.arc = 6
[win]Button.arc = 12
~~~
## 3.5.4
#### Fixed bugs
- HTML: Fixed NPE when using HTML text on a component with `null` font. (issue
#930; PR #931; regression in 3.5)
- Linux: Fixed NPE when using FlatLaf window decorations and switching theme.
(issue #933; regression in 3.5.3)
## 3.5.3
#### Fixed bugs
- HTML: Fixed wrong rendering if HTML text contains `<style>` tag with
attributes (e.g. `<style type='text/css'>`). (issue #905; regression in 3.5.1)
- FlatLaf window decorations:
- Windows: Fixed possible deadlock with TabbedPane in window title area in
"full window content" mode. (issue #909)
- Windows: Fixed wrong layout in maximized frame after changing screen scale
factor. (issue #904)
- Linux: Fixed continuous cursor toggling between resize and standard cursor
when resizing window. (issue #907)
- Fixed sometimes broken window moving with SplitPane in window title area in
"full window content" mode. (issue #926)
- Popup: On Windows 10, fixed misplaced popup drop shadow. (issue #911;
regression in 3.5)
- Popup: Fixed NPE if `GraphicsConfiguration` is `null` on Windows. (issue #921)
- Theme Editor: Fixed using color picker on secondary screen.
- Fixed detection of Windows 11 if custom exe launcher does not specify Windows
10+ compatibility in application manifest. (issue #916)
- Linux: Fixed slightly different font size (or letter width) used to paint HTML
text when default font family is _Cantarell_ (e.g. on Fedora). (issue #912)
#### Other Changes
- Class `FlatPropertiesLaf` now supports FlatLaf macOS themes as base themes.
## 3.5.2
#### Fixed bugs
- Windows: Fixed repaint issues (ghosting) on some systems (probably depending
on graphics card/driver). This is done by setting Java system property
`sun.java2d.d3d.onscreen` to `false` (but only if `sun.java2d.d3d.onscreen`,
`sun.java2d.d3d` and `sun.java2d.noddraw` are not yet set), which disables
usage of Windows Direct3D (DirectX) onscreen surfaces. Component rendering
still uses Direct3D. (issue #887)
- FlatLaf window decorations:
- Iconify/maximize/close buttons did not fill whole title bar height, if some
custom component in menu bar increases title bar height. (issue #897)
- Windows: Fixed possible application freeze when using custom component that
overrides `Component.contains(int x, int y)` and invokes
`SwingUtilities.convertPoint()` (or similar) from the overridden method.
(issue #878)
- TextComponents: Fixed too fast scrolling in multi-line text components when
using touchpads (e.g. on macOS). (issue #892)
- ToolBar: Fixed endless loop if button in Toolbar has focus and is made
invisible. (issue #884)
#### Other Changes
- FlatLaf window decorations: Added client property `JRootPane.titleBarHeight`
to allow specifying a (larger) preferred height for the title bar. (issue
#897)
- Added system property `flatlaf.useRoundedPopupBorder` to allow disabling
native rounded popup borders on Windows 11 and macOS. On macOS 14.4+, where
rounded popup borders are disabled since FlatLaf 3.5 because of occasional
problems, you can use this to enable rounded popup borders (at your risk).
## 3.5.1
#### Fixed bugs
- HTML: Fixed occasional cutoff wrapped text when using multi-line text in HTML
tags `<h1>`...`<h6>`, `<code>`, `<kbd>`, `<big>`, `<small>` or `<samp>`.
(issue #873; regression in 3.5)
- Popup: Fixed `UnsupportedOperationException: PERPIXEL_TRANSLUCENT translucency
is not supported` exception on Haiku OS when showing popup (partly) outside of
window. (issue #869)
- HiDPI: Fixed occasional wrong repaint areas when using
`HiDPIUtils.installHiDPIRepaintManager()`. (see PR #864)
- Added system property `flatlaf.useSubMenuSafeTriangle` to allow disabling
submenu safe triangle (PR #490) for
[SWTSwing](https://github.com/Chrriis/SWTSwing). (issue #870)
## 3.5
#### New features and improvements
- Table: Support rounded selection. (PR #856)
- Button and ToggleButton: Added border colors for pressed and selected states.
(issue #848)
- Label: Support painting background with rounded corners. (issue #842)
- Popup: Fixed flicker of popups (e.g. tooltips) while they are moving (e.g.
following mouse pointer). (issues #832 and #672)
- FileChooser: Wrap shortcuts in scroll pane. (issue #828)
- Theme Editor: On macOS, use larger window title bar. (PR #779)
#### Fixed bugs
- macOS: Disabled rounded popup border (see PR #772) on macOS 14.4+ because it
may freeze the application and crash the macOS WindowServer process (reports
vary from Finder restarts to OS restarts). This is a temporary change until a
solution is found. See NetBeans issues
[apache/netbeans#7560](https://github.com/apache/netbeans/issues/7560#issuecomment-2226439215)
and
[apache/netbeans#6647](https://github.com/apache/netbeans/issues/6647#issuecomment-2070124442).
- FlatLaf window decorations: Window top border on Windows 10 in "full window
content" mode was not fully repainted when activating or deactivating window.
(issue #809)
- Button and ToggleButton: UI properties `[Toggle]Button.selectedForeground` and
`[Toggle]Button.pressedForeground` did not work for HTML text. (issue #848)
- HTML: Fixed font sizes for HTML tags `<h1>`...`<h6>`, `<code>`, `<kbd>`,
`<big>`, `<small>` and `<samp>` in HTML text for components Button, CheckBox,
RadioButton, MenuItem (and subclasses), JideLabel, JideButton, JXBusyLabel and
JXHyperlink. Also fixed for Label and ToolTip if using Java 11+.
- ScrollPane: Fixed/improved border painting at 125% - 175% scaling to avoid
different border thicknesses. (issue #743)
- Table: Fixed painting of alternating rows below table if auto-resize mode is
`JTable.AUTO_RESIZE_OFF` and table width is smaller than scroll pane (was not
updated when table width changed and was painted on wrong side in
right-to-left component orientation).
- Theme Editor: Fixed occasional empty window on startup on macOS.
- FlatLaf window decorations: Fixed black line sometimes painted on top of
(native) window border on Windows 11. (issue #852)
- HiDPI: Fixed incomplete component paintings at 125% or 175% scaling on Windows
where sometimes a 1px wide area at the right or bottom component edge is not
repainted. E.g. ScrollPane focus indicator border. (issues #860 and #582)
#### Incompatibilities
- ProgressBar: Log warning (including stack trace) when uninstalling
indeterminate progress bar UI or using `JProgressBar.setIndeterminate(false)`
not on AWT thread, because this may throw NPE in `FlatProgressBarUI.paint()`.
(issues #841 and #830)
- Panel: Rounded background of panel with rounded corners is now painted even if
panel is not opaque. (issue #840)
## 3.4.1
#### Fixed bugs
- SplitPane: Update divider when client property `JSplitPane.expandableSide`
changed.
- TabbedPane: Fixed swapped back and forward scroll buttons when using
`TabbedPane.scrollButtonsPlacement = trailing` (regression in FlatLaf 3.3).
- Fixed missing window top border on Windows 10 in "full window content" mode.
(issue #809)
- Extras:
- `FlatSVGIcon` color filters now support linear gradients. (PR #817)
- `FlatSVGIcon`: Use log level `CONFIG` instead of `SEVERE` and allow
disabling logging. (issue #823)
- Added support for `JSplitPane.expandableSide` client property to
`FlatSplitPane`.
- Native libraries: Added API version check to test whether native library
matches the JAR (bad builds could e.g. ship a newer JAR with an older
incompatible native library) and to test whether native methods can be invoked
(some security software allows loading native library but blocks method
invocation).
- macOS: Fixed crash when running in WebSwing. (issue #826; regression in 3.4)
#### Incompatibilities
- File names of custom properties files for nested Laf classes now must include
name of enclosing class name. E.g. nested Laf class `IntelliJTheme.ThemeLaf`
used `ThemeLaf.properties` in previous versions, but now needs to be named
`IntelliJTheme$ThemeLaf.properties`.
## 3.4
#### New features and improvements
- FlatLaf window decorations (Windows 10/11 and Linux): Support "full window
content" mode, which allows you to extend the content into the window title
bar. (PR #801)
- macOS: Support larger window title bar close/minimize/zoom buttons spacing in
[full window content](https://www.formdev.com/flatlaf/macos/#full_window_content)
mode and introduced "buttons placeholder". (PR #779)
- Native libraries:
- System property `flatlaf.nativeLibraryPath` now supports loading native
libraries named the same as on Maven central.
- Published `flatlaf-<version>-no-natives.jar` to Maven Central. This JAR is
equal to `flatlaf-<version>.jar`, except that it does not contain the
FlatLaf native libraries. The Maven "classifier" to use this JAR is
`no-natives`. You need to distribute the FlatLaf native libraries with your
application.
See https://www.formdev.com/flatlaf/native-libraries/ for more details.
- Improved log messages for loading fails.
- Fonts: Updated **Inter** to
[v4.0](https://github.com/rsms/inter/releases/tag/v4.0).
- Table: Select all text in cell editor when starting editing using `F2` key on
Windows or Linux. (issue #652)
#### Fixed bugs
- macOS: Setting window background (of undecorated window) to translucent color
(alpha < 255) did not show the window translucent. (issue #705)
- JIDE CommandMenuBar: Fixed `ClassCastException` when JIDE command bar displays
`JideMenu` in popup. (PR #794)
## 3.3
#### New features and improvements
- macOS (10.14+): Popups (`JPopupMenu`, `JComboBox`, `JToolTip`, etc.) now use
native macOS rounded borders. (PR #772; issue #715)
- Native libraries: Added `libflatlaf-macos-arm64.dylib` and
`libflatlaf-macos-x86_64.dylib`. See also
https://www.formdev.com/flatlaf/native-libraries/.
- ScrollPane: Support rounded border. (PR #713)
- SplitPane: Support divider hover and pressed background colors. (PR #788)
- TabbedPane: Support vertical tabs. (PR #758, issue #633)
- TabbedPane: Paint rounded tab area background for rounded cards. (issue #717)
- ToolBar: Added styling properties `separatorWidth` and `separatorColor`.
#### Fixed bugs
- Button and ToggleButton: Selected buttons did not use explicitly set
foreground color. (issue #756)
- FileChooser: Catch NPE in Java 21 when getting icon for `.exe` files that use
default Windows exe icon. (see
[JDK-8320692](https://bugs.openjdk.org/browse/JDK-8320692))
- OptionPane: Fixed styling custom panel background in `JOptionPane`. (issue
#761)
- ScrollPane: Styling ScrollPane border properties did not work if view
component is a Table.
- Table:
- Switching theme looses table grid and intercell spacing. (issues #733 and
#750)
- Fixed background of `boolean` columns when using alternating row colors.
(issue #780)
- Fixed border arc of components in complex table cell editors. (issue #786)
- TableHeader:
- No longer temporary replace header cell renderer while painting. This avoids
a `StackOverflowError` in case that custom renderer does this too. (see
[NetBeans issue #6835](https://github.com/apache/netbeans/issues/6835)) This
also improves compatibility with custom table header implementations.
- Header cell renderer background/foreground colors were not restored after
hover if renderer uses `null` for background/foreground. (PR #790)
- TabbedPane:
- Avoid unnecessary repainting whole tabbed pane content area when layouting
leading/trailing components.
- Avoid unnecessary repainting of selected tab on temporary changes.
- Fixed "endless" layouting and repainting when using nested tabbed panes (top
and bottom tab placement) and RSyntaxTextArea (with enabled line-wrapping)
as tab content. (see
[jadx issue #2030](https://github.com/skylot/jadx/issues/2030))
- Fixed broken rendering after resizing window to minimum size and then
increasing size again. (issue #767)
#### Incompatibilities
- Removed support for JetBrains custom decorations, which required
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/wiki) (JBR)
8 or 11. It did not work for JBR 17. System property
`flatlaf.useJetBrainsCustomDecorations` is now ignored. **Note**: FlatLaf
window decorations continue to work with JBR.
## 3.2.5
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Windows 10. (issue #753;
regression in 3.2.1 in fix for #626)
## 3.2.4
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Linux with Wayland and Java 21.
(issue #752; regression in 3.2.3)
## 3.2.3
#### Fixed bugs
- Popup: Popups that request focus were not shown on Linux with Wayland and Java 21.
(issue #752)
## 3.2.2
#### Fixed bugs
- Button: Fixed painting icon and text at wrong location when using HTML text,
left/right vertical alignment and running in Java 19+. (issue #746)
- CheckBox and RadioButton: Fixed cut off right side when border is removed and
horizontal alignment is set to `right`. (issue #734)
- TabbedPane: Fixed NPE when using focusable component as tab component and
switching theme. (issue #745)
## 3.2.1
#### Fixed bugs
- Fixed memory leak in
`MultiResolutionImageSupport.create(int,Dimension[],Function<Dimension,Image>)`,
which caches images created by the producer function. Used by
`FlatSVGIcon.getImage()` and `FlatSVGUtils.createWindowIconImages()`. If you
use one of these methods, it is **strongly recommended** to upgrade to this
version, because if the returned image is larger and painted very often it may
result in an out-of-memory situation. (issue #726)
- FileChooser: Fixed occasional NPE in `FlatShortcutsPanel` on Windows. (issue
#718)
- TextField: Fixed placeholder text painting, which did not respect horizontal
alignment property of `JTextField`. (issue #721)
- Popup: Fixed drop shadow if popup overlaps a heavyweight component. (Windows
10 only; issue #626)
## 3.2
#### New features and improvements
@@ -659,6 +123,7 @@ FlatLaf Change Log
- Windows DLLs are now digitally signed with FormDev Software GmbH
certificate.
#### Fixed bugs
- FlatLaf window decorations:
@@ -1194,7 +659,7 @@ FlatLaf Change Log
- Native window decorations (Windows 10 only):
- Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357)
- When window is initially shown, fill background with window background color
(instead of white), which avoids flickering in dark themes. (issue #339)
(instead of white), which avoids flickering in dark themes. (issue 339)
- When resizing a window at the right/bottom edge, then first fill the new
space with the window background color (instead of black) before the layout
is updated.

144
README.md
View File

@@ -33,24 +33,14 @@ FlatLaf can use 3rd party themes created for IntelliJ Platform (see
Sponsors
--------
### Current Sponsors
<a href="https://www.soptim.de/"><img src="https://www.formdev.com/flatlaf/sponsor/soptim.svg" width="200" alt="SOPTIM" title="SOPTIM - your expert in software solutions for the energy industry"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://exocharts.com/"><img src="https://www.formdev.com/flatlaf/sponsor/Exocharts.png" width="200" alt="Exocharts" title="Exocharts - Professional Grade OrderFlow"></a>
<!-- [![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/) -->
[Become a Sponsor](https://www.formdev.com/flatlaf/sponsor/)
### Previous Sponsors
<a href="https://www.ej-technologies.com/"><img src="https://www.formdev.com/flatlaf/sponsor/ej-technologies.png" width="200" alt="ej-technologies" title="ej-technologies - Java APM, Java Profiler, Java Installer Builder"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://www.dbvis.com/"><img src="https://www.formdev.com/flatlaf/sponsor/dbvisualizer.svg" width="200" alt="DbVisualizer" title="DbVisualizer - SQL Client and Editor"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://www.dscsag.com/"><img src="https://www.formdev.com/flatlaf/sponsor/DSC.png" height="48" alt="DSC Software AG" title="DSC Software AG - Your Companion for Integrative PLM"></a>
[Become a Sponsor](https://www.formdev.com/flatlaf/sponsor/)
Demo
----
@@ -72,32 +62,23 @@ build script:
artifactId: flatlaf
version: (see button below)
Otherwise, download `flatlaf-<version>.jar` here:
Otherwise download `flatlaf-<version>.jar` here:
[![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf)
- See
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
for instructions on how to redistribute FlatLaf native libraries with your
application.
- Windows DLLs: Free code signing provided by
[SignPath.io](https://about.signpath.io/), certificate by
[SignPath Foundation](https://signpath.org/).
- 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.
See also
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
for instructions on how to redistribute FlatLaf native libraries with your
application.
### Snapshots
FlatLaf snapshot binaries are available on
[Sonatype Central](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/formdev/flatlaf/).
[Sonatype OSSRH](https://oss.sonatype.org/content/repositories/snapshots/com/formdev/flatlaf/).
To access the latest snapshot, change the FlatLaf version in your dependencies
to `<version>-SNAPSHOT` (e.g. `3.7-SNAPSHOT`) and add the repository
`https://central.sonatype.com/repository/maven-snapshots/` to your build (see
to `<version>-SNAPSHOT` (e.g. `0.27-SNAPSHOT`) and add the repository
`https://oss.sonatype.org/content/repositories/snapshots/` to your build (see
[Maven](https://maven.apache.org/guides/mini/guide-multiple-repositories.html)
and
[Gradle](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:declaring_custom_repository)
@@ -160,6 +141,7 @@ details and downloads.
Buzz
----
- [What others say about FlatLaf on Twitter](https://twitter.com/search?f=live&q=flatlaf)
- [FlatLaf 3.1 (and 3.0) announcement on Reddit](https://www.reddit.com/r/java/comments/12xgrsu/flatlaf_31_and_30_swing_look_and_feel/)
- [FlatLaf 1.0 announcement on Reddit](https://www.reddit.com/r/java/comments/lsbcwe/flatlaf_10_swing_look_and_feel/)
- [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/)
@@ -195,18 +177,11 @@ Applications using FlatLaf
relational data browsing tool
- ![Hot](images/hot.svg) [MagicPlot](https://magicplot.com/) (**commercial**) -
Software for nonlinear fitting, plotting and data analysis
- [Constellation](https://www.constellation-app.com/) - Data Visualization and
Analytics (based on NetBeans platform)
- [Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
- ![New](images/new.svg) [Constellation](https://www.constellation-app.com/) -
Data Visualization and Analytics (based on NetBeans platform)
- ![New](images/new.svg)
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
client
- ![New](images/new.svg)
[RedisFront](https://github.com/dromara/RedisFront/blob/master/README_EN.md) -
Cross-platform redis GUI
- ![New](images/new.svg)
[Zettelkasten](https://github.com/Zettelkasten-Team/Zettelkasten) - knowledge
management tool
- ![New](images/new.svg) [QStudio](https://www.timestored.com/qstudio/) - free
SQL editor
### Security
@@ -215,9 +190,11 @@ Applications using FlatLaf
- ![Hot](images/hot.svg)
[Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro)
(**commercial**) - the leading software for web security testing
- [Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
- ![New](images/new.svg)
[Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
reverse engineering (SRE) framework
- [jadx](https://github.com/skylot/jadx) - Dex to Java decompiler
- ![New](images/new.svg) [jadx](https://github.com/skylot/jadx) - Dex to Java
decompiler
- [BurpCustomizer](https://github.com/CoreyD97/BurpCustomizer) - adds more
FlatLaf themes to Burp Suite
- [Total Validator](https://www.totalvalidator.com/) (**commercial**) - checks
@@ -229,26 +206,19 @@ Applications using FlatLaf
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib)
- [KeyStore Explorer](https://keystore-explorer.org/)
- [muCommander](https://github.com/mucommander/mucommander) - lightweight
- ![New](images/new.svg)
[muCommander](https://github.com/mucommander/mucommander) - lightweight
cross-platform file manager
- [Guiffy](https://www.guiffy.com/) (**commercial**) - advanced cross-platform
Diff/Merge
- [HashGarten](https://github.com/jonelo/HashGarten) - cross-platform Swing GUI
for Jacksum
- ![New](images/new.svg) [Guiffy](https://www.guiffy.com/) (**commercial**) -
advanced cross-platform Diff/Merge
- ![New](images/new.svg) [HashGarten](https://github.com/jonelo/HashGarten) -
cross-platform Swing GUI for Jacksum
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
IDE for Pseudo-Assembler
- [Linotte](https://github.com/cpc6128/LangageLinotte) - French programming
language created to learn programming
- [lsfusion platform](https://github.com/lsfusion/platform) - information
systems development platform
- ![New](images/new.svg) [Consulo](https://github.com/consulo/consulo) - open
source cross-platform multi-language IDE (Java, .NET, JS, etc)
- [Convertigo](https://github.com/convertigo/convertigo) - low code & no code
mobile & web platform
- ![New](images/new.svg) [EduMIPS64](https://github.com/EduMIPS64/edumips64) -
visual MIPS64 CPU simulator
- ![New](images/new.svg) [Launch4j](https://launch4j.sourceforge.net/) -
cross-platform Java executable wrapper
### Electrical
@@ -256,11 +226,6 @@ Applications using FlatLaf
designing, simulating and explaining digital circuits
- [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) -
Digital logic design tool and simulator
- ![New](images/new.svg) [OpenPnP](https://github.com/openpnp/openpnp) - SMT
Pick and Place Hardware and Software
- ![New](images/new.svg)
[TrainControl](https://github.com/bob123456678/TrainControl) - control Marklin
/ Trix / DCC digital model train layout
- [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) -
for plotters, especially the wall-hanging polargraph
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI
@@ -275,10 +240,8 @@ Applications using FlatLaf
- ![Hot](images/hot.svg) [jAlbum](https://jalbum.net/) (**commercial**) -
creates photo album websites
- [MediathekView](https://mediathekview.de/) - search in media libraries of
various German broadcasters
- ![New](images/new.svg) [Pixelitor](https://github.com/lbalazscs/Pixelitor) -
image editor
- ![New](images/new.svg) [MediathekView](https://mediathekview.de/) - search in
media libraries of various German broadcasters
- [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit
sequences
- [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a
@@ -294,31 +257,19 @@ Applications using FlatLaf
from any webnovel and lightnovel site
- [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with
ease
- ![New](images/new.svg) [Nortantis](https://jandjheydorn.com/nortantis) -
fantasy map generator and editor
### Modelling / Planning
### Modelling
- ![New](images/new.svg) [OpenRocket](https://github.com/openrocket/openrocket) -
model-rocketry aerodynamics and trajectory simulation software
- ![New](images/new.svg)
[Warteschlangensimulator](https://github.com/A-Herzog/Warteschlangensimulator) -
discrete-event stochastic simulator
- ![New](images/new.svg) [Gephi](https://github.com/gephi/gephi) - the Open
Graph Viz Platform
- [Astah](https://astah.net/) (**commercial**) - create UML, ER Diagram,
Flowchart, Data Flow Diagram, Requirement Diagram, SysML diagrams and more
- ![New](images/new.svg) [Astah](https://astah.net/) (**commercial**) - create
UML, ER Diagram, Flowchart, Data Flow Diagram, Requirement Diagram, SysML
diagrams and more
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
Application System
- ![New](images/new.svg) [StarPlan](https://www.progotec.de/) (**commercial**) -
die Stundenplan Software für Bildungseinrichtungen
- ![New](images/new.svg) [SSPlot](https://github.com/babaissarkar/ssplot) -
plotting utility for plotting CSV data
### Documents
- [Big Faceless (BFO) PDF Viewer](https://bfo.com/) (**commercial**) - Swing PDF
Viewer
- ![New](images/new.svg) [Big Faceless (BFO) PDF Viewer](https://bfo.com/)
(**commercial**) - Swing PDF Viewer
- [PDF Studio](https://www.qoppa.com/pdfstudio/) (**commercial**) - create,
review and edit PDF documents
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) (**commercial**)
@@ -336,9 +287,6 @@ Applications using FlatLaf
### Business / Legal
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[Lisheane ERP](https://www.lisheane.ch/) (**commercial**) - backoffice
applikation
- ![Sponsor](images/sponsor.svg)
[j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
- ![Sponsor](images/sponsor.svg) [Jeyla Studio](https://www.jeylastudio.com/) -
@@ -355,20 +303,17 @@ Applications using FlatLaf
### Messaging
- [Spark](https://github.com/igniterealtime/Spark) - cross-platform IM client
optimized for businesses and organizations
- [Chatty](https://github.com/chatty/chatty) - Twitch Chat Client
- ![New](images/new.svg) [Spark](https://github.com/igniterealtime/Spark) -
cross-platform IM client optimized for businesses and organizations
- ![New](images/new.svg) [Chatty](https://github.com/chatty/chatty) - Twitch
Chat Client
### Gaming
- ![Sponsor](images/sponsor.svg) [BGBlitz](https://www.bgblitz.com/)
(**commercial**) - professional Backgammon
- ![New](images/new.svg) [josé](https://peteschaefer.github.io/jose/) - a
graphical chess tool
- ![New](images/new.svg) [MCreator](https://github.com/MCreator/MCreator) - make
Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons, and data packs
- [MapTool](https://github.com/RPTools/maptool) - virtual Tabletop for playing
role-playing games
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon
- ![New](images/new.svg) [MapTool](https://github.com/RPTools/maptool) - virtual
Tabletop for playing role-playing games
- [MegaMek](https://github.com/MegaMek/megamek),
[MegaMekLab](https://github.com/MegaMek/megameklab) and
[MekHQ](https://github.com/MegaMek/mekhq) - a sci-fi tabletop BattleTech
@@ -380,7 +325,8 @@ Applications using FlatLaf
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
OSHI, to view information about the system and hardware
- [Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
- ![New](images/new.svg)
[Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
GUI for monitoring and managing various aspects of a Linux system
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
@@ -389,8 +335,6 @@ Applications using FlatLaf
easy
- [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb
and fastboot commands easier to use
- ![New](images/new.svg) [Termora](https://github.com/TermoraDev/termora) -
Terminal emulator and SSH client
### Miscellaneous

View File

@@ -14,30 +14,21 @@
* 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
// for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT'
val pullRequestNumber = findProperty( "github.event.pull_request.number" )
if( pullRequestNumber != null )
version = "PR-${pullRequestNumber}-SNAPSHOT"
// apply version to all subprojects
subprojects {
allprojects {
version = rootProject.version
repositories {
mavenCentral()
}
}
// initialize toolchain version (default is Java 8)
val toolchainJavaVersion: String by extra {
System.getProperty( "toolchain", "8" )
}
// check required Java version
if( JavaVersion.current() < JavaVersion.VERSION_1_8 )
throw RuntimeException( "Java 8 or later required (running ${System.getProperty( "java.version" )})" )
// log version, Gradle and Java versions
println()
@@ -45,20 +36,17 @@ println( "----------------------------------------------------------------------
println( "FlatLaf Version: ${version}" )
println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" )
println( "Java ${System.getProperty( "java.version" )}" )
println( "Java toolchain ${toolchainJavaVersion}" )
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() )
println( "Java toolchain ${toolchainJavaVersion}" )
println()
plugins {
alias( libs.plugins.gradle.nexus.publish.plugin )
alias( libs.plugins.errorprone ) apply false
}
allprojects {
repositories {
mavenCentral()
}
tasks {
withType<JavaCompile>().configureEach {
sourceCompatibility = "1.8"
@@ -82,10 +70,6 @@ allprojects {
}
}
withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = true
}
withType<Javadoc>().configureEach {
options {
this as StandardJavadocDocletOptions
@@ -98,23 +82,6 @@ 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" )
}
}
@@ -166,33 +133,7 @@ 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 ) )
} )
}
}
}
}
}
nexusPublishing {
repositories {
sonatype {
// see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/
nexusUrl = uri( "https://ossrh-staging-api.central.sonatype.com/service/local/" )
snapshotRepositoryUrl = uri( "https://central.sonatype.com/repository/maven-snapshots/" )
// get from gradle.properties
val sonatypeUsername: String? by project
val sonatypePassword: String? by project
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
}
}
}

View File

@@ -45,7 +45,7 @@ public class ReorderJarEntries
// 1st pass: copy .properties files
copyFiles( zipOutStream, jarFile, name -> name.endsWith( ".properties" ) );
// 2nd pass: copy other files
// 2st pass: copy other files
copyFiles( zipOutStream, jarFile, name -> !name.endsWith( ".properties" ) );
}

View File

@@ -14,8 +14,6 @@
* limitations under the License.
*/
import org.gradle.kotlin.dsl.support.serviceOf
plugins {
`cpp-library`
}
@@ -29,7 +27,7 @@ library {
}
with( linkTask.get() ) {
if( name.contains( "Release" ) )
debuggable = false
debuggable.set( false )
}
}
}
@@ -39,14 +37,9 @@ tasks {
doFirst {
println( "Used Tool Chain:" )
println( " - ${toolChain.get()}" )
}
if( !project.gradle.serviceOf<BuildFeatures>().configurationCache.active.get() ) {
doFirst {
println( "Available Tool Chains:" )
toolChains.forEach {
println( " - $it" )
}
println( "Available Tool Chains:" )
toolChains.forEach {
println( " - $it" )
}
}
}

View File

@@ -18,14 +18,7 @@ plugins {
java
}
// 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 ) {
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets {
create( "java9" ) {
java {
@@ -42,13 +35,6 @@ if( !exclude ) {
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 {

View File

@@ -26,7 +26,7 @@ tasks {
// depend on :flatlaf-core:compileJava because it generates the JNI headers
dependsOn( ":flatlaf-core:compileJava" )
from( project( ":flatlaf-core" ).layout.buildDirectory.dir( "generated/jni-headers" ) )
from( project( ":flatlaf-core" ).buildDir.resolve( "generated/jni-headers" ) )
into( "src/main/headers" )
include( extension.headers )
filter<org.apache.tools.ant.filters.FixCrLfFilter>(

View File

@@ -29,14 +29,7 @@ plugins {
java
}
// 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 ) {
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets {
create( "module-info" ) {
java {
@@ -45,11 +38,10 @@ if( !exclude ) {
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
}
}
@@ -66,13 +58,6 @@ if( !exclude ) {
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 {

View File

@@ -15,7 +15,7 @@
*/
open class NativeArtifact( val fileName: String, val classifier: String, val extension: String ) {}
open class NativeArtifact( val fileName: String, val classifier: String, val type: String ) {}
open class PublishExtension {
var artifactId: String? = null
@@ -44,47 +44,66 @@ publishing {
pom {
afterEvaluate {
this@pom.name = extension.name
this@pom.description = extension.description
this@pom.name.set( extension.name )
this@pom.description.set( extension.description )
}
url = "https://github.com/JFormDesigner/FlatLaf"
url.set( "https://github.com/JFormDesigner/FlatLaf" )
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
name.set( "The Apache License, Version 2.0" )
url.set( "https://www.apache.org/licenses/LICENSE-2.0.txt" )
}
}
developers {
developer {
name = "Karl Tauber"
organization = "FormDev Software GmbH"
organizationUrl = "https://www.formdev.com/"
name.set( "Karl Tauber" )
organization.set( "FormDev Software GmbH" )
organizationUrl.set( "https://www.formdev.com/" )
}
}
scm {
connection = "scm:git:git://github.com/JFormDesigner/FlatLaf.git"
url = "https://github.com/JFormDesigner/FlatLaf"
connection.set( "scm:git:git://github.com/JFormDesigner/FlatLaf.git" )
url.set( "https://github.com/JFormDesigner/FlatLaf" )
}
issueManagement {
system = "GitHub"
url = "https://github.com/JFormDesigner/FlatLaf/issues"
system.set( "GitHub" )
url.set( "https://github.com/JFormDesigner/FlatLaf/issues" )
}
}
afterEvaluate {
extension.nativeArtifacts?.forEach {
artifact( file( it.fileName ) ) {
artifact( artifacts.add( "archives", file( it.fileName ) ) {
classifier = it.classifier
extension = it.extension
}
type = it.type
} )
}
}
}
}
repositories {
maven {
name = "OSSRH"
val releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
url = uri( if( rootProject.hasProperty( "release" ) ) releasesRepoUrl else snapshotsRepoUrl )
credentials {
// get from gradle.properties
val ossrhUsername: String? by project
val ossrhPassword: String? by project
username = System.getenv( "OSSRH_USERNAME" ) ?: ossrhUsername
password = System.getenv( "OSSRH_PASSWORD" ) ?: ossrhPassword
}
}
}
}
signing {
@@ -104,22 +123,10 @@ tasks.withType<Sign>().configureEach {
onlyIf { rootProject.hasProperty( "release" ) }
}
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"
)
// check whether parallel build is enabled
tasks.withType<PublishToMavenRepository>().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'." )
}
}

View File

@@ -18,8 +18,9 @@ plugins {
java
}
val toolchainJavaVersion: String by rootProject.extra
java.toolchain {
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() ) {
java.toolchain {
languageVersion.set( JavaLanguageVersion.of( toolchainJavaVersion ) )
}
}

View File

@@ -1,7 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false

View File

@@ -27,8 +27,8 @@ plugins {
val sigtest = configurations.create( "sigtest" )
dependencies {
testImplementation( libs.junit )
testRuntimeOnly( libs.junit.launcher )
testImplementation( libs.bundles.junit )
testRuntimeOnly( libs.junit.engine )
// https://github.com/jtulach/netbeans-apitest
sigtest( libs.sigtest )
@@ -42,11 +42,11 @@ java {
tasks {
compileJava {
// generate JNI headers
options.headerOutputDirectory = layout.buildDirectory.dir( "generated/jni-headers" )
options.headerOutputDirectory.set( buildDir.resolve( "generated/jni-headers" ) )
}
jar {
archiveBaseName = "flatlaf"
archiveBaseName.set( "flatlaf" )
doLast {
ReorderJarEntries.reorderJarEntries( outputs.files.singleFile );
@@ -54,32 +54,11 @@ tasks {
}
named<Jar>( "sourcesJar" ) {
archiveBaseName = "flatlaf"
archiveBaseName.set( "flatlaf" )
}
named<Jar>( "javadocJar" ) {
archiveBaseName = "flatlaf"
}
register<Zip>( "jarNoNatives" ) {
group = "build"
dependsOn( "jar" )
archiveBaseName = "flatlaf"
archiveClassifier = "no-natives"
archiveExtension = "jar"
destinationDirectory = layout.buildDirectory.dir( "libs" )
from( zipTree( jar.get().archiveFile.get().asFile ) )
exclude( "com/formdev/flatlaf/natives/**" )
}
withType<AbstractPublishToMaven>().configureEach {
dependsOn( "jarNoNatives" )
}
withType<Sign>().configureEach {
dependsOn( "jarNoNatives" )
archiveBaseName.set( "flatlaf" )
}
check {
@@ -90,7 +69,7 @@ tasks {
useJUnitPlatform()
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
if( java.toolchain.languageVersion.get().asInt() >= 9 )
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
jvmArgs( listOf( "--add-opens", "java.desktop/javax.swing.plaf.basic=ALL-UNNAMED" ) )
}
@@ -98,31 +77,21 @@ 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 classpath )
"classpath" to sigtest.asPath )
"sigtest"(
"action" to "generate",
"fileName" to signatureFile,
"classpath" to jarPath,
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"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" )
}
}
}
@@ -131,23 +100,17 @@ 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 classpath )
"classpath" to sigtest.asPath )
"sigtest"(
"action" to "check",
"fileName" to signatureFile,
"classpath" to jarPath,
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
@@ -164,14 +127,9 @@ flatlafPublish {
val natives = "src/main/resources/com/formdev/flatlaf/natives"
nativeArtifacts = listOf(
NativeArtifact( tasks.getByName( "jarNoNatives" ).outputs.files.asPath, "no-natives", "jar" ),
NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ),
NativeArtifact( "${natives}/libflatlaf-macos-arm64.dylib", "macos-arm64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-macos-x86_64.dylib", "macos-x86_64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ),
NativeArtifact( "${natives}/libflatlaf-linux-arm64.so", "linux-arm64", "so" ),
NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ),
NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ),
)
}

View File

@@ -1,5 +1,5 @@
#Signature file v4.1
#Version 3.7
#Version 3.2
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -12,25 +12,17 @@ fld public final static java.lang.String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarBu
fld public final static java.lang.String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"
fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect"
fld public final static java.lang.String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"
fld public final static java.lang.String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder"
fld public final static java.lang.String GLASS_PANE_FULL_HEIGHT = "JRootPane.glassPaneFullHeight"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium"
fld public final static java.lang.String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"
fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight"
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
fld public final static java.lang.String OUTLINE = "JComponent.outline"
fld public final static java.lang.String OUTLINE_ERROR = "error"
fld public final static java.lang.String OUTLINE_SUCCESS = "success"
fld public final static java.lang.String OUTLINE_WARNING = "warning"
fld public final static java.lang.String PLACEHOLDER_TEXT = "JTextField.placeholderText"
fld public final static java.lang.String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius"
fld public final static java.lang.String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted"
fld public final static java.lang.String POPUP_FORCE_HEAVY_WEIGHT = "Popup.forceHeavyWeight"
fld public final static java.lang.String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth"
fld public final static java.lang.String PROGRESS_BAR_LARGE_HEIGHT = "JProgressBar.largeHeight"
fld public final static java.lang.String PROGRESS_BAR_SQUARE = "JProgressBar.square"
fld public final static java.lang.String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons"
@@ -41,7 +33,6 @@ fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY = "JTextFiel
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always"
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_NEVER = "never"
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_ONCE = "once"
fld public final static java.lang.String SELECT_ALL_ON_MOUSE_CLICK = "JTextField.selectAllOnMouseClick"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE = "JSplitPane.expandableSide"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE_LEFT = "left"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE_RIGHT = "right"
@@ -76,18 +67,12 @@ fld public final static java.lang.String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JT
fld public final static java.lang.String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"
fld public final static java.lang.String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"
fld public final static java.lang.String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_AUTO = "auto"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_LEFT = "left"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_NONE = "none"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_RIGHT = "right"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_CARD = "card"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined"
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"
@@ -103,14 +88,12 @@ fld public final static java.lang.String TEXT_FIELD_TRAILING_COMPONENT = "JTextF
fld public final static java.lang.String TEXT_FIELD_TRAILING_ICON = "JTextField.trailingIcon"
fld public final static java.lang.String TITLE_BAR_BACKGROUND = "JRootPane.titleBarBackground"
fld public final static java.lang.String TITLE_BAR_FOREGROUND = "JRootPane.titleBarForeground"
fld public final static java.lang.String TITLE_BAR_HEIGHT = "JRootPane.titleBarHeight"
fld public final static java.lang.String TITLE_BAR_SHOW_CLOSE = "JRootPane.titleBarShowClose"
fld public final static java.lang.String TITLE_BAR_SHOW_ICON = "JRootPane.titleBarShowIcon"
fld public final static java.lang.String TITLE_BAR_SHOW_ICONIFFY = "JRootPane.titleBarShowIconify"
fld public final static java.lang.String TITLE_BAR_SHOW_MAXIMIZE = "JRootPane.titleBarShowMaximize"
fld public final static java.lang.String TITLE_BAR_SHOW_TITLE = "JRootPane.titleBarShowTitle"
fld public final static java.lang.String TREE_PAINT_SELECTION = "JTree.paintSelection"
fld public final static java.lang.String TREE_WIDE_CELL_RENDERER = "JTree.wideCellRenderer"
fld public final static java.lang.String TREE_WIDE_SELECTION = "JTree.wideSelection"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "JRootPane.useWindowDecorations"
fld public final static java.lang.String WINDOW_STYLE = "Window.style"
@@ -224,14 +207,10 @@ meth public static java.lang.String getPreferredFontFamily()
meth public static java.lang.String getPreferredLightFontFamily()
meth public static java.lang.String getPreferredMonospacedFontFamily()
meth public static java.lang.String getPreferredSemiboldFontFamily()
meth public static java.lang.String getUIKeyLightOrDarkPrefix(boolean)
meth public static java.util.Map<java.lang.String,java.lang.Class<?>> getStyleableInfos(javax.swing.JComponent)
meth public static java.util.Map<java.lang.String,java.lang.String> getGlobalExtraDefaults()
meth public static java.util.Set<java.lang.String> getUIKeyPlatformPrefixes()
meth public static java.util.Set<java.lang.String> getUIKeySpecialPrefixes()
meth public static java.util.function.Function<java.lang.String,java.awt.Color> getSystemColorGetter()
meth public static javax.swing.UIDefaults$ActiveValue createActiveFontValue(float)
meth public static void disableWindowsD3Donscreen()
meth public static void hideMnemonics()
meth public static void initIconColors(javax.swing.UIDefaults,boolean)
meth public static void installLafInfo(java.lang.String,java.lang.Class<? extends javax.swing.LookAndFeel>)
@@ -262,7 +241,7 @@ meth public void setExtraDefaults(java.util.Map<java.lang.String,java.lang.Strin
meth public void uninitialize()
meth public void unregisterUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>)
supr javax.swing.plaf.basic.BasicLookAndFeel
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,globalExtraDefaults,linuxPopupMenuCanceler,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,uiKeyPlatformPrefixes,uiKeySpecialPrefixes,updateUIPending
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,getUIMethod,getUIMethodInitialized,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hcls ActiveFont,FlatUIDefaults,ImageIconUIResource
CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider
@@ -298,17 +277,12 @@ CLSS public abstract interface com.formdev.flatlaf.FlatSystemProperties
fld public final static java.lang.String ANIMATION = "flatlaf.animation"
fld public final static java.lang.String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded"
fld public final static java.lang.String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"
fld public final static java.lang.String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow"
fld public final static java.lang.String UI_SCALE = "flatlaf.uiScale"
fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown"
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange"
fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"
anno 0 java.lang.Deprecated()
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"
@@ -327,7 +301,7 @@ meth public static boolean setup(java.io.InputStream)
meth public static com.formdev.flatlaf.FlatLaf createLaf(com.formdev.flatlaf.IntelliJTheme)
meth public static com.formdev.flatlaf.FlatLaf createLaf(java.io.InputStream) throws java.io.IOException
supr java.lang.Object
hfds checkboxDuplicateColors,checkboxKeyMapping,jsonColors,jsonIcons,jsonUI,namedColors,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludesContains,uiKeyExcludesStartsWith,uiKeyInverseMapping,uiKeyMapping
hfds checkboxDuplicateColors,checkboxKeyMapping,colors,icons,isMaterialUILite,namedColors,ui,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludes,uiKeyInverseMapping,uiKeyMapping
CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf
outer com.formdev.flatlaf.IntelliJTheme
@@ -422,7 +396,6 @@ innr public static Fade
innr public static HSLChange
innr public static HSLIncreaseDecrease
innr public static Mix
innr public static Mix2
meth public !varargs static java.awt.Color applyFunctions(java.awt.Color,com.formdev.flatlaf.util.ColorFunctions$ColorFunction[])
meth public static float clamp(float)
meth public static float luma(java.awt.Color)
@@ -484,16 +457,6 @@ meth public java.lang.String toString()
meth public void apply(float[])
supr java.lang.Object
CLSS public static com.formdev.flatlaf.util.ColorFunctions$Mix2
outer com.formdev.flatlaf.util.ColorFunctions
cons public init(java.awt.Color,float)
fld public final float weight
fld public final java.awt.Color color1
intf com.formdev.flatlaf.util.ColorFunctions$ColorFunction
meth public java.lang.String toString()
meth public void apply(float[])
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.CubicBezierEasing
cons public init(float,float,float,float)
fld public final static com.formdev.flatlaf.util.CubicBezierEasing EASE
@@ -653,33 +616,16 @@ hfds alpha,hsl,rgb
CLSS public com.formdev.flatlaf.util.HiDPIUtils
cons public init()
innr public abstract interface static DirtyRegionCallback
innr public abstract interface static Painter
innr public static HiDPIRepaintManager
meth public static float computeTextYCorrection(java.awt.Graphics2D)
meth public static java.awt.Graphics2D createGraphicsTextYCorrection(java.awt.Graphics2D)
meth public static void addDirtyRegion(javax.swing.JComponent,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$DirtyRegionCallback)
meth public static void drawStringUnderlineCharAtWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int,int)
meth public static void drawStringWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int)
meth public static void installHiDPIRepaintManager()
meth public static void paintAtScale1x(java.awt.Graphics2D,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$Painter)
meth public static void paintAtScale1x(java.awt.Graphics2D,javax.swing.JComponent,com.formdev.flatlaf.util.HiDPIUtils$Painter)
meth public static void repaint(java.awt.Component)
meth public static void repaint(java.awt.Component,int,int,int,int)
meth public static void repaint(java.awt.Component,java.awt.Rectangle)
supr java.lang.Object
hfds CORRECTION_INTER,CORRECTION_OPEN_SANS,CORRECTION_SEGOE_UI,CORRECTION_TAHOMA,SCALE_FACTORS,useDebugScaleFactor,useTextYCorrection
CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$DirtyRegionCallback
outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
CLSS public static com.formdev.flatlaf.util.HiDPIUtils$HiDPIRepaintManager
outer com.formdev.flatlaf.util.HiDPIUtils
cons public init()
meth public void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
supr javax.swing.RepaintManager
CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$Painter
outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void paint(java.awt.Graphics2D,int,int,int,int,double)
@@ -772,116 +718,9 @@ 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
fld public final static boolean isGNOME
fld public final static boolean isJava_11_orLater
fld public final static boolean isJava_12_orLater
fld public final static boolean isJava_15_orLater
@@ -898,7 +737,6 @@ fld public final static boolean isMacOS_10_11_ElCapitan_orLater
fld public final static boolean isMacOS_10_14_Mojave_orLater
fld public final static boolean isMacOS_10_15_Catalina_orLater
fld public final static boolean isProjector
fld public final static boolean isUnknownOS
fld public final static boolean isWebswing
fld public final static boolean isWinPE
fld public final static boolean isWindows
@@ -914,21 +752,13 @@ 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)
@@ -938,9 +768,8 @@ 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,ignoreFontChange,inUnitTests,initialized,jreHiDPI,listenerInitialized,scaleFactor,supportedZoomFactors,unzoomedScaleFactor,zoomFactor
hfds DEBUG,changeSupport,initialized,jreHiDPI,scaleFactor
CLSS public java.awt.Color
cons public init(float,float,float)
@@ -1295,31 +1124,6 @@ meth public void provideErrorFeedback(java.awt.Component)
meth public void uninitialize()
supr java.lang.Object
CLSS public javax.swing.RepaintManager
cons public init()
meth public boolean isCompletelyDirty(javax.swing.JComponent)
meth public boolean isDoubleBufferingEnabled()
meth public java.awt.Dimension getDoubleBufferMaximumSize()
meth public java.awt.Image getOffscreenBuffer(java.awt.Component,int,int)
meth public java.awt.Image getVolatileOffscreenBuffer(java.awt.Component,int,int)
meth public java.awt.Rectangle getDirtyRegion(javax.swing.JComponent)
meth public java.lang.String toString()
meth public static javax.swing.RepaintManager currentManager(java.awt.Component)
meth public static javax.swing.RepaintManager currentManager(javax.swing.JComponent)
meth public static void setCurrentManager(javax.swing.RepaintManager)
meth public void addDirtyRegion(java.applet.Applet,int,int,int,int)
meth public void addDirtyRegion(java.awt.Window,int,int,int,int)
meth public void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
meth public void addInvalidComponent(javax.swing.JComponent)
meth public void markCompletelyClean(javax.swing.JComponent)
meth public void markCompletelyDirty(javax.swing.JComponent)
meth public void paintDirtyRegions()
meth public void removeInvalidComponent(javax.swing.JComponent)
meth public void setDoubleBufferMaximumSize(java.awt.Dimension)
meth public void setDoubleBufferingEnabled(boolean)
meth public void validateInvalidComponents()
supr java.lang.Object
CLSS public abstract javax.swing.border.AbstractBorder
cons public init()
intf java.io.Serializable

View File

@@ -21,8 +21,6 @@ import java.awt.IllegalComponentStateException;
import java.awt.Window;
import java.util.Objects;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.SwingConstants;
/**
@@ -35,7 +33,7 @@ public interface FlatClientProperties
//---- JButton ------------------------------------------------------------
/**
* Specifies type of button.
* Specifies type of a button.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
@@ -104,17 +102,6 @@ public interface FlatClientProperties
*/
String BUTTON_TYPE_BORDERLESS = "borderless";
/**
* Specifies whether the button preferred size will be made square (quadratically).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SQUARE_SIZE = "JButton.squareSize";
//---- JCheckBox ----------------------------------------------------------
/**
* Specifies selected state of a checkbox.
* <p>
@@ -131,6 +118,14 @@ public interface FlatClientProperties
*/
String SELECTED_STATE_INDETERMINATE = "indeterminate";
/**
* Specifies whether the button preferred size will be made square (quadratically).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SQUARE_SIZE = "JButton.squareSize";
//---- JComponent ---------------------------------------------------------
@@ -222,7 +217,6 @@ public interface FlatClientProperties
* <strong>Allowed Values</strong>
* {@link #OUTLINE_ERROR},
* {@link #OUTLINE_WARNING},
* {@link #OUTLINE_SUCCESS},
* any color (type {@link java.awt.Color}) or
* an array of two colors (type {@link java.awt.Color}[2]) where the first color
* is for focused state and the second for unfocused state
@@ -243,14 +237,6 @@ public interface FlatClientProperties
*/
String OUTLINE_WARNING = "warning";
/**
* Paint the component border in another color (usually greenish) to indicate a success.
*
* @see #OUTLINE
* @since 3.6
*/
String OUTLINE_SUCCESS = "success";
/**
* Specifies a callback that is invoked to check whether a component is permanent focus owner.
* Used to paint focus indicators.
@@ -271,116 +257,19 @@ public interface FlatClientProperties
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner";
/**
* Specifies whether a component shown in a window title bar area should behave as caption
* Specifies whether a component in an embedded menu bar should behave as caption
* (left-click allows moving window, right-click shows window system menu).
* The caption component does not receive mouse pressed/released/clicked/dragged events,
* The component does not receive mouse pressed/released/clicked/dragged events,
* but it gets mouse entered/exited/moved events.
* <p>
* Since 3.4, this client property also supports using a function that can check
* whether a given location in the component should behave as caption.
* Useful for components that do not use mouse input on whole component bounds.
*
* <pre>{@code
* myComponent.putClientProperty( "JComponent.titleBarCaption",
* (Function<Point, Boolean>) pt -> {
* // parameter pt contains mouse location (in myComponent coordinates)
* // return true if the component is not interested in mouse input at the given location
* // return false if the component wants process mouse input at the given location
* // return null if the component children should be checked
* return ...; // check here
* } );
* }</pre>
* <b>Warning</b>:
* <ul>
* <li>This function is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This function is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul>
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Boolean} or {@link java.util.function.Function}&lt;Point, Boolean&gt;
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2.5
*/
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption";
//---- Panel --------------------------------------------------------------
/**
* Marks the panel as placeholder for the iconfify/maximize/close buttons
* in fullWindowContent mode. See {@link #FULL_WINDOW_CONTENT}.
* <p>
* If fullWindowContent mode is enabled, the preferred size of the panel is equal
* to the size of the iconfify/maximize/close buttons. Otherwise is is {@code 0,0}.
* <p>
* You're responsible to layout that panel at the top-left or top-right corner,
* depending on platform, where the iconfify/maximize/close buttons are located.
* <p>
* Syntax of the value string is: {@code "win|mac [horizontal|vertical] [zeroInFullScreen] [leftToRight|rightToLeft]"}.
* <p>
* The string must start with {@code "win"} (for Windows or Linux) or
* with {@code "mac"} (for macOS) and specifies the platform where the placeholder
* should be used. On macOS, you need the placeholder in the top-left corner,
* but on Windows/Linux you need it in the top-right corner. So if your application supports
* fullWindowContent mode on both platforms, you can add two placeholders to your layout
* and FlatLaf automatically uses only one of them. The other gets size {@code 0,0}.
* <p>
* Optionally, you can append following options to the value string (separated by space characters):
* <ul>
* <li>{@code "horizontal"} - preferred height is zero
* <li>{@code "vertical"} - preferred width is zero
* <li>{@code "zeroInFullScreen"} - in full-screen mode on macOS, preferred size is {@code 0,0}
* <li>{@code "leftToRight"} - in right-to-left component orientation, preferred size is {@code 0,0}
* <li>{@code "rightToLeft"} - in left-to-right component orientation, preferred size is {@code 0,0}
* </ul>
*
* Example for adding placeholder to top-left corner on macOS:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* JToolBar toolBar = new JToolBar();
* // add tool bar items
*
* JPanel toolBarPanel = new JPanel( new BorderLayout() );
* toolBarPanel.add( placeholder, BorderLayout.WEST );
* toolBarPanel.add( toolBar, BorderLayout.CENTER );
*
* frame.getContentPane().add( toolBarPanel, BorderLayout.NORTH );
* }</pre>
*
* Or add placeholder as first item to the tool bar:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* JToolBar toolBar = new JToolBar();
* toolBar.add( placeholder );
* // add tool bar items
*
* frame.getContentPane().add( toolBar, BorderLayout.NORTH );
* }</pre>
*
* If a tabbed pane is located at the top, you can add the placeholder
* as leading component to that tabbed pane:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_LEADING_COMPONENT, placeholder );
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JPanel}<br>
* <strong>Value type</strong> {@link java.lang.String}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder";
//---- Popup --------------------------------------------------------------
/**
@@ -389,13 +278,12 @@ public interface FlatClientProperties
* <p>
* Note that this is not available on all platforms since it requires special support.
* Supported platforms:
* <ul>
* <li><strong>Windows 11</strong>: Only two corner radiuses are supported
* by the OS: {@code DWMWCP_ROUND} is 8px and {@code DWMWCP_ROUNDSMALL} is 4px.
* If this value is {@code 1 - 4}, then {@code DWMWCP_ROUNDSMALL} is used.
* If it is {@code >= 5}, then {@code DWMWCP_ROUND} is used.
* <li><strong>macOS</strong> (10.14 and later): Any corner radius is supported.
* </ul>
* <p>
* <strong>Windows 11</strong> (x86 or x86_64): Only two corner radiuses are supported
* by the OS: {@code DWMWCP_ROUND} is 8px and {@code DWMWCP_ROUNDSMALL} is 4px.
* If this value is {@code 1 - 4}, then {@code DWMWCP_ROUNDSMALL} is used.
* If it is {@code >= 5}, then {@code DWMWCP_ROUND} is used.
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
*
@@ -403,24 +291,6 @@ public interface FlatClientProperties
*/
String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius";
/**
* Specifies the popup rounded border width if the component is shown in a popup
* or if the component is the owner of another component that is shown in a popup.
* <p>
* Only used if popup uses rounded border.
* <p>
* Note that this is not available on all platforms since it requires special support.
* Supported platforms:
* <ul>
* <li><strong>macOS</strong> (10.14 and later)
* </ul>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.Float}<br>
*
* @since 3.3
*/
String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth";
/**
* Specifies whether a drop shadow is painted if the component is shown in a popup
* or if the component is the owner of another component that is shown in a popup.
@@ -472,7 +342,7 @@ public interface FlatClientProperties
* {@link FlatSystemProperties#USE_WINDOW_DECORATIONS}, but higher priority
* than UI default {@code TitlePane.useWindowDecorations}.
* <p>
* (requires Windows 10/11)
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
@@ -492,55 +362,13 @@ public interface FlatClientProperties
* {@link FlatSystemProperties#MENUBAR_EMBEDDED}, but higher priority
* than UI default {@code TitlePane.menuBarEmbedded}.
* <p>
* (requires Windows 10/11)
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
/**
* Specifies whether the content pane (and the glass pane) should be extended
* into the window title bar area
* (requires enabled window decorations). Default is {@code false}.
* <p>
* On macOS, use client property {@code apple.awt.fullWindowContent}
* (see <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">macOS Full window content</a>).
* <p>
* Setting this enables/disables full window content
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* If {@code true}, the content pane (and the glass pane) is extended into
* the title bar area. The window icon and title are hidden.
* Only the iconfify/maximize/close buttons stay visible in the upper right corner
* (and overlap the content pane).
* <p>
* The user can left-click-and-drag on the title bar area to move the window,
* except when clicking on a component that processes mouse events (e.g. buttons or menus).
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent";
/**
* Contains the current bounds of the iconfify/maximize/close buttons
* (in root pane coordinates) if fullWindowContent mode is enabled.
* Otherwise its value is {@code null}.
* <p>
* <b>Note</b>: Do not set this client property. It is set by FlatLaf.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Rectangle}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds";
/**
* Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}.
@@ -550,7 +378,7 @@ public interface FlatClientProperties
* <p>
* This client property has higher priority than UI default {@code TitlePane.showIcon}.
* <p>
* (requires Windows 10/11)
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
@@ -566,8 +394,6 @@ public interface FlatClientProperties
* Setting this shows/hides the windows title
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
@@ -576,14 +402,12 @@ public interface FlatClientProperties
String TITLE_BAR_SHOW_TITLE = "JRootPane.titleBarShowTitle";
/**
* Specifies whether the "iconify" button should be shown in the window title bar
* Specifies whether the "iconfify" button should be shown in the window title bar
* (requires enabled window decorations). Default is {@code true}.
* <p>
* Setting this shows/hides the "iconify" button
* Setting this shows/hides the "iconfify" button
* for the {@code JFrame} that contains the root pane.
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
@@ -598,8 +422,6 @@ public interface FlatClientProperties
* Setting this shows/hides the "maximize/restore" button
* for the {@code JFrame} that contains the root pane.
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
@@ -614,8 +436,6 @@ public interface FlatClientProperties
* Setting this shows/hides the "close" button
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
@@ -626,7 +446,7 @@ public interface FlatClientProperties
/**
* Background color of window title bar (requires enabled window decorations).
* <p>
* (requires Windows 10/11)
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Color}
@@ -638,7 +458,7 @@ public interface FlatClientProperties
/**
* Foreground color of window title bar (requires enabled window decorations).
* <p>
* (requires Windows 10/11)
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Color}
@@ -647,24 +467,10 @@ public interface FlatClientProperties
*/
String TITLE_BAR_FOREGROUND = "JRootPane.titleBarForeground";
/**
* Specifies the preferred height of title bar (requires enabled window decorations).
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*
* @since 3.5.2
*/
String TITLE_BAR_HEIGHT = "JRootPane.titleBarHeight";
/**
* Specifies whether the glass pane should have full height and overlap the title bar,
* if FlatLaf window decorations are enabled. Default is {@code false}.
* <p>
* (requires Windows 10/11)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
@@ -681,7 +487,7 @@ public interface FlatClientProperties
* On macOS, Java supports this out of the box.
* <p>
* Note that this client property must be set before the window becomes displayable.
* Otherwise, an {@link IllegalComponentStateException} is thrown.
* Otherwise an {@link IllegalComponentStateException} is thrown.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
@@ -1022,7 +828,7 @@ public interface FlatClientProperties
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link SwingConstants#LEADING} (default),
* {@link SwingConstants#LEADING} (default)
* {@link SwingConstants#TRAILING},
* {@link SwingConstants#CENTER},
* {@link #TABBED_PANE_ALIGN_LEADING} (default),
@@ -1086,9 +892,8 @@ 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},
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT} or
* {@link #TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY}
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
*/
String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode";
@@ -1114,14 +919,6 @@ 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>
@@ -1135,59 +932,6 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement";
/**
* Specifies the rotation of the tabs (title, icon, etc.).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link SwingConstants#LEFT},
* {@link SwingConstants#RIGHT},
* {@link #TABBED_PANE_TAB_ROTATION_NONE} (default),
* {@link #TABBED_PANE_TAB_ROTATION_AUTO},
* {@link #TABBED_PANE_TAB_ROTATION_LEFT} or
* {@link #TABBED_PANE_TAB_ROTATION_RIGHT}
*
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation";
/**
* Tabs are not rotated.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_NONE = "none";
/**
* Tabs are rotated depending on tab placement.
* <p>
* For top and bottom tab placement, the tabs are not rotated.<br>
* For left tab placement, the tabs are rotated counter-clockwise.<br>
* For right tab placement, the tabs are rotated clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_AUTO = "auto";
/**
* Tabs are rotated counter-clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_LEFT = "left";
/**
* Tabs are rotated clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_RIGHT = "right";
/**
* Specifies a component that will be placed at the leading edge of the tabs area.
* <p>
@@ -1220,15 +964,12 @@ public interface FlatClientProperties
/**
* Specifies whether all text is selected when the text component gains focus.
* <p>
* <strong>Components</strong> {@link javax.swing.text.JTextComponent} (and subclasses),
* {@link javax.swing.JComboBox} (since 3.6) and {@link javax.swing.JSpinner} (since 3.6)<br>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #SELECT_ALL_ON_FOCUS_POLICY_NEVER},
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ONCE} (default) or
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}
*
* @see #SELECT_ALL_ON_MOUSE_CLICK
*/
String SELECT_ALL_ON_FOCUS_POLICY = "JTextField.selectAllOnFocusPolicy";
@@ -1243,12 +984,6 @@ public interface FlatClientProperties
* Select all text when the text component gains focus for the first time
* and selection was not modified (is at end of text).
* This is the default.
* <p>
* <b>Limitations:</b>
* For {@link JFormattedTextField} and {@link JSpinner} this behaves
* as {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}.
* This is because of special behavior of {@link JFormattedTextField}
* that did not allow implementation of {@code "once"}.
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
*/
@@ -1261,19 +996,6 @@ public interface FlatClientProperties
*/
String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always";
/**
* Specifies whether all text is selected when when clicking with the mouse
* into the text field (and if "select all on focus" policy is enabled).
* <p>
* <strong>Components</strong> {@link javax.swing.text.JTextComponent} (and subclasses),
* {@link javax.swing.JComboBox} and {@link javax.swing.JSpinner}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
* @since 3.6
*/
String SELECT_ALL_ON_MOUSE_CLICK = "JTextField.selectAllOnMouseClick";
/**
* Placeholder text that is only painted if the text field is empty.
* <p>
@@ -1414,8 +1136,8 @@ public interface FlatClientProperties
* <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
* <strong>Allowed Values</strong>
* {@link SwingConstants#BOTTOM} (default),
* <strong>SupportedValues:</strong>
* {@link SwingConstants#BOTTOM} (default)
* {@link SwingConstants#TOP},
* {@link SwingConstants#LEFT} or
* {@link SwingConstants#RIGHT}
@@ -1452,23 +1174,13 @@ public interface FlatClientProperties
//---- JTree --------------------------------------------------------------
/**
* Specifies whether tree shows a wide selection. Default is {@code true}.
* Override if a tree shows a wide selection. Default is {@code true}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTree}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String TREE_WIDE_SELECTION = "JTree.wideSelection";
/**
* Specifies whether tree uses a wide cell renderer. Default is {@code false}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTree}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 3.6
*/
String TREE_WIDE_CELL_RENDERER = "JTree.wideCellRenderer";
/**
* Specifies whether tree item selection is painted. Default is {@code true}.
* If set to {@code false}, then the tree cell renderer is responsible for painting selection.
@@ -1479,44 +1191,6 @@ public interface FlatClientProperties
String TREE_PAINT_SELECTION = "JTree.paintSelection";
//---- macOS --------------------------------------------------------------
/**
* Specifies the spacing around the macOS window close/minimize/zoom buttons.
* Useful if <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">full window content</a>
* is enabled.
* <p>
* (requires macOS 10.14+ for "medium" spacing and macOS 11+ for "large" spacing, requires Java 17+)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #MACOS_WINDOW_BUTTONS_SPACING_MEDIUM} or
* {@link #MACOS_WINDOW_BUTTONS_SPACING_LARGE} (requires macOS 11+)
*
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing";
/**
* Add medium spacing around the macOS window close/minimize/zoom buttons.
*
* @see #MACOS_WINDOW_BUTTONS_SPACING
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium";
/**
* Add large spacing around the macOS window close/minimize/zoom buttons.
* <p>
* (requires macOS 11+; "medium" is used on older systems)
*
* @see #MACOS_WINDOW_BUTTONS_SPACING
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large";
//---- helper methods -----------------------------------------------------
/**

View File

@@ -48,7 +48,7 @@ public abstract class FlatDefaultsAddon
public InputStream getDefaults( Class<?> lafClass ) {
Class<?> addonClass = this.getClass();
String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' )
+ '/' + UIDefaultsLoader.simpleClassName( lafClass ) + ".properties";
+ '/' + lafClass.getSimpleName() + ".properties";
return addonClass.getResourceAsStream( propertiesName );
}

View File

@@ -71,7 +71,7 @@ class FlatInputMaps
);
}
// join ltr and rtl bindings to fix up/down/etc. keys in right-to-left component orientation
// join ltr and rtl bindings to fix up/down/etc keys in right-to-left component orientation
Object[] bindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings" );
Object[] rtlBindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings.RightToLeft" );
if( bindings != null && rtlBindings != null ) {

View File

@@ -20,7 +20,6 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
@@ -31,13 +30,15 @@ import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -45,7 +46,6 @@ import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
@@ -78,7 +78,6 @@ import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.JavaCompatibility2;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.GrayFilter;
@@ -102,8 +101,6 @@ public abstract class FlatLaf
private static Map<String, String> globalExtraDefaults;
private Map<String, String> extraDefaults;
private static Function<String, Color> systemColorGetter;
private static Set<String> uiKeyPlatformPrefixes;
private static Set<String> uiKeySpecialPrefixes;
private String desktopPropertyName;
private String desktopPropertyName2;
@@ -115,7 +112,6 @@ public abstract class FlatLaf
private PopupFactory oldPopupFactory;
private MnemonicHandler mnemonicHandler;
private boolean subMenuUsabilityHelperInstalled;
private LinuxPopupMenuCanceler linuxPopupMenuCanceler;
private Consumer<UIDefaults> postInitialization;
private List<Function<Object, Object>> uiDefaultsGetters;
@@ -125,46 +121,6 @@ public abstract class FlatLaf
private static String preferredSemiboldFontFamily;
private static String preferredMonospacedFontFamily;
static {
// see disableWindowsD3Donscreen() for details
// https://github.com/JFormDesigner/FlatLaf/issues/887
if( SystemInfo.isWindows &&
System.getProperty( "sun.java2d.d3d.onscreen" ) == null &&
System.getProperty( "sun.java2d.d3d" ) == null &&
System.getProperty( "sun.java2d.noddraw" ) == null )
System.setProperty( "sun.java2d.d3d.onscreen", "false" );
}
/**
* Disable usage of Windows Direct3D (DirectX) onscreen surfaces because this may lead to
* repaint issues (ghosting) on some systems (probably depending on graphics card/driver).
* Problem occurs usually when a small heavy-weight popup window (menu, combobox, tooltip) is shown.
* <p>
* Sets system property {@code sun.java2d.d3d.onscreen} to {@code false},
* but only if {@code sun.java2d.d3d.onscreen}, {@code sun.java2d.d3d}
* and {@code sun.java2d.noddraw} are not yet set.
* <p>
* <strong>Note</strong>: Must be invoked very early before the graphics environment is created.
* <p>
* This method is automatically invoked when loading this class,
* which is usually before the graphics environment is created.
* E.g. when doing {@code FlatLightLaf.setup()} or
* {@code UIManager.setLookAndFeel( "com.formdev.flatlaf.FlatLightLaf" )}.
* <p>
* However, it may be invoked too late if you use some methods from {@link UIManager}
* of {@link GraphicsEnvironment} before setting look and feel.
* E.g. {@link UIManager#put(Object, Object)}.
* In that case invoke this method yourself very early.
* <p>
* <strong>Tip</strong>: How to find out when the graphics environment is created?
* Set a breakpoint at constructor of class {@link GraphicsEnvironment} and look at the stack.
*
* @since 3.5.2
*/
public static void disableWindowsD3Donscreen() {
// dummy method used to trigger invocation of "static {...}" block
}
/**
* Sets the application look and feel to the given LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
@@ -227,11 +183,17 @@ public abstract class FlatLaf
* This depends on the operating system and on the used Java runtime.
* <p>
* This method returns {@code true} on Windows 10/11 (see exception below)
* and on Linux, otherwise returns {@code false}.
* <p>
* Returns also {@code false} on Windows 10/11 if
* FlatLaf native window border support is available (requires Windows 10/11).
* and on Linux, {@code false} otherwise.
* <p>
* Returns also {@code false} on Windows 10/11 if:
* <ul>
* <li>FlatLaf native window border support is available (requires Windows 10/11)</li>
* <li>running in
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)
* and JBR supports custom window decorations
* </li>
* </ul>
* In these cases, custom decorations are enabled by the root pane.
* Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or
* {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary.
@@ -310,10 +272,6 @@ public abstract class FlatLaf
// install submenu usability helper
subMenuUsabilityHelperInstalled = SubMenuUsabilityHelper.install();
// install Linux popup menu canceler
if( SystemInfo.isLinux )
linuxPopupMenuCanceler = new LinuxPopupMenuCanceler();
// listen to desktop property changes to update UI if system font or scaling changes
if( SystemInfo.isWindows ) {
// Windows 10 allows increasing font size independent of scaling:
@@ -368,22 +326,6 @@ 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
@@ -422,12 +364,6 @@ public abstract class FlatLaf
subMenuUsabilityHelperInstalled = false;
}
// uninstall Linux popup menu canceler
if( linuxPopupMenuCanceler != null ) {
linuxPopupMenuCanceler.uninstall();
linuxPopupMenuCanceler = null;
}
// restore default link color
new HTMLEditorKit().getStyleSheet().addRule( "a, address { color: blue; }" );
postInitialization = null;
@@ -541,10 +477,10 @@ public abstract class FlatLaf
// load defaults from properties
List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading();
if( lafClassesForDefaultsLoading == null )
lafClassesForDefaultsLoading = UIDefaultsLoader.getLafClassesForDefaultsLoading( getClass() );
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons,
this::applyAdditionalProperties, getAdditionalDefaults(), isDark(), defaults );
if( lafClassesForDefaultsLoading != null )
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons, getAdditionalDefaults(), isDark(), defaults );
else
UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, getAdditionalDefaults(), isDark(), defaults );
// setup default font after loading defaults from properties
// to allow defining "defaultFont" in properties
@@ -561,6 +497,9 @@ public abstract class FlatLaf
// initialize text antialiasing
putAATextInfo( defaults );
// apply additional defaults (e.g. from IntelliJ themes)
applyAdditionalDefaults( defaults );
// allow addons modifying UI defaults
for( FlatDefaultsAddon addon : addons )
addon.afterDefaultsLoading( this, defaults );
@@ -570,9 +509,6 @@ public abstract class FlatLaf
return UIScale.getUserScaleFactor();
} );
// add lazy UI delegate class loading (if necessary)
addLazyUIdelegateClassLoading( defaults );
if( postInitialization != null ) {
postInitialization.accept( defaults );
postInitialization = null;
@@ -581,8 +517,7 @@ public abstract class FlatLaf
return defaults;
}
// apply additional properties (e.g. from IntelliJ themes)
void applyAdditionalProperties( Properties properties ) {
void applyAdditionalDefaults( UIDefaults defaults ) {
}
protected List<Class<?>> getLafClassesForDefaultsLoading() {
@@ -723,22 +658,11 @@ 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 );
}
@@ -782,53 +706,6 @@ public abstract class FlatLaf
}
}
/**
* Handle UI delegate classes if running in special application where multiple class loaders are involved.
* E.g. in Eclipse plugin or in LibreOffice extension.
* <p>
* Problem: Swing runs in Java's system classloader and FlatLaf is loaded in plugin classloader.
* When Swing tries to load UI delegate class in {@link UIDefaults#getUIClass(String, ClassLoader)},
* invoked from {@link UIDefaults#getUI(JComponent)}, it uses the component's classloader,
* which is Java's system classloader for core Swing components,
* and can not find FlatLaf UI delegates.
* <p>
* Solution: Add lazy values for UI delegate class names.
* Those lazy values use FlatLaf classloader to load UI delegate class.
* This is similar to what {@link UIDefaults#getUIClass(String, ClassLoader)} does.
* <p>
* Not using {@code defaults.put( "ClassLoader", FlatLaf.class.getClassLoader() )},
* which would work for FlatLaf UI delegates, but it would break custom
* UI delegates used in other classloaders.
*/
private static void addLazyUIdelegateClassLoading( UIDefaults defaults ) {
if( FlatLaf.class.getClassLoader() == ClassLoader.getSystemClassLoader() )
return; // not necessary
Map<String, LazyValue> map = new HashMap<>();
for( Map.Entry<Object, Object> e : defaults.entrySet() ) {
Object key = e.getKey();
Object value = e.getValue();
if( key instanceof String && ((String)key).endsWith( "UI" ) &&
value instanceof String && !defaults.containsKey( value ) )
{
String className = (String) value;
map.put( className, (LazyValue) t -> {
try {
Class<?> uiClass = FlatLaf.class.getClassLoader().loadClass( className );
if( ComponentUI.class.isAssignableFrom( uiClass ) )
return uiClass;
} catch( ClassNotFoundException ex ) {
// ignore
}
// let UIDefaults.getUIClass() try to load UI delegate class
return null;
} );
}
}
defaults.putAll( map );
}
private void putAATextInfo( UIDefaults defaults ) {
if ( SystemInfo.isMacOS && SystemInfo.isJetBrainsJVM ) {
// The awt.font.desktophints property suggests sub-pixel anti-aliasing
@@ -938,7 +815,8 @@ public abstract class FlatLaf
* <p>
* Invoke this method before setting the look and feel.
* <p>
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
* If using Java modules, the package must be opened in {@code module-info.java}.
* Otherwise, use {@link #registerCustomDefaultsSource(URL)}.
*
* @param packageName a package name (e.g. "com.myapp.resources")
*/
@@ -985,9 +863,9 @@ public abstract class FlatLaf
* <p>
* See {@link #registerCustomDefaultsSource(String)} for details.
* <p>
* This method is useful if using Java modules and the package containing the properties files
* is not opened in {@code module-info.java}.
* E.g. {@code FlatLaf.registerCustomDefaultsSource( MyApp.class.getResource( "/com/myapp/themes/" ) )}.
* <p>
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
*
* @param packageUrl a package URL
* @since 2
@@ -1152,92 +1030,6 @@ public abstract class FlatLaf
FlatLaf.systemColorGetter = systemColorGetter;
}
/**
* Returns UI key prefix, used in FlatLaf properties files, for light or dark themes.
* Return value is either {@code [light]} or {@code [dark]}.
*
* @since 3.6
*/
public static String getUIKeyLightOrDarkPrefix( boolean dark ) {
return dark ? "[dark]" : "[light]";
}
/**
* Returns set of UI key prefixes, used in FlatLaf properties files, for current platform.
* If UI keys in properties files start with a prefix (e.g. {@code [someprefix]Button.background}),
* then they are only used if that prefix is contained in this set
* (or is one of {@code [light]} or {@code [dark]} depending on current theme).
* <p>
* By default, the set contains one or more of following prefixes:
* <ul>
* <li>{@code [win]} on Windows
* <li>{@code [mac]} on macOS
* <li>{@code [linux]} on Linux
* <li>{@code [unknown]} on other platforms
* <li>{@code [gnome]} on Linux with GNOME desktop environment
* <li>{@code [kde]} on Linux with KDE desktop environment
* <li>on Linux, the value of the environment variable {@code XDG_CURRENT_DESKTOP},
* split at colons and converted to lower case (e.g. if value of {@code XDG_CURRENT_DESKTOP}
* is {@code ubuntu:GNOME}, then {@code [ubuntu]} and {@code [gnome]})
* </ul>
* <p>
* You can add own prefixes to the set.
* The prefixes must start with '[' and end with ']' characters, otherwise they will be ignored.
*
* @since 3.6
*/
public static Set<String> getUIKeyPlatformPrefixes() {
if( uiKeyPlatformPrefixes == null ) {
uiKeyPlatformPrefixes = new HashSet<>();
uiKeyPlatformPrefixes.add(
SystemInfo.isWindows ? "[win]" :
SystemInfo.isMacOS ? "[mac]" :
SystemInfo.isLinux ? "[linux]" : "[unknown]" );
// Linux
if( SystemInfo.isLinux ) {
if( SystemInfo.isGNOME )
uiKeyPlatformPrefixes.add( "[gnome]" );
else if( SystemInfo.isKDE )
uiKeyPlatformPrefixes.add( "[kde]" );
// add values from XDG_CURRENT_DESKTOP for other desktops
String desktop = System.getenv( "XDG_CURRENT_DESKTOP" );
if( desktop != null ) {
// XDG_CURRENT_DESKTOP is a colon-separated list of strings
// https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html#key-onlyshowin
// e.g. "ubuntu:GNOME" on Ubuntu 24.10 or "GNOME-Classic:GNOME" on CentOS 7
for( String desk : StringUtils.split( desktop.toLowerCase( Locale.ENGLISH ), ':', true, true ) )
uiKeyPlatformPrefixes.add( '[' + desk + ']' );
}
}
}
return uiKeyPlatformPrefixes;
}
/**
* Returns set of special UI key prefixes, used in FlatLaf properties files.
* Unlike other prefixes, properties with special prefixes are preserved.
* You can access them using `UIManager`. E.g. `UIManager.get( "[someSpecialPrefix]someKey" )`.
* <p>
* By default, the set contains following special prefixes:
* <ul>
* <li>{@code [style]}
* </ul>
* <p>
* You can add own prefixes to the set.
* The prefixes must start with '[' and end with ']' characters, otherwise they will be ignored.
*
* @since 3.6
*/
public static Set<String> getUIKeySpecialPrefixes() {
if( uiKeySpecialPrefixes == null ) {
uiKeySpecialPrefixes = new HashSet<>();
uiKeySpecialPrefixes.add( "[style]" );
}
return uiKeySpecialPrefixes;
}
private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
@@ -1503,8 +1295,8 @@ public abstract class FlatLaf
* @since 2.5
*/
public static Map<String, Class<?>> getStyleableInfos( JComponent c ) {
ComponentUI ui = JavaCompatibility2.getUI( c );
return (ui instanceof StyleableUI) ? ((StyleableUI)ui).getStyleableInfos( c ) : null;
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? ui.getStyleableInfos( c ) : null;
}
/**
@@ -1516,10 +1308,41 @@ public abstract class FlatLaf
*/
@SuppressWarnings( "unchecked" )
public static <T> T getStyleableValue( JComponent c, String key ) {
ComponentUI ui = JavaCompatibility2.getUI( c );
return (ui instanceof StyleableUI) ? (T) ((StyleableUI)ui).getStyleableValue( c, key ) : null;
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? (T) ui.getStyleableValue( c, key ) : null;
}
private static StyleableUI getStyleableUI( JComponent c ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
if( SystemInfo.isJava_9_orLater ) {
try {
// JComponent.getUI() is available since Java 9
getUIMethod = MethodHandles.lookup().findVirtual( JComponent.class, "getUI",
MethodType.methodType( ComponentUI.class ) );
} catch( Exception ex ) {
// ignore
}
}
}
try {
Object ui;
if( getUIMethod != null )
ui = getUIMethod.invoke( c );
else
ui = c.getClass().getMethod( "getUI" ).invoke( c );
return (ui instanceof StyleableUI) ? (StyleableUI) ui : null;
} catch( Throwable ex ) {
// ignore
return null;
}
}
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
/**
* Returns the preferred font family to be used for (nearly) all fonts; or {@code null}.
*
@@ -1795,7 +1618,7 @@ public abstract class FlatLaf
return toUIResource( baseFont );
}
private static FontUIResource toUIResource( Font font ) {
private FontUIResource toUIResource( Font font ) {
// make sure that font is a UIResource for LaF switching
return (font instanceof FontUIResource)
? (FontUIResource) font

View File

@@ -20,21 +20,16 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Properties;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
/**
* A Flat LaF that is able to load UI defaults from properties passed to the constructor.
* <p>
* Specify the base theme in the properties with {@code @baseTheme=<baseTheme>}.
* Allowed values for {@code <baseTheme>} are {@code light} (the default), {@code dark},
* {@code intellij}, {@code darcula}, {@code maclight} or {@code macdark}.
* {@code intellij} or {@code darcula}.
* <p>
* The properties are applied after loading the base theme and may overwrite base properties.
* All features of FlatLaf properties files are available.
@@ -65,8 +60,8 @@ public class FlatPropertiesLaf
throws IOException
{
Properties properties = new Properties();
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
properties.load( reader );
try( InputStream in2 = in ) {
properties.load( in2 );
}
return properties;
}
@@ -76,8 +71,7 @@ public class FlatPropertiesLaf
this.properties = properties;
baseTheme = properties.getProperty( "@baseTheme", "light" );
dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme ) ||
"macdark".equalsIgnoreCase( baseTheme );
dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme );
}
@Override
@@ -122,16 +116,6 @@ public class FlatPropertiesLaf
lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatDarculaLaf.class );
break;
case "maclight":
lafClasses.add( FlatLightLaf.class );
lafClasses.add( FlatMacLightLaf.class );
break;
case "macdark":
lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatMacDarkLaf.class );
break;
}
return lafClasses;
}

View File

@@ -16,10 +16,7 @@
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;
/**
@@ -85,7 +82,7 @@ public interface FlatSystemProperties
* {@link FlatClientProperties#USE_WINDOW_DECORATIONS} and
* UI default {@code TitlePane.useWindowDecorations}.
* <p>
* (requires Windows 10/11)
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
@@ -102,14 +99,11 @@ public interface FlatSystemProperties
* Setting this to {@code false} disables using JetBrains Runtime custom window decorations.
* Then FlatLaf native window decorations are used.
* <p>
* (requires Windows 10/11)
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code false} (since v2; was {@code true} in v1)
*
* @deprecated No longer used since FlatLaf 3.3. Retained for API compatibility.
*/
@Deprecated
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
/**
@@ -123,7 +117,7 @@ public interface FlatSystemProperties
* {@link FlatClientProperties#MENU_BAR_EMBEDDED} and
* UI default {@code TitlePane.menuBarEmbedded}.
* <p>
* (requires Windows 10/11)
* (requires Window 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
@@ -139,35 +133,12 @@ public interface FlatSystemProperties
String ANIMATION = "flatlaf.animation";
/**
* Specifies whether native rounded popup borders should be used (if supported by operating system).
* <p>
* (requires Windows 11 or macOS)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}; except in FlatLaf 3.5.x on macOS 14.4+ where it was {@code false}
*
* @since 3.5.2
*/
String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder";
/**
* Species whether popup windows may be reused without (temporary) hiding them.
* E.g. if "moving" a tooltip to follow the mouse pointer, normally it is necessary
* to hide the tooltip and show it again at the new location, which causes some
* flicker with heavy-weight popup windows that FlatLaf uses on all platforms.
* <p>
* If {@code true}, hiding popup window is deferred for an event cycle,
* which allows reusing still visible popup window and avoids flicker when "moving" the popup.
* <p>
* Note that {@link JPopupMenu} popup windows (menus and combobox lists) are newer reused.
* Specifies whether smooth scrolling is enabled.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*
* @since 3.6.2
*/
String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow";
String SMOOTH_SCROLLING = "flatlaf.smoothScrolling";
/**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
@@ -206,59 +177,24 @@ public interface FlatSystemProperties
String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary";
/**
* Specifies a directory in which the FlatLaf native libraries are searched for.
* Specifies a directory in which the native FlatLaf libraries have been extracted.
* The path can be absolute or relative to current application working directory.
* This can be used to avoid extraction of the native libraries to the temporary directory at runtime.
* <p>
* If the value is {@code "system"} (supported since FlatLaf 2.6),
* then {@link System#loadLibrary(String)} is used to load the native library.
* This searches for the native library in classloader of caller
* If the value is {@code "system"}, then {@link System#loadLibrary(String)} is
* used to load the native library.
* Searches for the native library in classloader of caller
* (using {@link ClassLoader#findLibrary(String)}) and in paths specified
* in system properties {@code sun.boot.library.path} and {@code java.library.path}.
* (supported since FlatLaf 2.6)
* <p>
* If the native library can not be loaded from the given path (or via {@link System#loadLibrary(String)}),
* If the native library can not loaded from the given path (or via {@link System#loadLibrary(String)}),
* then the embedded native library is extracted to the temporary directory and loaded from there.
* <p>
* The file names of the native libraries must be either:
* <ul>
* <li>the same as in flatlaf.jar in package 'com/formdev/flatlaf/natives' (required for "system") or
* <li>when downloaded from Maven central then as described here:
* <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* (requires FlatLaf 3.4)
* </ul>
* <p>
* <strong>Note</strong>: Since FlatLaf 3.1 it is recommended to download the
* FlatLaf native libraries from Maven central and distribute them with your
* application in the same directory as flatlaf.jar.
* Then it is <strong>not necessary</strong> to set this system property.
* See <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* for details.
*
* @since 2
*/
String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath";
/**
* Specifies whether safe triangle is used to improve usability of submenus.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*
* @since 3.5.1
*/
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}.

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -24,16 +25,20 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import javax.swing.UIDefaults;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.json.Json;
import com.formdev.flatlaf.json.ParseException;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
@@ -58,11 +63,13 @@ public class IntelliJTheme
public final boolean dark;
public final String author;
private final Map<String, String> jsonColors;
private final Map<String, Object> jsonUI;
private final Map<String, Object> jsonIcons;
private final boolean isMaterialUILite;
private Map<String, String> namedColors = Collections.emptyMap();
private Map<String, String> colors;
private Map<String, Object> ui;
private Map<String, Object> icons;
private Map<String, ColorUIResource> namedColors = Collections.emptyMap();
/**
* Loads a IntelliJ .theme.json file from the given input stream,
@@ -77,7 +84,7 @@ public class IntelliJTheme
try {
return FlatLaf.setup( createLaf( in ) );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load IntelliJ theme", ex );
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load IntelliJ theme", ex );
return false;
}
}
@@ -131,90 +138,94 @@ public class IntelliJTheme
dark = Boolean.parseBoolean( (String) json.get( "dark" ) );
author = (String) json.get( "author" );
jsonColors = (Map<String, String>) json.get( "colors" );
jsonUI = (Map<String, Object>) json.get( "ui" );
jsonIcons = (Map<String, Object>) json.get( "icons" );
isMaterialUILite = author.equals( "Mallowigi" );
colors = (Map<String, String>) json.get( "colors" );
ui = (Map<String, Object>) json.get( "ui" );
icons = (Map<String, Object>) json.get( "icons" );
}
private void applyProperties( Properties properties ) {
if( jsonUI == null )
private void applyProperties( UIDefaults defaults ) {
if( ui == null )
return;
put( properties, "Component.isIntelliJTheme", "true" );
defaults.put( "Component.isIntelliJTheme", true );
// enable button shadows
put( properties, "Button.paintShadow", "true" );
put( properties, "Button.shadowWidth", dark ? "2" : "1" );
defaults.put( "Button.paintShadow", true );
defaults.put( "Button.shadowWidth", dark ? 2 : 1 );
Map<String, String> themeSpecificProps = removeThemeSpecificProps( properties );
Set<String> jsonUIKeys = new HashSet<>();
Map<Object, Object> themeSpecificDefaults = removeThemeSpecificDefaults( defaults );
// Json node "colors"
loadNamedColors( properties, jsonUIKeys );
loadNamedColors( defaults );
// convert Json "ui" structure to UI properties
for( Map.Entry<String, Object> e : jsonUI.entrySet() )
apply( e.getKey(), e.getValue(), properties, jsonUIKeys );
// convert Json "ui" structure to UI defaults
ArrayList<Object> defaultsKeysCache = new ArrayList<>();
Set<String> uiKeys = new HashSet<>();
for( Map.Entry<String, Object> e : ui.entrySet() )
apply( e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
// set FlatLaf variables
copyIfSetInJson( properties, jsonUIKeys, "@background", "Panel.background", "*.background" );
copyIfSetInJson( properties, jsonUIKeys, "@foreground", "CheckBox.foreground", "*.foreground" );
copyIfSetInJson( properties, jsonUIKeys, "@accentBaseColor",
"ColorPalette.accent", // Material UI Lite, Hiberbee
"ColorPalette.accentColor", // Dracula, One Dark
"ProgressBar.foreground",
"*.selectionBackground" );
copyIfSetInJson( properties, jsonUIKeys, "@accentUnderlineColor", "*.underlineColor", "TabbedPane.underlineColor" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionBackground", "*.selectionBackground" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionForeground", "*.selectionForeground" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionInactiveBackground", "*.selectionInactiveBackground" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionInactiveForeground", "*.selectionInactiveForeground" );
// Json node "icons/ColorPalette"
applyIconsColorPalette( properties );
// apply "CheckBox.icon." colors
applyCheckBoxColors( properties );
applyColorPalette( defaults );
applyCheckBoxColors( defaults );
// copy values
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() ) {
Object value = properties.get( e.getValue() );
Object value = defaults.get( e.getValue() );
if( value != null )
put( properties, e.getKey(), value );
defaults.put( e.getKey(), value );
}
// IDEA does not paint button background if disabled, but FlatLaf does
put( properties, "Button.disabledBackground", "@disabledBackground" );
put( properties, "ToggleButton.disabledBackground", "@disabledBackground" );
Object panelBackground = defaults.get( "Panel.background" );
defaults.put( "Button.disabledBackground", panelBackground );
defaults.put( "ToggleButton.disabledBackground", panelBackground );
// fix Button
fixStartEnd( properties, jsonUIKeys, "Button.startBackground", "Button.endBackground", "Button.background" );
fixStartEnd( properties, jsonUIKeys, "Button.startBorderColor", "Button.endBorderColor", "Button.borderColor" );
fixStartEnd( properties, jsonUIKeys, "Button.default.startBackground", "Button.default.endBackground", "Button.default.background" );
fixStartEnd( properties, jsonUIKeys, "Button.default.startBorderColor", "Button.default.endBorderColor", "Button.default.borderColor" );
// fix Button borders
copyIfNotSet( defaults, "Button.focusedBorderColor", "Component.focusedBorderColor", uiKeys );
defaults.put( "Button.hoverBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.hoverBorderColor", defaults.get( "Button.focusedBorderColor" ) );
// IDEA uses an SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground
Object helpButtonBackground = defaults.get( "Button.startBackground" );
Object helpButtonBorderColor = defaults.get( "Button.startBorderColor" );
if( helpButtonBackground == null )
helpButtonBackground = defaults.get( "Button.background" );
if( helpButtonBorderColor == null )
helpButtonBorderColor = defaults.get( "Button.borderColor" );
defaults.put( "HelpButton.background", helpButtonBackground );
defaults.put( "HelpButton.borderColor", helpButtonBorderColor );
defaults.put( "HelpButton.disabledBackground", panelBackground );
defaults.put( "HelpButton.disabledBorderColor", defaults.get( "Button.disabledBorderColor" ) );
defaults.put( "HelpButton.focusedBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.focusedBackground", defaults.get( "Button.focusedBackground" ) );
// IDEA uses TextField.background for editable ComboBox and Spinner
Object textFieldBackground = get( properties, themeSpecificProps, "TextField.background" );
put( properties, "ComboBox.editableBackground", textFieldBackground );
put( properties, "Spinner.background", textFieldBackground );
Object textFieldBackground = get( defaults, themeSpecificDefaults, "TextField.background" );
defaults.put( "ComboBox.editableBackground", textFieldBackground );
defaults.put( "Spinner.background", textFieldBackground );
// Spinner arrow button always has same colors as ComboBox arrow button
defaults.put( "Spinner.buttonBackground", defaults.get( "ComboBox.buttonEditableBackground" ) );
defaults.put( "Spinner.buttonArrowColor", defaults.get( "ComboBox.buttonArrowColor" ) );
defaults.put( "Spinner.buttonDisabledArrowColor", defaults.get( "ComboBox.buttonDisabledArrowColor" ) );
// some themes specify colors for TextField.background, but forget to specify it for other components
// (probably because those components are not used in IntelliJ IDEA)
putAll( properties, textFieldBackground,
putAll( defaults, textFieldBackground,
"EditorPane.background",
"FormattedTextField.background",
"PasswordField.background",
"TextArea.background",
"TextPane.background"
);
putAll( properties, get( properties, themeSpecificProps, "TextField.selectionBackground" ),
putAll( defaults, get( defaults, themeSpecificDefaults, "TextField.selectionBackground" ),
"EditorPane.selectionBackground",
"FormattedTextField.selectionBackground",
"PasswordField.selectionBackground",
"TextArea.selectionBackground",
"TextPane.selectionBackground"
);
putAll( properties, get( properties, themeSpecificProps, "TextField.selectionForeground" ),
putAll( defaults, get( defaults, themeSpecificDefaults, "TextField.selectionForeground" ),
"EditorPane.selectionForeground",
"FormattedTextField.selectionForeground",
"PasswordField.selectionForeground",
@@ -224,7 +235,7 @@ public class IntelliJTheme
// fix disabled and not-editable backgrounds for text components, combobox and spinner
// (IntelliJ IDEA does not use those colors; instead it used background color of parent)
putAll( properties, "@disabledBackground",
putAll( defaults, panelBackground,
"ComboBox.disabledBackground",
"EditorPane.disabledBackground", "EditorPane.inactiveBackground",
"FormattedTextField.disabledBackground", "FormattedTextField.inactiveBackground",
@@ -235,143 +246,132 @@ public class IntelliJTheme
"TextPane.disabledBackground", "TextPane.inactiveBackground"
);
// fix ToggleButton
if( !uiKeys.contains( "ToggleButton.startBackground" ) && !uiKeys.contains( "*.startBackground" ) )
defaults.put( "ToggleButton.startBackground", defaults.get( "Button.startBackground" ) );
if( !uiKeys.contains( "ToggleButton.endBackground" ) && !uiKeys.contains( "*.endBackground" ) )
defaults.put( "ToggleButton.endBackground", defaults.get( "Button.endBackground" ) );
if( !uiKeys.contains( "ToggleButton.foreground" ) && uiKeys.contains( "Button.foreground" ) )
defaults.put( "ToggleButton.foreground", defaults.get( "Button.foreground" ) );
// fix DesktopPane background (use Panel.background and make it 5% darker/lighter)
put( properties, "Desktop.background", dark ? "lighten($Panel.background,5%)" : "darken($Panel.background,5%)" );
Color desktopBackgroundBase = defaults.getColor( "Panel.background" );
Color desktopBackground = ColorFunctions.applyFunctions( desktopBackgroundBase,
new ColorFunctions.HSLIncreaseDecrease( 2, dark, 5, false, true ) );
defaults.put( "Desktop.background", new ColorUIResource( desktopBackground ) );
// fix List and Table background colors in Material UI Lite themes
if( isMaterialUILite ) {
defaults.put( "List.background", defaults.get( "Tree.background" ) );
defaults.put( "Table.background", defaults.get( "Tree.background" ) );
}
// limit tree row height
String rowHeightStr = (String) properties.get( "Tree.rowHeight" );
int rowHeight = (rowHeightStr != null) ? Integer.parseInt( rowHeightStr ) : 0;
int rowHeight = defaults.getInt( "Tree.rowHeight" );
if( rowHeight > 22 )
put( properties, "Tree.rowHeight", "22" );
defaults.put( "Tree.rowHeight", 22 );
// get (and remove) theme specific wildcard replacements, which override all other properties that end with same suffix
HashMap<String, String> wildcardProps = new HashMap<>();
Iterator<Map.Entry<String, String>> it = themeSpecificProps.entrySet().iterator();
// get (and remove) theme specific wildcard replacements, which override all other defaults that end with same suffix
HashMap<String, Object> wildcards = new HashMap<>();
Iterator<Entry<Object, Object>> it = themeSpecificDefaults.entrySet().iterator();
while( it.hasNext() ) {
Map.Entry<String, String> e = it.next();
String key = e.getKey();
Entry<Object, Object> e = it.next();
String key = (String) e.getKey();
if( key.startsWith( "*." ) ) {
wildcardProps.put( key, e.getValue() );
wildcards.put( key.substring( "*.".length() ), e.getValue() );
it.remove();
}
}
// override properties with theme specific wildcard replacements
if( !wildcardProps.isEmpty() ) {
for( Map.Entry<String, String> e : wildcardProps.entrySet() )
applyWildcard( properties, e.getKey(), e.getValue() );
// override UI defaults with theme specific wildcard replacements
if( !wildcards.isEmpty() ) {
for( Object key : defaults.keySet().toArray() ) {
int dot;
if( !(key instanceof String) ||
(dot = ((String)key).lastIndexOf( '.' )) < 0 )
continue;
String wildcardKey = ((String)key).substring( dot + 1 );
Object wildcardValue = wildcards.get( wildcardKey );
if( wildcardValue != null )
defaults.put( key, wildcardValue );
}
}
// apply theme specific properties at the end to allow overwriting
for( Map.Entry<String, String> e : themeSpecificProps.entrySet() ) {
String key = e.getKey();
String value = e.getValue();
// apply theme specific UI defaults at the end to allow overwriting
for( Map.Entry<Object, Object> e : themeSpecificDefaults.entrySet() ) {
Object key = e.getKey();
Object value = e.getValue();
// append styles to existing styles
if( key.startsWith( "[style]" ) ) {
String oldValue = (String) properties.get( key );
if( key instanceof String && ((String)key).startsWith( "[style]" ) ) {
Object oldValue = defaults.get( key );
if( oldValue != null )
value = oldValue + "; " + value;
}
put( properties, key, value );
defaults.put( key, value );
}
// let Java release memory
colors = null;
ui = null;
icons = null;
}
private String get( Properties properties, Map<String, String> themeSpecificProps, String key ) {
return themeSpecificProps.getOrDefault( key, (String) properties.get( key ) );
private Object get( UIDefaults defaults, Map<Object, Object> themeSpecificDefaults, String key ) {
return themeSpecificDefaults.getOrDefault( key, defaults.get( key ) );
}
private void put( Properties properties, Object key, Object value ) {
if( value != null )
properties.put( key, value );
else
properties.remove( key );
}
private void putAll( Properties properties, Object value, String... keys ) {
private void putAll( UIDefaults defaults, Object value, String... keys ) {
for( String key : keys )
put( properties, key, value );
defaults.put( key, value );
}
private void copyIfSetInJson( Properties properties, Set<String> jsonUIKeys, String destKey, String... srcKeys ) {
for( String srcKey : srcKeys ) {
if( jsonUIKeys.contains( srcKey ) ) {
Object value = properties.get( srcKey );
if( value != null ) {
put( properties, destKey, value );
break;
}
}
}
}
private void fixStartEnd( Properties properties, Set<String> jsonUIKeys, String startKey, String endKey, String key ) {
if( jsonUIKeys.contains( startKey ) && jsonUIKeys.contains( endKey ) )
put( properties, key, "$" + startKey );
}
private Map<String, String> removeThemeSpecificProps( Properties properties ) {
// search for theme specific properties keys
private Map<Object, Object> removeThemeSpecificDefaults( UIDefaults defaults ) {
// search for theme specific UI defaults keys
ArrayList<String> themeSpecificKeys = new ArrayList<>();
for( Object key : properties.keySet() ) {
if( ((String)key).startsWith( "{" ) )
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).startsWith( "[" ) && !((String)key).startsWith( "[style]" ) )
themeSpecificKeys.add( (String) key );
}
// special prefixes (priority from highest to lowest)
String currentThemePrefix = '{' + name.replace( ' ', '_' ) + '}';
String currentThemeAndAuthorPrefix = '{' + name.replace( ' ', '_' ) + "---" + author.replace( ' ', '_' ) + '}';
String currentAuthorPrefix = "{author-" + author.replace( ' ', '_' ) + '}';
String lightOrDarkPrefix = dark ? "{*-dark}" : "{*-light}";
String allThemesPrefix = "{*}";
String[] prefixes = { currentThemePrefix, currentThemeAndAuthorPrefix, currentAuthorPrefix, lightOrDarkPrefix, allThemesPrefix };
// collect values for special prefixes in its own maps
@SuppressWarnings( "unchecked" )
Map<String, String>[] maps = new Map[prefixes.length];
for( int i = 0; i < maps.length; i++ )
maps[i] = new HashMap<>();
// remove theme specific properties and remember only those for current theme
// remove theme specific UI defaults and remember only those for current theme
Map<Object, Object> themeSpecificDefaults = new HashMap<>();
String currentThemePrefix = '[' + name.replace( ' ', '_' ) + ']';
String currentThemeAndAuthorPrefix = '[' + name.replace( ' ', '_' ) + "---" + author.replace( ' ', '_' ) + ']';
String currentAuthorPrefix = "[author-" + author.replace( ' ', '_' ) + ']';
String allThemesPrefix = "[*]";
String[] prefixes = { currentThemePrefix, currentThemeAndAuthorPrefix, currentAuthorPrefix, allThemesPrefix };
for( String key : themeSpecificKeys ) {
String value = (String) properties.remove( key );
for( int i = 0; i < prefixes.length; i++ ) {
String prefix = prefixes[i];
Object value = defaults.remove( key );
for( String prefix : prefixes ) {
if( key.startsWith( prefix ) ) {
maps[i].put( key.substring( prefix.length() ), value );
themeSpecificDefaults.put( key.substring( prefix.length() ), value );
break;
}
}
}
// copy values into single map (from lowest to highest priority)
Map<String, String> themeSpecificProps = new HashMap<>();
for( int i = maps.length - 1; i >= 0; i-- )
themeSpecificProps.putAll( maps[i] );
return themeSpecificProps;
return themeSpecificDefaults;
}
/**
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html#defining-named-colors
*/
private void loadNamedColors( Properties properties, Set<String> jsonUIKeys ) {
if( jsonColors == null )
private void loadNamedColors( UIDefaults defaults ) {
if( colors == null )
return;
namedColors = new HashMap<>();
for( Map.Entry<String, String> e : jsonColors.entrySet() ) {
for( Map.Entry<String, String> e : colors.entrySet() ) {
String value = e.getValue();
if( canParseColor( value ) ) {
ColorUIResource color = parseColor( value );
if( color != null ) {
String key = e.getKey();
namedColors.put( key, value );
String uiKey = "ColorPalette." + key;
put( properties, uiKey, value );
// this is only necessary for copyIfSetInJson() (used for accent color)
jsonUIKeys.add( uiKey );
namedColors.put( key, color );
defaults.put( "ColorPalette." + key, color );
}
}
}
@@ -380,7 +380,7 @@ public class IntelliJTheme
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html#custom-ui-control-colors
*/
@SuppressWarnings( "unchecked" )
private void apply( String key, Object value, Properties properties, Set<String> jsonUIKeys ) {
private void apply( String key, Object value, UIDefaults defaults, ArrayList<Object> defaultsKeysCache, Set<String> uiKeys ) {
if( value instanceof Map ) {
Map<String, Object> map = (Map<String, Object>)value;
if( map.containsKey( "os.default" ) || map.containsKey( "os.windows" ) || map.containsKey( "os.mac" ) || map.containsKey( "os.linux" ) ) {
@@ -388,12 +388,12 @@ public class IntelliJTheme
: SystemInfo.isMacOS ? "os.mac"
: SystemInfo.isLinux ? "os.linux" : null;
if( osKey != null && map.containsKey( osKey ) )
apply( key, map.get( osKey ), properties, jsonUIKeys );
apply( key, map.get( osKey ), defaults, defaultsKeysCache, uiKeys );
else if( map.containsKey( "os.default" ) )
apply( key, map.get( "os.default" ), properties, jsonUIKeys );
apply( key, map.get( "os.default" ), defaults, defaultsKeysCache, uiKeys );
} else {
for( Map.Entry<String, Object> e : map.entrySet() )
apply( key + '.' + e.getKey(), e.getValue(), properties, jsonUIKeys );
apply( key + '.' + e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
}
} else {
if( "".equals( value ) )
@@ -408,40 +408,25 @@ public class IntelliJTheme
key.equals( "Tree.rightChildIndent" ) )
return; // ignore
// ignore icons
if( key.endsWith( "Icon" ) )
return; // ignore
// map keys
key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() )
return; // ignore key
// exclude properties (1st level)
// exclude properties
int dot = key.indexOf( '.' );
if( dot > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot + 1 ) ) )
if( dot > 0 && uiKeyExcludes.contains( key.substring( 0, dot + 1 ) ) )
return;
// exclude properties (2st level)
int dot2 = (dot > 0) ? key.indexOf( '.', dot + 1 ) : -1;
if( dot2 > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot2 + 1 ) ) )
if( uiKeyDoNotOverride.contains( key ) && uiKeys.contains( key ) )
return;
// exclude properties (contains)
for( String s : uiKeyExcludesContains ) {
if( key.contains( s ) )
return;
}
uiKeys.add( key );
if( uiKeyDoNotOverride.contains( key ) && jsonUIKeys.contains( key ) )
return;
jsonUIKeys.add( key );
String valueStr = value.toString().trim();
String valueStr = value.toString();
// map named colors
String uiValue = namedColors.get( valueStr );
Object uiValue = namedColors.get( valueStr );
// parse value
if( uiValue == null ) {
@@ -460,64 +445,47 @@ public class IntelliJTheme
// parse value
try {
UIDefaultsLoader.parseValue( key, valueStr, null );
uiValue = valueStr;
uiValue = UIDefaultsLoader.parseValue( key, valueStr, null );
} catch( RuntimeException ex ) {
UIDefaultsLoader.logParseError( key, valueStr, ex, true );
UIDefaultsLoader.logParseError( key, valueStr, ex, false );
return; // ignore invalid value
}
}
// wildcards
if( applyWildcard( properties, key, uiValue ) )
return;
if( key.startsWith( "*." ) ) {
// wildcard
String tail = key.substring( 1 );
put( properties, key, uiValue );
// because we can not iterate over the UI defaults keys while
// modifying UI defaults in the same loop, we have to copy the keys
if( defaultsKeysCache.size() != defaults.size() ) {
defaultsKeysCache.clear();
Enumeration<Object> e = defaults.keys();
while( e.hasMoreElements() )
defaultsKeysCache.add( e.nextElement() );
}
// replace all values in UI defaults that match the wildcard key
for( Object k : defaultsKeysCache ) {
if( k.equals( "Desktop.background" ) ||
k.equals( "DesktopIcon.background" ) ||
k.equals( "TabbedPane.focusColor" ) )
continue;
if( k instanceof String ) {
// support replacing of mapped keys
// (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, (String) k );
if( km.endsWith( tail ) && !((String)k).startsWith( "CheckBox.icon." ) )
defaults.put( k, uiValue );
}
}
} else
defaults.put( key, uiValue );
}
}
private boolean applyWildcard( Properties properties, String key, String value ) {
if( !key.startsWith( "*." ) )
return false;
String tail = key.substring( 1 );
// because we can not iterate over the properties keys while
// modifying properties in the same loop, we have to copy the keys
String[] keys = properties.keySet().toArray( new String[properties.size()] );
// replace all values in properties that match the wildcard key
for( String k : keys ) {
if( k.startsWith( "*" ) ||
k.startsWith( "@" ) ||
k.startsWith( "HelpButton." ) ||
k.startsWith( "JX" ) ||
k.startsWith( "Jide" ) ||
k.startsWith( "ProgressBar.selection" ) ||
k.startsWith( "TitlePane." ) ||
k.startsWith( "ToggleButton.tab." ) ||
k.equals( "Desktop.background" ) ||
k.equals( "DesktopIcon.background" ) ||
k.equals( "TabbedPane.focusColor" ) ||
k.endsWith( ".hoverBackground" ) ||
k.endsWith( ".pressedBackground" ) )
continue;
// support replacing of mapped keys
// (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, k );
if( km.endsWith( tail ) && !k.startsWith( "CheckBox.icon." ) )
put( properties, k, value );
}
// Note: also add wildcards to properties and let UIDefaultsLoader
// process it on BasicLookAndFeel UI defaults
put( properties, key, value );
return true;
}
private String fixColorIfValid( String newColorStr, String colorStr ) {
try {
// check whether it is valid
@@ -529,11 +497,11 @@ public class IntelliJTheme
}
}
private void applyIconsColorPalette( Properties properties ) {
if( jsonIcons == null )
private void applyColorPalette( UIDefaults defaults ) {
if( icons == null )
return;
Object palette = jsonIcons.get( "ColorPalette" );
Object palette = icons.get( "ColorPalette" );
if( !(palette instanceof Map) )
return;
@@ -542,48 +510,44 @@ public class IntelliJTheme
for( Map.Entry<String, Object> e : colorPalette.entrySet() ) {
String key = e.getKey();
Object value = e.getValue();
if( key.startsWith( "Checkbox." ) || key.startsWith( "#" ) || !(value instanceof String) )
if( key.startsWith( "Checkbox." ) || !(value instanceof String) )
continue;
if( dark )
key = StringUtils.removeTrailing( key, ".Dark" );
String color = toColor( (String) value );
ColorUIResource color = toColor( (String) value );
if( color != null )
put( properties, key, color );
defaults.put( key, color );
}
}
private String toColor( String value ) {
if( value.startsWith( "##" ) )
value = fixColorIfValid( value.substring( 1 ), value );
private ColorUIResource toColor( String value ) {
// map named colors
String color = namedColors.get( value );
ColorUIResource color = namedColors.get( value );
// parse color
return (color != null) ? color : (canParseColor( value ) ? value : null);
return (color != null) ? color : parseColor( value );
}
private boolean canParseColor( String value ) {
private ColorUIResource parseColor( String value ) {
try {
return UIDefaultsLoader.parseColor( value ) != null;
return UIDefaultsLoader.parseColor( value );
} catch( IllegalArgumentException ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to parse color: '" + value + '\'', ex );
return false;
return null;
}
}
/**
* Because IDEA uses SVGs for check boxes and radio buttons, the colors for
* these two components are specified in "icons > ColorPalette".
* FlatLaf uses vector icons and expects colors for the two components in properties.
* FlatLaf uses vector icons and expects colors for the two components in UI defaults.
*/
private void applyCheckBoxColors( Properties properties ) {
if( jsonIcons == null )
private void applyCheckBoxColors( UIDefaults defaults ) {
if( icons == null )
return;
Object palette = jsonIcons.get( "ColorPalette" );
Object palette = icons.get( "ColorPalette" );
if( !(palette instanceof Map) )
return;
@@ -605,9 +569,9 @@ public class IntelliJTheme
if( !dark && newKey.startsWith( checkBoxIconPrefix ) )
newKey = "CheckBox.icon[filled].".concat( newKey.substring( checkBoxIconPrefix.length() ) );
String color = toColor( (String) value );
ColorUIResource color = toColor( (String) value );
if( color != null ) {
put( properties, newKey, color );
defaults.put( newKey, color );
String key2 = checkboxDuplicateColors.get( key + ".Dark");
if( key2 != null ) {
@@ -628,7 +592,7 @@ public class IntelliJTheme
String newKey2 = checkboxKeyMapping.get( key2 );
if( newKey2 != null )
put( properties, newKey2, color );
defaults.put( newKey2, color );
}
}
@@ -639,13 +603,13 @@ public class IntelliJTheme
// update hover, pressed and focused colors
if( checkboxModified ) {
// for non-filled checkbox/radiobutton used in dark themes
properties.remove( "CheckBox.icon.focusWidth" );
put( properties, "CheckBox.icon.hoverBorderColor", properties.get( "CheckBox.icon.focusedBorderColor" ) );
defaults.remove( "CheckBox.icon.focusWidth" );
defaults.put( "CheckBox.icon.hoverBorderColor", defaults.get( "CheckBox.icon.focusedBorderColor" ) );
// for filled checkbox/radiobutton used in light themes
properties.remove( "CheckBox.icon[filled].focusWidth" );
put( properties, "CheckBox.icon[filled].hoverBorderColor", properties.get( "CheckBox.icon[filled].focusedBorderColor" ) );
put( properties, "CheckBox.icon[filled].focusedSelectedBackground", properties.get( "CheckBox.icon[filled].selectedBackground" ) );
defaults.remove( "CheckBox.icon[filled].focusWidth" );
defaults.put( "CheckBox.icon[filled].hoverBorderColor", defaults.get( "CheckBox.icon[filled].focusedBorderColor" ) );
defaults.put( "CheckBox.icon[filled].focusedSelectedBackground", defaults.get( "CheckBox.icon[filled].selectedBackground" ) );
if( dark ) {
// IDEA Darcula checkBoxFocused.svg, checkBoxSelectedFocused.svg,
@@ -659,16 +623,22 @@ public class IntelliJTheme
"CheckBox.icon[filled].focusedSelectedBorderColor",
};
for( String key : focusedBorderColorKeys ) {
Object color = properties.get( key );
if( color != null )
put( properties, key, "fade(" + color + ", 65%)" );
Color color = defaults.getColor( key );
if( color != null ) {
defaults.put( key, new ColorUIResource( new Color(
(color.getRGB() & 0xffffff) | 0xa6000000, true ) ) );
}
}
}
}
}
private static final Set<String> uiKeyExcludesStartsWith;
private static final String[] uiKeyExcludesContains;
private void copyIfNotSet( UIDefaults defaults, String destKey, String srcKey, Set<String> uiKeys ) {
if( !uiKeys.contains( destKey ) )
defaults.put( destKey, defaults.get( srcKey ) );
}
private static final Set<String> uiKeyExcludes;
private static final Set<String> uiKeyDoNotOverride;
/** Rename UI default keys (key --> value). */
private static final Map<String, String> uiKeyMapping = new HashMap<>();
@@ -680,30 +650,29 @@ public class IntelliJTheme
static {
// IntelliJ UI properties that are not used in FlatLaf
uiKeyExcludesStartsWith = new HashSet<>( Arrays.asList(
uiKeyExcludes = new HashSet<>( Arrays.asList(
"ActionButton.", "ActionToolbar.", "ActionsList.", "AppInspector.", "AssignedMnemonic.", "Autocomplete.",
"AvailableMnemonic.",
"Badge.", "Banner.", "BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
"BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
"BookmarkMnemonicCurrent.", "BookmarkMnemonicIcon.", "Borders.", "Breakpoint.",
"Canvas.", "CellEditor.", "Code.", "CodeWithMe.", "ColumnControlButton.", "CombinedDiff.", "ComboBoxButton.",
"CompilationCharts.", "CompletionPopup.", "ComplexPopup.", "Content.", "ContextHelp.", "CurrentMnemonic.", "Counter.",
"Debugger.", "DebuggerPopup.", "DebuggerTabs.", "DefaultTabs.", "Dialog.", "DialogWrapper.",
"DisclosureButton.", "DragAndDrop.",
"Canvas.", "CodeWithMe.", "ComboBoxButton.", "CompletionPopup.", "ComplexPopup.", "Content.",
"CurrentMnemonic.", "Counter.",
"Debugger.", "DebuggerPopup.", "DebuggerTabs.", "DefaultTabs.", "Dialog.", "DialogWrapper.", "DragAndDrop.",
"Editor.", "EditorGroupsTabs.", "EditorTabs.",
"FileColor.", "FindPopup.", "FlameGraph.", "Focus.",
"FileColor.", "FlameGraph.", "Focus.",
"Git.", "Github.", "GotItTooltip.", "Group.", "Gutter.", "GutterTooltip.",
"HeaderColor.", "HelpTooltip.", "Hg.",
"IconBadge.", "InformationHint.", "InlineBanner.", "InplaceRefactoringPopup.",
"Lesson.", "LineProfiler.", "Link.", "LiveIndicator.",
"MainMenu.", "MainToolbar.", "MainWindow.", "MemoryIndicator.", "MlModelBinding.", "MnemonicIcon.",
"IconBadge.", "InformationHint.", "InplaceRefactoringPopup.",
"Lesson.", "Link.", "LiveIndicator.",
"MainMenu.", "MainToolbar.", "MemoryIndicator.", "MlModelBinding.", "MnemonicIcon.",
"NavBar.", "NewClass.", "NewPSD.", "Notification.", "Notifications.", "NotificationsToolwindow.",
"OnePixelDivider.", "OptionButton.", "Outline.",
"ParameterInfo.", "PresentationAssistant.", "Plugins.", "Profiler.", "ProgressIcon.", "PsiViewer.",
"Resizable.", "Review.", "ReviewList.", "RunToolbar.", "RunWidget.",
"ParameterInfo.", "Plugins.", "ProgressIcon.", "PsiViewer.",
"ReviewList.", "RunWidget.",
"ScreenView.", "SearchEverywhere.", "SearchFieldWithExtension.", "SearchMatch.", "SearchOption.",
"SearchResults.", "SegmentedButton.", "Settings.", "SidePanel.", "Space.", "SpeedSearch.", "StateWidget.",
"StatusBar.", "StripeToolbar.",
"Tag.", "TipOfTheDay.", "ToolbarComboWidget.", "ToolWindow.", "TrialWidget.",
"StatusBar.",
"Tag.", "TipOfTheDay.", "ToolbarComboWidget.", "ToolWindow.",
"UIDesigner.", "UnattendedHostStatus.",
"ValidationTooltip.", "VersionControl.",
"WelcomeScreen.",
@@ -714,24 +683,11 @@ public class IntelliJTheme
// possible typos in .theme.json files
"Checkbox.", "Toolbar.", "Tooltip.", "UiDesigner.", "link."
) );
uiKeyExcludesContains = new String[] {
".darcula."
};
uiKeyDoNotOverride = new HashSet<>( Arrays.asList(
"TabbedPane.selectedForeground"
) );
// "*."
uiKeyMapping.put( "*.fontFace", "" ); // ignore (used in OnePauintxi themes)
uiKeyMapping.put( "*.fontSize", "" ); // ignore (used in OnePauintxi themes)
// Button
uiKeyMapping.put( "Button.minimumSize", "" ); // ignore (used in Material Theme UI Lite)
// CheckBox.iconSize
uiKeyMapping.put( "CheckBox.iconSize", "" ); // ignore (used in Rider themes)
// ComboBox
uiKeyMapping.put( "ComboBox.background", "" ); // ignore
uiKeyMapping.put( "ComboBox.buttonBackground", "" ); // ignore
@@ -740,17 +696,14 @@ public class IntelliJTheme
uiKeyMapping.put( "ComboBox.ArrowButton.disabledIconColor", "ComboBox.buttonDisabledArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
uiKeyCopying.put( "ComboBox.buttonSeparatorColor", "Component.borderColor" );
uiKeyCopying.put( "ComboBox.buttonDisabledSeparatorColor", "Component.disabledBorderColor" );
// Component
uiKeyMapping.put( "Component.inactiveErrorFocusColor", "Component.error.borderColor" );
uiKeyMapping.put( "Component.errorFocusColor", "Component.error.focusedBorderColor" );
uiKeyMapping.put( "Component.inactiveWarningFocusColor", "Component.warning.borderColor" );
uiKeyMapping.put( "Component.warningFocusColor", "Component.warning.focusedBorderColor" );
uiKeyMapping.put( "Component.inactiveSuccessFocusColor", "Component.success.borderColor" );
uiKeyMapping.put( "Component.successFocusColor", "Component.success.focusedBorderColor" );
// Label
uiKeyMapping.put( "Label.disabledForegroundColor", "" ); // ignore (used in Material Theme UI Lite)
// Link
uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" );
@@ -758,7 +711,10 @@ public class IntelliJTheme
// Menu
uiKeyMapping.put( "Menu.border", "Menu.margin" );
uiKeyMapping.put( "MenuItem.border", "MenuItem.margin" );
uiKeyCopying.put( "CheckBoxMenuItem.margin", "MenuItem.margin" );
uiKeyCopying.put( "RadioButtonMenuItem.margin", "MenuItem.margin" );
uiKeyMapping.put( "PopupMenu.border", "PopupMenu.borderInsets" );
uiKeyCopying.put( "MenuItem.underlineSelectionColor", "TabbedPane.underlineColor" );
// IDEA uses List.selectionBackground also for menu selection
uiKeyCopying.put( "Menu.selectionBackground", "List.selectionBackground" );
@@ -766,14 +722,13 @@ public class IntelliJTheme
uiKeyCopying.put( "CheckBoxMenuItem.selectionBackground", "List.selectionBackground" );
uiKeyCopying.put( "RadioButtonMenuItem.selectionBackground", "List.selectionBackground" );
// ProgressBar: IDEA uses ProgressBar.trackColor and ProgressBar.progressColor
// ProgressBar
uiKeyMapping.put( "ProgressBar.background", "" ); // ignore
uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
// RadioButton
uiKeyMapping.put( "RadioButton.iconSize", "" ); // ignore (used in Rider themes)
uiKeyCopying.put( "ProgressBar.selectionForeground", "ProgressBar.background" );
uiKeyCopying.put( "ProgressBar.selectionBackground", "ProgressBar.foreground" );
// ScrollBar
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
@@ -783,30 +738,34 @@ public class IntelliJTheme
uiKeyMapping.put( "Separator.separatorColor", "Separator.foreground" );
// Slider
uiKeyMapping.put( "Slider.buttonColor", "Slider.thumbColor" );
uiKeyMapping.put( "Slider.buttonBorderColor", "" ); // ignore
uiKeyMapping.put( "Slider.thumb", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "Slider.track", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "Slider.trackDisabled", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite)
uiKeyCopying.put( "Slider.trackValueColor", "ProgressBar.foreground" );
uiKeyCopying.put( "Slider.thumbColor", "ProgressBar.foreground" );
uiKeyCopying.put( "Slider.trackColor", "ProgressBar.background" );
// Spinner
uiKeyCopying.put( "Spinner.buttonSeparatorColor", "Component.borderColor" );
uiKeyCopying.put( "Spinner.buttonDisabledSeparatorColor", "Component.disabledBorderColor" );
// TabbedPane
uiKeyMapping.put( "DefaultTabs.underlinedTabBackground", "TabbedPane.selectedBackground" );
uiKeyMapping.put( "DefaultTabs.underlinedTabForeground", "TabbedPane.selectedForeground" );
uiKeyMapping.put( "DefaultTabs.inactiveUnderlineColor", "TabbedPane.inactiveUnderlineColor" );
uiKeyMapping.put( "TabbedPane.tabAreaInsets", "" ); // ignore (used in Material Theme UI Lite)
// TableHeader
uiKeyMapping.put( "TableHeader.cellBorder", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "TableHeader.height", "" ); // ignore (used in Material Theme UI Lite)
// TitlePane
uiKeyCopying.put( "TitlePane.inactiveBackground", "TitlePane.background" );
uiKeyMapping.put( "TitlePane.infoForeground", "TitlePane.foreground" );
uiKeyMapping.put( "TitlePane.inactiveInfoForeground", "TitlePane.inactiveForeground" );
for( Map.Entry<String, String> e : uiKeyMapping.entrySet() )
uiKeyInverseMapping.put( e.getValue(), e.getKey() );
uiKeyCopying.put( "ToggleButton.tab.underlineColor", "TabbedPane.underlineColor" );
uiKeyCopying.put( "ToggleButton.tab.disabledUnderlineColor", "TabbedPane.disabledUnderlineColor" );
uiKeyCopying.put( "ToggleButton.tab.selectedBackground", "TabbedPane.selectedBackground" );
uiKeyCopying.put( "ToggleButton.tab.hoverBackground", "TabbedPane.hoverColor" );
uiKeyCopying.put( "ToggleButton.tab.focusBackground", "TabbedPane.focusColor" );
checkboxKeyMapping.put( "Checkbox.Background.Default", "CheckBox.icon.background" );
checkboxKeyMapping.put( "Checkbox.Background.Disabled", "CheckBox.icon.disabledBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" );
@@ -859,15 +818,17 @@ public class IntelliJTheme
}
@Override
void applyAdditionalProperties( Properties properties ) {
theme.applyProperties( properties );
void applyAdditionalDefaults( UIDefaults defaults ) {
theme.applyProperties( defaults );
}
@Override
protected ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
ArrayList<Class<?>> lafClasses = UIDefaultsLoader.getLafClassesForDefaultsLoading( getClass() );
lafClasses.add( 1, theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );
lafClasses.add( 2, theme.dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
ArrayList<Class<?>> lafClasses = new ArrayList<>();
lafClasses.add( FlatLaf.class );
lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );
lafClasses.add( theme.dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
lafClasses.add( ThemeLaf.class );
return lafClasses;
}
}

View File

@@ -111,7 +111,7 @@ class LinuxFontPolicy
if( logicalFamily != null )
family = logicalFamily;
return createFontEx( family, style, size );
return createFontEx( family, style, size, dsize );
}
/**
@@ -121,9 +121,9 @@ class LinuxFontPolicy
* E.g. family 'URW Bookman Light' is not found, but 'URW Bookman' is found.
* If still not found, then font of family 'Dialog' is returned.
*/
private static Font createFontEx( String family, int style, int size ) {
private static Font createFontEx( String family, int style, int size, double dsize ) {
for(;;) {
Font font = FlatLaf.createCompositeFont( family, style, size );
Font font = createFont( family, style, size, dsize );
if( Font.DIALOG.equals( family ) )
return font;
@@ -135,7 +135,7 @@ class LinuxFontPolicy
// - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8)
FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font );
if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 )
return FlatLaf.createCompositeFont( Font.DIALOG, style, size );
return createFont( Font.DIALOG, style, size, dsize );
return font;
}
@@ -143,7 +143,7 @@ class LinuxFontPolicy
// find last word in family
int index = family.lastIndexOf( ' ' );
if( index < 0 )
return FlatLaf.createCompositeFont( Font.DIALOG, style, size );
return createFont( Font.DIALOG, style, size, dsize );
// check whether last work contains some font weight (e.g. Ultra-Bold or Heavy)
String lastWord = family.substring( index + 1 ).toLowerCase( Locale.ENGLISH );
@@ -155,6 +155,15 @@ class LinuxFontPolicy
}
}
private static Font createFont( String family, int style, int size, double dsize ) {
Font font = FlatLaf.createCompositeFont( family, style, size );
// set font size in floating points
font = font.deriveFont( style, (float) dsize );
return font;
}
private static double getGnomeFontScale() {
// do not scale font here if JRE scales
if( isSystemScaling() )
@@ -194,7 +203,7 @@ class LinuxFontPolicy
* Gets the default font for KDE from KDE configuration files.
*
* The Swing fonts are not updated when the user changes system font size
* (System Settings > Fonts > Force Font DPI). An application restart is necessary.
* (System Settings > Fonts > Force Font DPI). A application restart is necessary.
* This is the same behavior as in native KDE applications.
*
* The "display scale factor" (kdeglobals: [KScreen] > ScaleFactor) is not used
@@ -248,7 +257,7 @@ class LinuxFontPolicy
if( size < 1 )
size = 1;
return FlatLaf.createCompositeFont( family, style, size );
return createFont( family, style, size, dsize );
}
@SuppressWarnings( "MixedMutabilityReturnType" ) // Error Prone

View File

@@ -1,166 +0,0 @@
/*
* 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;
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Cancels (hides) popup menus on Linux.
* <p>
* On Linux, popups are not hidden under following conditions, which results in
* misplaced popups:
* <ul>
* <li>window moved or resized
* <li>window maximized or restored
* <li>window iconified
* <li>window deactivated (e.g. activated other application)
* </ul>
*
* On Windows and macOS, popups are automatically hidden.
* <p>
* The implementation is similar to what's done in
* {@code javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber},
* but only hides popup in some conditions.
*
* @author Karl Tauber
*/
class LinuxPopupMenuCanceler
extends WindowAdapter
implements ChangeListener, ComponentListener
{
private MenuElement[] lastPathSelectedPath;
private Window window;
LinuxPopupMenuCanceler() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
msm.addChangeListener( this );
lastPathSelectedPath = msm.getSelectedPath();
if( lastPathSelectedPath.length > 0 )
addWindowListeners( lastPathSelectedPath[0] );
}
void uninstall() {
MenuSelectionManager.defaultManager().removeChangeListener( this );
}
private void addWindowListeners( MenuElement selected ) {
removeWindowListeners();
// see BasicPopupMenuUI.MouseGrabber.grabWindow()
Component invoker = selected.getComponent();
if( invoker instanceof JPopupMenu )
invoker = ((JPopupMenu)invoker).getInvoker();
window = (invoker instanceof Window)
? (Window) invoker
: SwingUtilities.windowForComponent( invoker );
if( window != null ) {
window.addWindowListener( this );
window.addComponentListener( this );
}
}
private void removeWindowListeners() {
if( window != null ) {
window.removeWindowListener( this );
window.removeComponentListener( this );
window = null;
}
}
private void cancelPopupMenu() {
try {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
MenuElement[] selectedPath = msm.getSelectedPath();
for( MenuElement e : selectedPath ) {
if( e instanceof JPopupMenu )
((JPopupMenu)e).putClientProperty( "JPopupMenu.firePopupMenuCanceled", true );
}
msm.clearSelectedPath();
} catch( RuntimeException ex ) {
removeWindowListeners();
throw ex;
} catch( Error ex ) {
removeWindowListeners();
throw ex;
}
}
//---- ChangeListener ----
@Override
public void stateChanged( ChangeEvent e ) {
MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
if( selectedPath.length == 0 )
removeWindowListeners();
else if( lastPathSelectedPath.length == 0 )
addWindowListeners( selectedPath[0] );
lastPathSelectedPath = selectedPath;
}
//---- WindowListener ----
@Override
public void windowIconified( WindowEvent e ) {
cancelPopupMenu();
}
@Override
public void windowDeactivated( WindowEvent e ) {
cancelPopupMenu();
}
@Override
public void windowClosing( WindowEvent e ) {
cancelPopupMenu();
}
//---- ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
cancelPopupMenu();
}
@Override
public void componentMoved( ComponentEvent e ) {
cancelPopupMenu();
}
@Override
public void componentShown( ComponentEvent e ) {
}
@Override
public void componentHidden( ComponentEvent e ) {
cancelPopupMenu();
}
}

View File

@@ -45,7 +45,6 @@ import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Improves usability of submenus by using a
@@ -65,7 +64,6 @@ class SubMenuUsabilityHelper
// https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607
private static SubMenuUsabilityHelper instance;
private boolean eventQueuePushNotSupported;
private SubMenuEventQueue subMenuEventQueue;
private SafeTrianglePainter safeTrianglePainter;
private boolean changePending;
@@ -85,9 +83,6 @@ class SubMenuUsabilityHelper
if( instance != null )
return false;
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SUB_MENU_SAFE_TRIANGLE, true ) )
return false;
instance = new SubMenuUsabilityHelper();
MenuSelectionManager.defaultManager().addChangeListener( instance );
return true;
@@ -104,7 +99,7 @@ class SubMenuUsabilityHelper
@Override
public void stateChanged( ChangeEvent e ) {
if( eventQueuePushNotSupported || !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
if( !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
return;
// handle menu selection change later, but only once in case of temporary changes
@@ -177,30 +172,9 @@ debug*/
targetTopY = popupLocation.y;
targetBottomY = popupLocation.y + popupSize.height;
// install own event queue to suppress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null ) {
SubMenuEventQueue queue = new SubMenuEventQueue();
try {
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.getSystemEventQueue().push( queue );
// check whether push() worked
// (e.g. SWTSwing uses own event queue that does not support push())
if( toolkit.getSystemEventQueue() != queue ) {
eventQueuePushNotSupported = true;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push submenu event queue. Disabling submenu safe triangle.", null );
return;
}
subMenuEventQueue = queue;
} catch( RuntimeException ex ) {
// catch runtime exception from EventQueue.push()
eventQueuePushNotSupported = true;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push submenu event queue. Disabling submenu safe triangle.", ex );
return;
}
}
// install own event queue to supress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null )
subMenuEventQueue = new SubMenuEventQueue();
// create safe triangle painter
if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) )
@@ -273,6 +247,8 @@ debug*/
}
} );
timeoutTimer.setRepeats( false );
Toolkit.getDefaultToolkit().getSystemEventQueue().push( this );
}
void uninstall() {

View File

@@ -25,15 +25,12 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -44,9 +41,6 @@ import java.util.Locale;
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;
import javax.swing.UIDefaults;
@@ -67,6 +61,7 @@ import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SoftCache;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -90,14 +85,15 @@ class UIDefaultsLoader
private static final String WILDCARD_PREFIX = "*.";
static final String KEY_VARIABLES = "FlatLaf.internal.variables";
static final String KEY_PROPERTIES = "FlatLaf.internal.properties";
private static int parseColorDepth;
private static Map<String, ColorUIResource> systemColorCache;
private static final SoftCache<String, Object> fontCache = new SoftCache<>();
static ArrayList<Class<?>> getLafClassesForDefaultsLoading( Class<?> lookAndFeelClass ) {
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
// determine classes in class hierarchy in reverse order
ArrayList<Class<?>> lafClasses = new ArrayList<>();
for( Class<?> lafClass = lookAndFeelClass;
@@ -106,62 +102,20 @@ class UIDefaultsLoader
{
lafClasses.add( 0, lafClass );
}
return lafClasses;
}
static Properties newUIProperties( boolean dark ) {
// UI key prefixes
String lightOrDarkPrefix = FlatLaf.getUIKeyLightOrDarkPrefix( dark );
Set<String> platformPrefixes = FlatLaf.getUIKeyPlatformPrefixes();
Set<String> specialPrefixes = FlatLaf.getUIKeySpecialPrefixes();
return new Properties() {
@Override
public void load( InputStream in ) throws IOException {
// use UTF-8 to load properties file
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
super.load( reader );
}
}
@Override
public synchronized Object put( Object k, Object value ) {
// process key prefixes (while loading properties files)
String key = (String) k;
while( key.startsWith( "[" ) ) {
int closeIndex = key.indexOf( ']' );
if( closeIndex < 0 )
return null; // ignore property with invalid prefix
String prefix = key.substring( 0, closeIndex + 1 );
if( specialPrefixes.contains( prefix ) )
break; // keep special prefix
if( !lightOrDarkPrefix.equals( prefix ) && !platformPrefixes.contains( prefix ) )
return null; // ignore property
// prefix is known and enabled --> remove prefix
key = key.substring( closeIndex + 1 );
}
return super.put( key, value );
}
};
loadDefaultsFromProperties( lafClasses, addons, additionalDefaults, dark, defaults );
}
static void loadDefaultsFromProperties( List<Class<?>> lafClasses, List<FlatDefaultsAddon> addons,
Consumer<Properties> intellijThemesHook, Properties additionalDefaults, boolean dark, UIDefaults defaults )
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
try {
// temporary cache system colors while loading defaults,
// which avoids that system color getter is invoked multiple times
systemColorCache = (FlatLaf.getSystemColorGetter() != null) ? new HashMap<>() : null;
// all properties files will be loaded into this map
Properties properties = newUIProperties( dark );
// load core properties files
Properties properties = new Properties();
for( Class<?> lafClass : lafClasses ) {
String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
@@ -188,10 +142,6 @@ class UIDefaultsLoader
addonClassLoaders.add( addonClassLoader );
}
// apply IntelliJ themes properties
if( intellijThemesHook != null )
intellijThemesHook.accept( properties );
// load custom properties files (usually provided by applications)
List<Object> customDefaultsSources = FlatLaf.getCustomDefaultsSources();
int size = (customDefaultsSources != null) ? customDefaultsSources.size() : 0;
@@ -210,48 +160,18 @@ class UIDefaultsLoader
if( classLoader == null )
classLoader = FlatLaf.class.getClassLoader();
boolean found = false;
for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
String propertiesName = packageName + '/' + lafClass.getSimpleName() + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null ) {
if( in != null )
properties.load( in );
found = true;
}
}
}
// fallback for named Java modules
if( !found ) {
// Get package URL using ClassLoader.getResource(...) because this works
// also in named Java modules, even without opening the package in module-info.java.
// This extra step is necessary because ClassLoader.getResource("<package>/<file>.properties")
// does not work for named Java modules.
URL url = classLoader.getResource( packageName );
if( url == null )
continue;
String packageUrl = url.toExternalForm();
if( !packageUrl.endsWith( "/" ) )
packageUrl = packageUrl.concat( "/" );
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in );
} catch( FileNotFoundException ex ) {
// ignore
}
}
}
} else if( source instanceof URL ) {
// load from package URL
String packageUrl = ((URL)source).toExternalForm();
if( !packageUrl.endsWith( "/" ) )
packageUrl = packageUrl.concat( "/" );
URL packageUrl = (URL) source;
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
URL propertiesUrl = new URL( packageUrl + lafClass.getSimpleName() + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in );
@@ -263,7 +183,7 @@ class UIDefaultsLoader
// load from folder
File folder = (File) source;
for( Class<?> lafClass : lafClasses ) {
File propertiesFile = new File( folder, simpleClassName( lafClass ) + ".properties" );
File propertiesFile = new File( folder, lafClass.getSimpleName() + ".properties" );
if( !propertiesFile.isFile() )
continue;
@@ -278,6 +198,41 @@ class UIDefaultsLoader
if( additionalDefaults != null )
properties.putAll( additionalDefaults );
// collect all platform specific keys (but do not modify properties)
ArrayList<String> platformSpecificKeys = new ArrayList<>();
for( Object okey : properties.keySet() ) {
String key = (String) okey;
if( key.startsWith( "[" ) &&
(key.startsWith( "[win]" ) ||
key.startsWith( "[mac]" ) ||
key.startsWith( "[linux]" ) ||
key.startsWith( "[light]" ) ||
key.startsWith( "[dark]" )) )
platformSpecificKeys.add( key );
}
// remove platform specific properties and re-add only properties
// for current platform, but with platform prefix removed
if( !platformSpecificKeys.isEmpty() ) {
// handle light/dark specific properties
String lightOrDarkPrefix = dark ? "[dark]" : "[light]";
for( String key : platformSpecificKeys ) {
if( key.startsWith( lightOrDarkPrefix ) )
properties.put( key.substring( lightOrDarkPrefix.length() ), properties.remove( key ) );
}
// handle platform specific properties
String platformPrefix =
SystemInfo.isWindows ? "[win]" :
SystemInfo.isMacOS ? "[mac]" :
SystemInfo.isLinux ? "[linux]" : "[unknown]";
for( String key : platformSpecificKeys ) {
Object value = properties.remove( key );
if( key.startsWith( platformPrefix ) )
properties.put( key.substring( platformPrefix.length() ), value );
}
}
// get (and remove) wildcard replacements, which override all other defaults that end with same suffix
HashMap<String, String> wildcards = new HashMap<>();
Iterator<Entry<Object, Object>> it = properties.entrySet().iterator();
@@ -332,15 +287,6 @@ class UIDefaultsLoader
// remember variables in defaults to allow using them in styles
defaults.put( KEY_VARIABLES, variables );
// remember properties (for testing)
if( FlatSystemProperties.getBoolean( KEY_PROPERTIES, false ) ) {
Properties properties2 = new Properties();
properties2.putAll( properties );
for( Map.Entry<String, String> e : wildcards.entrySet() )
properties2.put( WILDCARD_PREFIX + e.getKey(), e.getValue() );
defaults.put( KEY_PROPERTIES, properties2 );
}
// clear/disable system color cache
systemColorCache = null;
} catch( IOException ex ) {
@@ -348,14 +294,6 @@ class UIDefaultsLoader
}
}
/**
* Similar to Class.getSimpleName(), but includes enclosing class for nested classes.
*/
static String simpleClassName( Class<?> cls ) {
String className = cls.getName();
return className.substring( className.lastIndexOf( '.' ) + 1 );
}
static void logParseError( String key, String value, RuntimeException ex, boolean severe ) {
String message = "FlatLaf: Failed to parse: '" + key + '=' + value + '\'';
if( severe )
@@ -432,7 +370,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 AtomicReference<ValueType> tempResultValueType = new AtomicReference<>();
private static final ValueType[] tempResultValueType = new ValueType[1];
private static Map<Class<?>, ValueType> javaValueTypes;
private static Map<String, ValueType> knownValueTypes;
@@ -442,7 +380,7 @@ class UIDefaultsLoader
return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() );
}
static Object parseValue( String key, String value, Class<?> javaValueType, AtomicReference<ValueType> resultValueType,
static Object parseValue( String key, String value, Class<?> javaValueType, ValueType[] resultValueType,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
throws IllegalArgumentException
{
@@ -451,7 +389,7 @@ class UIDefaultsLoader
// do not parse styles here
if( key.startsWith( "[style]" ) ) {
resultValueType.set( ValueType.STRING );
resultValueType[0] = ValueType.STRING;
return value;
}
@@ -459,7 +397,7 @@ class UIDefaultsLoader
// null
if( value.equals( "null" ) || value.isEmpty() ) {
resultValueType.set( ValueType.NULL );
resultValueType[0] = ValueType.NULL;
return null;
}
@@ -515,14 +453,14 @@ class UIDefaultsLoader
} else {
// false, true
switch( value ) {
case "false": resultValueType.set( ValueType.BOOLEAN ); return false;
case "true": resultValueType.set( ValueType.BOOLEAN ); return true;
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType.set( ValueType.LAZY );
resultValueType[0] = ValueType.LAZY;
String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 );
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
@@ -603,7 +541,7 @@ class UIDefaultsLoader
}
}
resultValueType.set( valueType );
resultValueType[0] = valueType;
// parse value
switch( valueType ) {
@@ -630,14 +568,14 @@ class UIDefaultsLoader
default:
// string
if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
resultValueType.set( ValueType.STRING );
resultValueType[0] = ValueType.STRING;
return value.substring( 1, value.length() - 1 );
}
// colors
if( value.startsWith( "#" ) || value.endsWith( ")" ) ) {
Object color = parseColorOrFunction( value, resolver );
resultValueType.set( (color != null) ? ValueType.COLOR : ValueType.NULL );
resultValueType[0] = (color != null) ? ValueType.COLOR : ValueType.NULL;
return color;
}
@@ -649,7 +587,7 @@ class UIDefaultsLoader
// integer
try {
Integer integer = parseInteger( value );
resultValueType.set( ValueType.INTEGER );
resultValueType[0] = ValueType.INTEGER;
return integer;
} catch( NumberFormatException ex ) {
// ignore
@@ -658,7 +596,7 @@ class UIDefaultsLoader
// float
try {
Float f = parseFloat( value );
resultValueType.set( ValueType.FLOAT );
resultValueType[0] = ValueType.FLOAT;
return f;
} catch( NumberFormatException ex ) {
// ignore
@@ -666,7 +604,7 @@ class UIDefaultsLoader
}
// string
resultValueType.set( ValueType.STRING );
resultValueType[0] = ValueType.STRING;
return value;
}
}
@@ -691,26 +629,18 @@ class UIDefaultsLoader
if( value.indexOf( ',' ) >= 0 ) {
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' );
try {
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
? parseFloat( parts.get( 5 ) )
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f;
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0;
return (LazyValue) t -> {
return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
} catch( RuntimeException ex ) {
throw new IllegalArgumentException( "invalid border '" + value + "' (" + ex.getMessage() + ")" );
}
return (LazyValue) t -> {
return (lineColor != null)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
} else
return parseInstance( value, resolver, addonClassLoaders );
}
@@ -781,7 +711,7 @@ class UIDefaultsLoader
Integer.parseInt( numbers.get( 1 ) ),
Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid insets '" + value + "'" );
}
}
@@ -794,7 +724,7 @@ class UIDefaultsLoader
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid size '" + value + "'" );
}
}
@@ -888,7 +818,6 @@ class UIDefaultsLoader
try {
switch( function ) {
case "if": return parseColorIf( value, params, resolver );
case "lazy": return parseColorLazy( value, params, resolver );
case "systemColor": return parseColorSystemColor( value, params, resolver );
case "rgb": return parseColorRgbOrRgba( false, params, resolver );
case "rgba": return parseColorRgbOrRgba( true, params, resolver );
@@ -936,32 +865,6 @@ class UIDefaultsLoader
return parseColorOrFunction( resolver.apply( ifValue ), resolver );
}
/**
* Syntax: lazy(uiKey)
* <p>
* This "lazy" function is only used if the "lazy" is passed as parameter to another
* color function. Otherwise, the general "lazy" function is used.
* <p>
* Note: The color is resolved immediately, not lazy, because it is passed as parameter to another color function.
* So e.g. {@code darken(lazy(List.background), 10%)} is the same as {@code darken($List.background, 10%)}.
* <p>
* Only useful if a property is defined as lazy and that property is used
* in another property's color function. E.g.
*
* <pre>{@code
* someProperty = lazy(List.background)
* anotherProperty = darken($someProperty, 10%)
* }</pre>
*/
private static Object parseColorLazy( String value, List<String> params, Function<String, String> resolver )
throws IllegalArgumentException
{
if( params.size() != 1 )
throw newMissingParametersException( value );
return parseColorOrFunction( resolver.apply( PROPERTY_PREFIX + params.get( 0 ) ), resolver );
}
/**
* Syntax: systemColor(name[,defaultValue])
* - name: system color name
@@ -1059,7 +962,7 @@ class UIDefaultsLoader
* fadein(color,amount[,options]) or fadeout(color,amount[,options])
* - color: a color (e.g. #f00) or a color function
* - amount: percentage 0-100%
* - options: [relative] [autoInverse] [noAutoInverse] [derived] [lazy]
* - options: [relative] [autoInverse] [noAutoInverse] [lazy] [derived]
*/
private static Object parseColorHSLIncreaseDecrease( int hslIndex, boolean increase,
List<String> params, Function<String, String> resolver )
@@ -1069,15 +972,15 @@ class UIDefaultsLoader
int amount = parsePercentage( params.get( 1 ) );
boolean relative = false;
boolean autoInverse = false;
boolean derived = false;
boolean lazy = false;
boolean derived = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
derived = options.contains( "derived" );
// use autoInverse by default for derived colors, except if noAutoInverse is set
if( derived && !options.contains( "noAutoInverse" ) )
@@ -1088,8 +991,14 @@ class UIDefaultsLoader
ColorFunction function = new ColorFunctions.HSLIncreaseDecrease(
hslIndex, increase, amount, relative, autoInverse );
if( lazy )
return newLazyColorFunction( colorStr, function );
if( lazy ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( colorStr );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
@@ -1118,8 +1027,14 @@ class UIDefaultsLoader
// create function
ColorFunction function = new ColorFunctions.Fade( amount );
if( lazy )
return newLazyColorFunction( colorStr, function );
if( lazy ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( colorStr );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
@@ -1129,7 +1044,7 @@ class UIDefaultsLoader
* Syntax: spin(color,angle[,options])
* - color: a color (e.g. #f00) or a color function
* - angle: number of degrees to rotate
* - options: [derived] [lazy]
* - options: [derived]
*/
private static Object parseColorSpin( List<String> params, Function<String, String> resolver )
throws IllegalArgumentException
@@ -1137,20 +1052,15 @@ class UIDefaultsLoader
String colorStr = params.get( 0 );
int amount = parseInteger( params.get( 1 ) );
boolean derived = false;
boolean lazy = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
// create function
ColorFunction function = new ColorFunctions.HSLIncreaseDecrease( 0, true, amount, false, false );
if( lazy )
return newLazyColorFunction( colorStr, function );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
}
@@ -1162,7 +1072,7 @@ class UIDefaultsLoader
* changeAlpha(color,value[,options])
* - color: a color (e.g. #f00) or a color function
* - value: for hue: number of degrees; otherwise: percentage 0-100%
* - options: [derived] [lazy]
* - options: [derived]
*/
private static Object parseColorChange( int hslIndex,
List<String> params, Function<String, String> resolver )
@@ -1173,33 +1083,27 @@ class UIDefaultsLoader
? parseInteger( params.get( 1 ) )
: parsePercentage( params.get( 1 ) );
boolean derived = false;
boolean lazy = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
// create function
ColorFunction function = new ColorFunctions.HSLChange( hslIndex, value );
if( lazy )
return newLazyColorFunction( colorStr, function );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
}
/**
* Syntax: mix(color1,color2[,weight][,options]) or
* tint(color[,weight][,options]) or
* shade(color[,weight][,options])
* Syntax: mix(color1,color2[,weight]) or
* tint(color[,weight]) or
* shade(color[,weight])
* - color1: a color (e.g. #f00) or a color function
* - color2: a color (e.g. #f00) or a color function
* - weight: the weight (in range 0-100%) to mix the two colors
* larger weight uses more of first color, smaller weight more of second color
* - options: [derived] [lazy]
*/
private static Object parseColorMix( String color1Str, List<String> params, Function<String, String> resolver )
throws IllegalArgumentException
@@ -1208,36 +1112,18 @@ class UIDefaultsLoader
if( color1Str == null )
color1Str = params.get( i++ );
String color2Str = params.get( i++ );
int weight = 50;
boolean derived = false;
boolean lazy = false;
if( params.size() > i ) {
String weightStr = params.get( i );
if( !weightStr.isEmpty() && Character.isDigit( weightStr.charAt( 0 ) ) ) {
weight = parsePercentage( weightStr );
i++;
}
}
if( params.size() > i ) {
String options = params.get( i );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50;
// parse second color
ColorUIResource color1 = (ColorUIResource) parseColorOrFunction( resolver.apply( color1Str ), resolver );
if( color1 == null )
ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver );
if( color2 == null )
return null;
// create function
ColorFunction function = new ColorFunctions.Mix2( color1, weight );
if( lazy )
return newLazyColorFunction( color2Str, function );
ColorFunction function = new ColorFunctions.Mix( color2, weight );
// parse first color, apply function and create mixed color
return parseFunctionBaseColor( color2Str, function, derived, resolver );
return parseFunctionBaseColor( color1Str, function, false, resolver );
}
/**
@@ -1331,15 +1217,6 @@ class UIDefaultsLoader
return new ColorUIResource( newColor );
}
private static LazyValue newLazyColorFunction( String uiKey, ColorFunction function ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( uiKey );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
/**
* Syntax: [normal] [bold|+bold|-bold] [italic|+italic|-italic] [<size>|+<incr>|-<decr>|<percent>%] [family[, family]] [$baseFontKey]
*/
@@ -1425,17 +1302,17 @@ class UIDefaultsLoader
break;
}
}
} catch( RuntimeException | IOException ex ) {
throw new IllegalArgumentException( "invalid font '" + value + "' (" + ex.getMessage() + ")" );
} catch( IOException ex ) {
throw new IllegalArgumentException( ex );
}
if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "invalid font '" + value + "': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" );
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+bold' and '-bold'" );
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+italic' and '-italic'" );
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
}
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
@@ -1575,7 +1452,7 @@ class UIDefaultsLoader
return (LazyValue) t -> {
return new GrayFilter( brightness, contrast, alpha );
};
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
}
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.util.UIScale.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
@@ -28,7 +29,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
@@ -36,15 +37,10 @@ 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,13 +57,8 @@ public abstract class FlatAbstractIcon
// g2.setColor( Color.blue );
// g2.drawRect( x, y, getIconWidth() - 1, getIconHeight() - 1 );
paintBackground( c, g2, x, y );
g2.translate( x, y );
UIScale.scaleGraphics( g2 );
float scale = getScale();
if( scale != 1 )
g2.scale( scale, scale );
if( color != null )
g2.setColor( color );
@@ -78,71 +69,15 @@ public abstract class FlatAbstractIcon
}
}
/**
* 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 ) {
}
protected abstract void paintIcon( Component c, Graphics2D g2 );
/**
* 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( UIScale.scale( width ) );
return scale( width );
}
/**
* Returns the scaled icon height.
*/
@Override
public int getIconHeight() {
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 );
return scale( height );
}
}

View File

@@ -17,6 +17,7 @@
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;
@@ -25,8 +26,7 @@ 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.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -36,11 +36,8 @@ 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;
@@ -48,6 +45,23 @@ 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 ) {
/*

View File

@@ -17,20 +17,19 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.FlatClientProperties.*;
import static com.formdev.flatlaf.ui.FlatUIUtils.stateColor;
import java.awt.BasicStroke;
import java.awt.Color;
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;
/**
@@ -49,8 +48,6 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @uiDefault CheckBox.icon.borderWidth int or float optional; defaults to Component.borderWidth
* @uiDefault CheckBox.icon.selectedBorderWidth int or float optional; defaults to CheckBox.icon.borderWidth
* @uiDefault CheckBox.icon.disabledSelectedBorderWidth int or float optional; defaults to CheckBox.icon.selectedBorderWidth
* @uiDefault CheckBox.icon.indeterminateBorderWidth int or float optional; defaults to CheckBox.icon.selectedBorderWidth
* @uiDefault CheckBox.icon.disabledIndeterminateBorderWidth int or float optional; defaults to CheckBox.icon.disabledSelectedBorderWidth
* @uiDefault CheckBox.arc int
*
* @uiDefault CheckBox.icon.focusColor Color optional; defaults to Component.focusColor
@@ -59,52 +56,35 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @uiDefault CheckBox.icon.selectedBorderColor Color
* @uiDefault CheckBox.icon.selectedBackground Color
* @uiDefault CheckBox.icon.checkmarkColor Color
* @uiDefault CheckBox.icon.indeterminateBorderColor Color optional; defaults to CheckBox.icon.selectedBorderColor
* @uiDefault CheckBox.icon.indeterminateBackground Color optional; defaults to CheckBox.icon.selectedBackground
* @uiDefault CheckBox.icon.indeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
*
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.disabledBackground Color
* @uiDefault CheckBox.icon.disabledSelectedBorderColor Color optional; defaults to CheckBox.icon.disabledBorderColor
* @uiDefault CheckBox.icon.disabledSelectedBackground Color optional; defaults to CheckBox.icon.disabledBackground
* @uiDefault CheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBox.icon.disabledIndeterminateBorderColor Color optional; defaults to CheckBox.icon.disabledSelectedBorderColor
* @uiDefault CheckBox.icon.disabledIndeterminateBackground Color optional; defaults to CheckBox.icon.disabledSelectedBackground
* @uiDefault CheckBox.icon.disabledIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.disabledCheckmarkColor
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.disabledBackground Color
* @uiDefault CheckBox.icon.disabledSelectedBorderColor Color optional; CheckBox.icon.disabledBorderColor is used if not specified
* @uiDefault CheckBox.icon.disabledSelectedBackground Color optional; CheckBox.icon.disabledBackground is used if not specified
* @uiDefault CheckBox.icon.disabledCheckmarkColor Color
*
* @uiDefault CheckBox.icon.focusedBorderColor Color optional
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.focusedSelectedBorderColor Color optional; defaults to CheckBox.icon.focusedBorderColor
* @uiDefault CheckBox.icon.focusedSelectedBackground Color optional; defaults to CheckBox.icon.focusedBackground
* @uiDefault CheckBox.icon.focusedCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
* @uiDefault CheckBox.icon.focusedIndeterminateBorderColor Color optional; defaults to CheckBox.icon.focusedSelectedBorderColor
* @uiDefault CheckBox.icon.focusedIndeterminateBackground Color optional; defaults to CheckBox.icon.focusedSelectedBackground
* @uiDefault CheckBox.icon.focusedIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.focusedCheckmarkColor
* @uiDefault CheckBox.icon.focusedBorderColor Color optional
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.focusedSelectedBorderColor Color optional; CheckBox.icon.focusedBorderColor is used if not specified
* @uiDefault CheckBox.icon.focusedSelectedBackground Color optional; CheckBox.icon.focusedBackground is used if not specified
* @uiDefault CheckBox.icon.focusedCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
*
* @uiDefault CheckBox.icon.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.hoverSelectedBorderColor Color optional; defaults to CheckBox.icon.hoverBorderColor
* @uiDefault CheckBox.icon.hoverSelectedBackground Color optional; defaults to CheckBox.icon.hoverBackground
* @uiDefault CheckBox.icon.hoverCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
* @uiDefault CheckBox.icon.hoverIndeterminateBorderColor Color optional; defaults to CheckBox.icon.hoverSelectedBorderColor
* @uiDefault CheckBox.icon.hoverIndeterminateBackground Color optional; defaults to CheckBox.icon.hoverSelectedBackground
* @uiDefault CheckBox.icon.hoverIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.hoverCheckmarkColor
* @uiDefault CheckBox.icon.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.hoverSelectedBorderColor Color optional; CheckBox.icon.hoverBorderColor is used if not specified
* @uiDefault CheckBox.icon.hoverSelectedBackground Color optional; CheckBox.icon.hoverBackground is used if not specified
* @uiDefault CheckBox.icon.hoverCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
*
* @uiDefault CheckBox.icon.pressedBorderColor Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.pressedSelectedBorderColor Color optional; defaults to CheckBox.icon.pressedBorderColor
* @uiDefault CheckBox.icon.pressedSelectedBackground Color optional; defaults to CheckBox.icon.pressedBackground
* @uiDefault CheckBox.icon.pressedCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
* @uiDefault CheckBox.icon.pressedIndeterminateBorderColor Color optional; defaults to CheckBox.icon.pressedSelectedBorderColor
* @uiDefault CheckBox.icon.pressedIndeterminateBackground Color optional; defaults to CheckBox.icon.pressedSelectedBackground
* @uiDefault CheckBox.icon.pressedIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.pressedCheckmarkColor
* @uiDefault CheckBox.icon.pressedBorderColor Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.pressedSelectedBorderColor Color optional; CheckBox.icon.pressedBorderColor is used if not specified
* @uiDefault CheckBox.icon.pressedSelectedBackground Color optional; CheckBox.icon.pressedBackground is used if not specified
* @uiDefault CheckBox.icon.pressedCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
*
* @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 );
@@ -112,8 +92,6 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected float borderWidth = getUIFloat( "CheckBox.icon.borderWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ), style );
/** @since 2 */ @Styleable protected float selectedBorderWidth = getUIFloat( "CheckBox.icon.selectedBorderWidth", Float.MIN_VALUE, style );
/** @since 2 */ @Styleable protected float disabledSelectedBorderWidth = getUIFloat( "CheckBox.icon.disabledSelectedBorderWidth", Float.MIN_VALUE, style );
/** @since 3.6 */ @Styleable protected float indeterminateBorderWidth = getUIFloat( "CheckBox.icon.indeterminateBorderWidth", Float.MIN_VALUE, style );
/** @since 3.6 */ @Styleable protected float disabledIndeterminateBorderWidth = getUIFloat( "CheckBox.icon.disabledIndeterminateBorderWidth", Float.MIN_VALUE, style );
@Styleable protected int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
// enabled
@@ -122,9 +100,6 @@ public class FlatCheckBoxIcon
@Styleable protected Color selectedBorderColor = getUIColor( "CheckBox.icon.selectedBorderColor", style );
@Styleable protected Color selectedBackground = getUIColor( "CheckBox.icon.selectedBackground", style );
@Styleable protected Color checkmarkColor = getUIColor( "CheckBox.icon.checkmarkColor", style );
/** @since 3.6 */ @Styleable protected Color indeterminateBorderColor = getUIColor( "CheckBox.icon.indeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color indeterminateBackground = getUIColor( "CheckBox.icon.indeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color indeterminateCheckmarkColor = getUIColor( "CheckBox.icon.indeterminateCheckmarkColor", style );
// disabled
@Styleable protected Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style );
@@ -132,9 +107,6 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color disabledSelectedBorderColor = getUIColor( "CheckBox.icon.disabledSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color disabledSelectedBackground = getUIColor( "CheckBox.icon.disabledSelectedBackground", style );
@Styleable protected Color disabledCheckmarkColor = getUIColor( "CheckBox.icon.disabledCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color disabledIndeterminateBorderColor = getUIColor( "CheckBox.icon.disabledIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color disabledIndeterminateBackground = getUIColor( "CheckBox.icon.disabledIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color disabledIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.disabledIndeterminateCheckmarkColor", style );
// focused
@Styleable protected Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style );
@@ -142,9 +114,6 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color focusedSelectedBorderColor = getUIColor( "CheckBox.icon.focusedSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color focusedSelectedBackground = getUIColor( "CheckBox.icon.focusedSelectedBackground", style );
/** @since 2 */ @Styleable protected Color focusedCheckmarkColor = getUIColor( "CheckBox.icon.focusedCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color focusedIndeterminateBorderColor = getUIColor( "CheckBox.icon.focusedIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color focusedIndeterminateBackground = getUIColor( "CheckBox.icon.focusedIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color focusedIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.focusedIndeterminateCheckmarkColor", style );
// hover
@Styleable protected Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style );
@@ -152,9 +121,6 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color hoverSelectedBorderColor = getUIColor( "CheckBox.icon.hoverSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color hoverSelectedBackground = getUIColor( "CheckBox.icon.hoverSelectedBackground", style );
/** @since 2 */ @Styleable protected Color hoverCheckmarkColor = getUIColor( "CheckBox.icon.hoverCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color hoverIndeterminateBorderColor = getUIColor( "CheckBox.icon.hoverIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color hoverIndeterminateBackground = getUIColor( "CheckBox.icon.hoverIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color hoverIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.hoverIndeterminateCheckmarkColor", style );
// pressed
/** @since 2 */ @Styleable protected Color pressedBorderColor = getUIColor( "CheckBox.icon.pressedBorderColor", style );
@@ -162,9 +128,6 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color pressedSelectedBorderColor = getUIColor( "CheckBox.icon.pressedSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color pressedSelectedBackground = getUIColor( "CheckBox.icon.pressedSelectedBackground", style );
/** @since 2 */ @Styleable protected Color pressedCheckmarkColor = getUIColor( "CheckBox.icon.pressedCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color pressedIndeterminateBorderColor = getUIColor( "CheckBox.icon.pressedIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color pressedIndeterminateBackground = getUIColor( "CheckBox.icon.pressedIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color pressedIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.pressedIndeterminateCheckmarkColor", style );
protected String getPropertyPrefix() {
return "CheckBox.";
@@ -199,22 +162,31 @@ 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 );
boolean selected = indeterminate || isSelected( c );
boolean isFocused = FlatUIUtils.isPermanentFocusOwner( c );
float bw = Float.MIN_VALUE;
if( !c.isEnabled() ) {
bw = (indeterminate && disabledIndeterminateBorderWidth != Float.MIN_VALUE)
? disabledIndeterminateBorderWidth
: (selected ? disabledSelectedBorderWidth : selectedBorderWidth);
}
if( bw == Float.MIN_VALUE ) {
bw = (indeterminate && indeterminateBorderWidth != Float.MIN_VALUE)
? indeterminateBorderWidth
: ((selected && selectedBorderWidth != Float.MIN_VALUE) ? selectedBorderWidth : borderWidth);
}
float bw = selected
? (disabledSelectedBorderWidth != Float.MIN_VALUE && !c.isEnabled()
? disabledSelectedBorderWidth
: (selectedBorderWidth != Float.MIN_VALUE ? selectedBorderWidth : borderWidth))
: borderWidth;
// paint focused border
if( isFocused && focusWidth > 0 && FlatButtonUI.isFocusPainted( c ) ) {
@@ -223,15 +195,15 @@ public class FlatCheckBoxIcon
}
// paint border
g.setColor( getBorderColor( c, selected, indeterminate ) );
g.setColor( getBorderColor( c, selected ) );
paintBorder( c, g, bw );
// paint background
Color baseBg = stateColor( indeterminate, indeterminateBackground, selected, selectedBackground, background );
Color bg = FlatUIUtils.deriveColor( getBackground( c, selected, indeterminate ), baseBg );
Color bg = FlatUIUtils.deriveColor( getBackground( c, selected ),
selected ? selectedBackground : background );
if( bg.getAlpha() < 255 ) {
// fill background with default color before filling with non-opaque background
g.setColor( baseBg );
g.setColor( selected ? selectedBackground : background );
paintBackground( c, g, bw );
}
g.setColor( bg );
@@ -239,7 +211,7 @@ public class FlatCheckBoxIcon
// paint checkmark
if( selected ) {
g.setColor( getCheckmarkColor( c, indeterminate ) );
g.setColor( getCheckmarkColor( c ) );
if( indeterminate )
paintIndeterminate( c, g );
else
@@ -293,40 +265,37 @@ public class FlatCheckBoxIcon
/** @since 2 */
public float getFocusWidth() {
return focusWidth * getScale();
return focusWidth;
}
protected Color getFocusColor( Component c ) {
return focusColor;
}
/** @since 3.6 */
protected Color getBorderColor( Component c, boolean selected, boolean indeterminate ) {
protected Color getBorderColor( Component c, boolean selected ) {
return FlatButtonUI.buttonStateColor( c,
stateColor( indeterminate, indeterminateBorderColor, selected, selectedBorderColor, borderColor ),
stateColor( indeterminate, disabledIndeterminateBorderColor, selected, disabledSelectedBorderColor, disabledBorderColor ),
stateColor( indeterminate, focusedIndeterminateBorderColor, selected, focusedSelectedBorderColor, focusedBorderColor ),
stateColor( indeterminate, hoverIndeterminateBorderColor, selected, hoverSelectedBorderColor, hoverBorderColor ),
stateColor( indeterminate, pressedIndeterminateBorderColor, selected, pressedSelectedBorderColor, pressedBorderColor ) );
selected ? selectedBorderColor : borderColor,
(selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor,
(selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor,
(selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor,
(selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor );
}
/** @since 3.6 */
protected Color getBackground( Component c, boolean selected, boolean indeterminate ) {
protected Color getBackground( Component c, boolean selected ) {
return FlatButtonUI.buttonStateColor( c,
stateColor( indeterminate, indeterminateBackground, selected, selectedBackground, background ),
stateColor( indeterminate, disabledIndeterminateBackground, selected, disabledSelectedBackground, disabledBackground ),
stateColor( indeterminate, focusedIndeterminateBackground, selected, focusedSelectedBackground, focusedBackground ),
stateColor( indeterminate, hoverIndeterminateBackground, selected, hoverSelectedBackground, hoverBackground ),
stateColor( indeterminate, pressedIndeterminateBackground, selected, pressedSelectedBackground, pressedBackground ) );
selected ? selectedBackground : background,
(selected && disabledSelectedBackground != null) ? disabledSelectedBackground : disabledBackground,
(selected && focusedSelectedBackground != null) ? focusedSelectedBackground : focusedBackground,
(selected && hoverSelectedBackground != null) ? hoverSelectedBackground : hoverBackground,
(selected && pressedSelectedBackground != null) ? pressedSelectedBackground : pressedBackground );
}
/** @since 3.6 */
protected Color getCheckmarkColor( Component c, boolean indeterminate ) {
protected Color getCheckmarkColor( Component c ) {
return FlatButtonUI.buttonStateColor( c,
stateColor( indeterminate, indeterminateCheckmarkColor, checkmarkColor ),
stateColor( indeterminate, disabledIndeterminateCheckmarkColor, disabledCheckmarkColor ),
stateColor( indeterminate, focusedIndeterminateCheckmarkColor, focusedCheckmarkColor ),
stateColor( indeterminate, hoverIndeterminateCheckmarkColor, hoverCheckmarkColor ),
stateColor( indeterminate, pressedIndeterminateCheckmarkColor, pressedCheckmarkColor ) );
checkmarkColor,
disabledCheckmarkColor,
focusedCheckmarkColor,
hoverCheckmarkColor,
pressedCheckmarkColor );
}
}

View File

@@ -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,10 +38,8 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
*
* @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" );
@@ -51,6 +49,21 @@ 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();

View File

@@ -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,10 +39,8 @@ 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" );
@@ -60,6 +58,21 @@ 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 ) {

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.util.UIScale.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
@@ -23,12 +24,11 @@ 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,10 +52,8 @@ 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" );
@@ -78,6 +76,21 @@ 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 ) {
/*
@@ -154,12 +167,12 @@ public class FlatHelpButtonIcon
@Override
public int getIconWidth() {
return scale( UIScale.scale( iconSize() ) );
return scale( iconSize() );
}
@Override
public int getIconHeight() {
return scale( UIScale.scale( iconSize() ) );
return scale( iconSize() );
}
private int iconSize() {

View File

@@ -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,10 +39,8 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
*
* @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" );
@@ -53,6 +51,21 @@ 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() )

View File

@@ -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,10 +38,8 @@ 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" );
@@ -60,6 +58,21 @@ 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 ) {
/*

View File

@@ -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,10 +46,8 @@ 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" );
@@ -67,6 +65,21 @@ 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
@@ -77,7 +90,7 @@ public class FlatTabbedPaneCloseIcon
closeSize.width, closeSize.height, closeArc, closeArc );
}
// set color of cross
// set cross color
Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground );
g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) );

View File

@@ -57,11 +57,11 @@ public class FlatTreeOpenIcon
double arc = 1.5;
double arc2 = 0.5;
path = FlatUIUtils.createPath( false,
// bottom-left of opened part
// bottom-left of opend part
2,13.5,
// top-left of opened part
// top-left of opend part
FlatUIUtils.ROUNDED, 4.5,7.5, arc,
// top-right of opened part
// top-right of opend part
FlatUIUtils.ROUNDED, 15.5,7.5, arc2,
// bottom-right

View File

@@ -18,141 +18,76 @@ package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Window;
import javax.swing.SwingUtilities;
import java.awt.RenderingHints;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatTitlePane;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
/**
* Base class for window icons.
*
* @uiDefault TitlePane.buttonSize Dimension
* @uiDefault TitlePane.buttonInsets Insets optional
* @uiDefault TitlePane.buttonArc int optional
* @uiDefault TitlePane.buttonSymbolHeight int
* @uiDefault TitlePane.buttonBackground Color optional
* @uiDefault TitlePane.buttonForeground Color optional
* @uiDefault TitlePane.buttonInactiveBackground Color optional
* @uiDefault TitlePane.buttonInactiveForeground Color optional
* @uiDefault TitlePane.buttonHoverBackground Color optional
* @uiDefault TitlePane.buttonHoverForeground Color optional
* @uiDefault TitlePane.buttonPressedBackground Color optional
* @uiDefault TitlePane.buttonPressedForeground Color optional
* @uiDefault TitlePane.buttonHoverBackground Color
* @uiDefault TitlePane.buttonPressedBackground Color
*
* @author Karl Tauber
*/
public abstract class FlatWindowAbstractIcon
extends FlatAbstractIcon
{
/** @since 3.6 */ protected final Insets insets;
/** @since 3.6 */ protected final int arc;
/** @since 3.6 */ protected final int symbolHeight;
/** @since 3.6 */ protected final Color background;
/** @since 3.6 */ protected final Color foreground;
/** @since 3.6 */ protected final Color inactiveBackground;
/** @since 3.6 */ protected final Color inactiveForeground;
protected final Color hoverBackground;
/** @since 3.6 */ protected final Color hoverForeground;
protected final Color pressedBackground;
/** @since 3.6 */ protected final Color pressedForeground;
private final int symbolHeight;
private final Color hoverBackground;
private final Color pressedBackground;
/** @since 3.2 */
protected FlatWindowAbstractIcon( String windowStyle ) {
this( windowStyle, null, null, null, null, null, null, null, null );
}
/** @since 3.6 */
protected FlatWindowAbstractIcon( String windowStyle,
Color background, Color foreground, Color inactiveBackground, Color inactiveForeground,
Color hoverBackground, Color hoverForeground, Color pressedBackground, Color pressedForeground )
{
this( FlatUIUtils.getSubUIDimension( "TitlePane.buttonSize", windowStyle ),
FlatUIUtils.getSubUIInsets( "TitlePane.buttonInsets", windowStyle ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonArc", windowStyle, 0 ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonSymbolHeight", windowStyle, 10 ),
(background != null) ? background : FlatUIUtils.getSubUIColor( "TitlePane.buttonBackground", windowStyle ),
(foreground != null) ? foreground : FlatUIUtils.getSubUIColor( "TitlePane.buttonForeground", windowStyle ),
(inactiveBackground != null) ? inactiveBackground : FlatUIUtils.getSubUIColor( "TitlePane.buttonInactiveBackground", windowStyle ),
(inactiveForeground != null) ? inactiveForeground : FlatUIUtils.getSubUIColor( "TitlePane.buttonInactiveForeground", windowStyle ),
(hoverBackground != null) ? hoverBackground : FlatUIUtils.getSubUIColor( "TitlePane.buttonHoverBackground", windowStyle ),
(hoverForeground != null) ? hoverForeground : FlatUIUtils.getSubUIColor( "TitlePane.buttonHoverForeground", windowStyle ),
(pressedBackground != null) ? pressedBackground : FlatUIUtils.getSubUIColor( "TitlePane.buttonPressedBackground", windowStyle ),
(pressedForeground != null) ? pressedForeground : FlatUIUtils.getSubUIColor( "TitlePane.buttonPressedForeground", windowStyle ) );
FlatUIUtils.getSubUIColor( "TitlePane.buttonHoverBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.buttonPressedBackground", windowStyle ) );
}
/** @since 3.6 */
protected FlatWindowAbstractIcon( Dimension size, Insets insets, int arc, int symbolHeight,
Color background, Color foreground, Color inactiveBackground, Color inactiveForeground,
Color hoverBackground, Color hoverForeground, Color pressedBackground, Color pressedForeground )
{
/** @since 3.2 */
protected FlatWindowAbstractIcon( Dimension size, int symbolHeight, Color hoverBackground, Color pressedBackground ) {
super( size.width, size.height, null );
this.insets = (insets != null) ? insets : new Insets( 0, 0, 0, 0 );
this.arc = arc;
this.symbolHeight = symbolHeight;
this.background = background;
this.foreground = foreground;
this.inactiveBackground = inactiveBackground;
this.inactiveForeground = inactiveForeground;
this.hoverBackground = hoverBackground;
this.hoverForeground = hoverForeground;
this.pressedBackground = pressedBackground;
this.pressedForeground = pressedForeground;
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
paintBackground( c, g );
g.setColor( getForeground( c ) );
HiDPIUtils.paintAtScale1x( g, 0, 0, width, height, this::paintIconAt1x );
}
protected abstract void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor );
/** @since 3.5.2 */
@Override
protected void paintBackground( Component c, Graphics2D g, int x, int y ) {
Color bg = null;
if( background != null || inactiveBackground != null ) {
Window window = SwingUtilities.windowForComponent( c );
bg = (window == null || window.isActive()) ? background : inactiveBackground;
}
Color background = FlatButtonUI.buttonStateColor( c, bg, null, null, hoverBackground, pressedBackground );
protected void paintBackground( Component c, Graphics2D g ) {
Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
if( background != null ) {
Insets insets = UIScale.scale( this.insets );
float arc = UIScale.scale( (float) this.arc );
// disable antialiasing for background rectangle painting to avoid blury edges when scaled (e.g. at 125% or 175%)
Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
// derive color from title pane background
if( background instanceof DerivedColor ) {
Container titlePane = SwingUtilities.getAncestorOfClass( FlatTitlePane.class, c );
Component baseComp = (titlePane != null) ? titlePane : c;
background = FlatUIUtils.deriveColor( background, baseComp.getBackground() );
}
g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
g.fillRect( 0, 0, width, height );
g.setColor( background );
FlatUIUtils.paintComponentBackground( g, insets.left, insets.top,
c.getWidth() - insets.left - insets.right,
c.getHeight() - insets.top - insets.bottom,
0, arc );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, oldHint );
}
}
protected Color getForeground( Component c ) {
Color fg = null;
if( foreground != null || inactiveForeground != null ) {
Window window = SwingUtilities.windowForComponent( c );
fg = (window == null || window.isActive()) ? foreground : inactiveForeground;
}
return FlatButtonUI.buttonStateColor( c, (fg != null) ? fg : c.getForeground(),
null, null, hoverForeground, pressedForeground );
return c.getForeground();
}
/** @since 3.2 */
protected int getSymbolHeight() {
return symbolHeight;
}
}

View File

@@ -17,54 +17,53 @@
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* "close" icon for windows (frames and dialogs).
*
* @uiDefault TitlePane.closeBackground Color optional
* @uiDefault TitlePane.closeForeground Color optional
* @uiDefault TitlePane.closeInactiveBackground Color optional
* @uiDefault TitlePane.closeInactiveForeground Color optional
* @uiDefault TitlePane.closeHoverBackground Color optional
* @uiDefault TitlePane.closeHoverForeground Color optional
* @uiDefault TitlePane.closePressedBackground Color optional
* @uiDefault TitlePane.closePressedForeground Color optional
* @uiDefault TitlePane.closeHoverBackground Color
* @uiDefault TitlePane.closePressedBackground Color
* @uiDefault TitlePane.closeHoverForeground Color
* @uiDefault TitlePane.closePressedForeground Color
*
* @author Karl Tauber
*/
public class FlatWindowCloseIcon
extends FlatWindowAbstractIcon
{
private final Color hoverForeground;
private final Color pressedForeground;
public FlatWindowCloseIcon() {
this( null );
}
/** @since 3.2 */
public FlatWindowCloseIcon( String windowStyle ) {
super( windowStyle,
FlatUIUtils.getSubUIColor( "TitlePane.closeBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeForeground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeInactiveBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeInactiveForeground", windowStyle ),
super( FlatUIUtils.getSubUIDimension( "TitlePane.buttonSize", windowStyle ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonSymbolHeight", windowStyle, 10 ),
FlatUIUtils.getSubUIColor( "TitlePane.closeHoverBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeHoverForeground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closePressedBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closePressedForeground", windowStyle ) );
FlatUIUtils.getSubUIColor( "TitlePane.closePressedBackground", windowStyle ) );
hoverForeground = FlatUIUtils.getSubUIColor( "TitlePane.closeHoverForeground", windowStyle );
pressedForeground = FlatUIUtils.getSubUIColor( "TitlePane.closePressedForeground", windowStyle );
}
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (symbolHeight * scaleFactor);
int iwh = (int) (getSymbolHeight() * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int ix2 = ix + iwh - 1;
int iy2 = iy + iwh - 1;
boolean isWindows10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
float thickness = Math.max( isWindows10 ? (int) scaleFactor : (float) scaleFactor, 1 );
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD, 4 );
path.moveTo( ix, iy );
@@ -74,4 +73,9 @@ public class FlatWindowCloseIcon
g.setStroke( new BasicStroke( thickness ) );
g.draw( path );
}
@Override
protected Color getForeground( Component c ) {
return FlatButtonUI.buttonStateColor( c, c.getForeground(), null, null, hoverForeground, pressedForeground );
}
}

View File

@@ -37,8 +37,8 @@ public class FlatWindowIconifyIcon
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iw = (int) (symbolHeight * scaleFactor);
int ih = Math.max( (int) scaleFactor, 1 );
int iw = (int) (getSymbolHeight() * scaleFactor);
int ih = (int) scaleFactor;
int ix = x + ((width - iw) / 2);
int iy = y + ((height - ih) / 2);

View File

@@ -39,11 +39,10 @@ public class FlatWindowMaximizeIcon
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (symbolHeight * scaleFactor);
int iwh = (int) (getSymbolHeight() * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
boolean isWindows10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
float thickness = Math.max( isWindows10 ? (int) scaleFactor : (float) scaleFactor, 1 );
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
int arc = Math.max( (int) (1.5 * scaleFactor), 2 );
g.fill( SystemInfo.isWindows_11_orLater

View File

@@ -42,15 +42,14 @@ public class FlatWindowRestoreIcon
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (symbolHeight * scaleFactor);
int iwh = (int) (getSymbolHeight() * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
boolean isWindows10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
float thickness = Math.max( isWindows10 ? (int) scaleFactor : (float) scaleFactor, 1 );
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
int arc = Math.max( (int) (1.5 * scaleFactor), 2 );
int arcOuter = (int) (arc + (1.5 * scaleFactor));
int rwh = (int) ((symbolHeight - 2) * scaleFactor);
int rwh = (int) ((getSymbolHeight() - 2) * scaleFactor);
int ro2 = iwh - rwh;
// upper-right rectangle

View File

@@ -23,15 +23,17 @@ 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;
import javax.swing.JSpinner;
import javax.swing.JViewport;
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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.util.DerivedColor;
/**
@@ -58,15 +60,13 @@ import com.formdev.flatlaf.util.DerivedColor;
* @uiDefault Component.error.focusedBorderColor Color
* @uiDefault Component.warning.borderColor Color
* @uiDefault Component.warning.focusedBorderColor Color
* @uiDefault Component.success.borderColor Color
* @uiDefault Component.success.focusedBorderColor Color
* @uiDefault Component.custom.borderColor Color
*
* @author Karl Tauber
*/
public class FlatBorder
extends BasicBorders.MarginBorder
implements StyleableObject
implements StyleableBorder
{
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
@@ -82,8 +82,6 @@ public class FlatBorder
@Styleable(dot=true) protected Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" );
@Styleable(dot=true) protected Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" );
@Styleable(dot=true) protected Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" );
/** @since 3.6 */ @Styleable(dot=true) protected Color successBorderColor = UIManager.getColor( "Component.success.borderColor" );
/** @since 3.6 */ @Styleable(dot=true) protected Color successFocusedBorderColor = UIManager.getColor( "Component.success.focusedBorderColor" );
@Styleable(dot=true) protected Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
// only used via styling (not in UI defaults, but has likewise client properties)
@@ -91,6 +89,24 @@ 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();
@@ -120,7 +136,7 @@ public class FlatBorder
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null, c instanceof JScrollPane );
focusColor, borderColor, null );
} finally {
g2.dispose();
}
@@ -153,9 +169,6 @@ public class FlatBorder
case FlatClientProperties.OUTLINE_WARNING:
return isFocused( c ) ? warningFocusedBorderColor : warningBorderColor;
case FlatClientProperties.OUTLINE_SUCCESS:
return isFocused( c ) ? successFocusedBorderColor : successBorderColor;
}
} else if( outline instanceof Color ) {
Color color = (Color) outline;
@@ -182,7 +195,8 @@ public class FlatBorder
protected boolean isEnabled( Component c ) {
if( c instanceof JScrollPane ) {
// check whether view component is disabled
Component view = FlatScrollPaneUI.getView( (JScrollPane) c );
JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view != null && !isEnabled( view ) )
return false;
}
@@ -265,7 +279,7 @@ public class FlatBorder
}
/**
* Returns the (unscaled) arc diameter of the border corners.
* Returns the (unscaled) arc diameter of the border.
*/
protected int getArc( Component c ) {
return 0;

View File

@@ -42,13 +42,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional
* @uiDefault Button.pressedBorderColor Color optional
*
* @uiDefault Button.selectedBorderColor Color optional
* @uiDefault Button.disabledSelectedBorderColor Color optional
* @uiDefault Button.focusedSelectedBorderColor Color optional
* @uiDefault Button.hoverSelectedBorderColor Color optional
* @uiDefault Button.pressedSelectedBorderColor Color optional
*
* @uiDefault Button.default.borderWidth int or float
* @uiDefault Button.default.borderColor Color
@@ -56,7 +49,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color
* @uiDefault Button.default.pressedBorderColor Color optional
* @uiDefault Button.default.hoverBorderColor Color optional
*
* @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5
@@ -73,13 +65,6 @@ public class FlatButtonBorder
protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
/** @since 3.5 */ @Styleable protected Color pressedBorderColor = UIManager.getColor( "Button.pressedBorderColor" );
/** @since 3.5 */ @Styleable protected Color selectedBorderColor = UIManager.getColor( "Button.selectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color disabledSelectedBorderColor = UIManager.getColor( "Button.disabledSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color focusedSelectedBorderColor = UIManager.getColor( "Button.focusedSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color hoverSelectedBorderColor = UIManager.getColor( "Button.hoverSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color pressedSelectedBorderColor = UIManager.getColor( "Button.pressedSelectedBorderColor" );
@Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 );
@Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
@@ -87,7 +72,6 @@ public class FlatButtonBorder
@Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
@Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
/** @since 3.5 */ @Styleable(dot=true) protected Color defaultPressedBorderColor = UIManager.getColor( "Button.default.pressedBorderColor" );
/** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f );
/** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
@@ -155,13 +139,12 @@ public class FlatButtonBorder
@Override
protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c );
boolean selected = (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
Paint color = FlatButtonUI.buttonStateColor( c,
def ? defaultBorderColor : ((selected && selectedBorderColor != null) ? selectedBorderColor : borderColor),
(selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor,
def ? defaultFocusedBorderColor : ((selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor),
def ? defaultHoverBorderColor : ((selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor),
def ? defaultPressedBorderColor : ((selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor) );
def ? defaultBorderColor : borderColor,
disabledBorderColor,
def ? defaultFocusedBorderColor : focusedBorderColor,
def ? defaultHoverBorderColor : hoverBorderColor,
null );
// change to gradient paint if start/end colors are specified
Color startBg = def ? defaultBorderColor : borderColor;

View File

@@ -29,7 +29,6 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.util.Map;
@@ -54,15 +53,12 @@ import javax.swing.plaf.ToolBarUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
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;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -280,6 +276,8 @@ public class FlatButtonUI
LookAndFeel.installProperty( b, "opaque", false );
LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) );
MigLayoutVisualPadding.install( b );
}
@Override
@@ -289,21 +287,8 @@ public class FlatButtonUI
oldStyleValues = null;
borderShared = null;
defaults_initialized = false;
}
@Override
protected void installListeners( AbstractButton b ) {
super.installListeners( b );
MigLayoutVisualPadding.install( b );
}
@Override
protected void uninstallListeners( AbstractButton b ) {
super.uninstallListeners( b );
MigLayoutVisualPadding.uninstall( b );
defaults_initialized = false;
}
@Override
@@ -313,10 +298,6 @@ public class FlatButtonUI
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case SQUARE_SIZE:
case MINIMUM_WIDTH:
case MINIMUM_HEIGHT:
@@ -325,11 +306,11 @@ public class FlatButtonUI
case BUTTON_TYPE:
b.revalidate();
HiDPIUtils.repaint( b );
b.repaint();
break;
case OUTLINE:
HiDPIUtils.repaint( b );
b.repaint();
break;
case STYLE:
@@ -341,7 +322,7 @@ public class FlatButtonUI
} else
installStyle( b );
b.revalidate();
HiDPIUtils.repaint( b );
b.repaint();
break;
}
}
@@ -369,8 +350,8 @@ public class FlatButtonUI
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
if( key.startsWith( "help." ) ) {
if( !(helpButtonIcon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( !(helpButtonIcon instanceof FlatHelpButtonIcon) )
return new UnknownStyleException( key );
if( helpButtonIconShared ) {
helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon );
@@ -378,13 +359,7 @@ public class FlatButtonUI
}
key = key.substring( "help.".length() );
return ((StyleableObject)helpButtonIcon).applyStyleProperty( key, value );
}
// update internal values; otherwise isCustomBackground() and isCustomForeground() would return wrong results
switch( key ) {
case "background": background = (Color) value; break;
case "foreground": foreground = (Color) value; break;
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
}
if( "iconTextGap".equals( key ) && value instanceof Integer )
@@ -399,8 +374,8 @@ public class FlatButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this, c.getBorder() );
if( helpButtonIcon instanceof StyleableObject )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((StyleableObject)helpButtonIcon).getStyleableInfos() );
if( helpButtonIcon instanceof FlatHelpButtonIcon )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((FlatHelpButtonIcon)helpButtonIcon).getStyleableInfos() );
return infos;
}
@@ -408,8 +383,8 @@ public class FlatButtonUI
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.startsWith( "help." ) ) {
return (helpButtonIcon instanceof StyleableObject)
? ((StyleableObject)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
return (helpButtonIcon instanceof FlatHelpButtonIcon)
? ((FlatHelpButtonIcon)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
: null;
}
@@ -576,52 +551,9 @@ public class FlatButtonUI
}
}
/**
* Similar to BasicButtonUI.paint(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
@Override
public void paint( Graphics g, JComponent c ) {
g = FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c );
AbstractButton b = (AbstractButton) c;
// layout
String clippedText = layout( b, b.getFontMetrics( b.getFont() ), b.getWidth(), b.getHeight() );
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
clearTextShiftOffset();
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
ButtonModel model = b.getModel();
if( model.isArmed() && model.isPressed() )
paintButtonPressed( g, b );
// paint icon
if( b.getIcon() != null )
paintIcon( g, b, iconR );
// paint text
if( clippedText != null && !clippedText.isEmpty() ) {
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// update foreground color in HTML view, which is necessary
// for selected and pressed states
// (only for enabled buttons, because UIManager.getColor("textInactiveText")
// is used for disabled components; see: javax.swing.text.GlyphView.paint())
if( b.isEnabled() )
FlatHTML.updateRendererCSSForeground( view, getForeground( b ) );
view.paint( g, textR ); // HTML text
} else
paintText( g, b, textR, clippedText );
}
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
if( b.isFocusPainted() && b.hasFocus() )
paintFocus( g, b, viewR, textR, iconR );
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
}
@Override
@@ -661,6 +593,8 @@ public class FlatButtonUI
}
public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) {
if(foreground == null)
foreground=Color.red;
FontMetrics fm = b.getFontMetrics( b.getFont() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1;
@@ -670,8 +604,7 @@ public class FlatButtonUI
}
protected Color getBackground( JComponent c ) {
boolean def = isDefaultButton( c );
boolean toolBarButton = !def && (isToolBarButton( c ) || isBorderlessButton( c ));
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
// selected state
if( ((AbstractButton)c).isSelected() ) {
@@ -699,6 +632,7 @@ public class FlatButtonUI
toolbarPressedBackground );
}
boolean def = isDefaultButton( c );
return buttonStateColor( c,
getBackgroundBase( c, def ),
disabledBackground,
@@ -749,16 +683,14 @@ public class FlatButtonUI
}
protected Color getForeground( JComponent c ) {
Color fg = c.getForeground();
boolean def = isDefaultButton( c );
boolean toolBarButton = !def && (isToolBarButton( c ) || isBorderlessButton( c ));
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
// selected state
if( ((AbstractButton)c).isSelected() ) {
return buttonStateColor( c,
toolBarButton
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : fg)
: (isCustomForeground( fg ) ? fg : selectedForeground),
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : c.getForeground())
: selectedForeground,
toolBarButton
? (toolbarDisabledSelectedForeground != null ? toolbarDisabledSelectedForeground : disabledText)
: (disabledSelectedForeground != null ? disabledSelectedForeground : disabledText),
@@ -770,17 +702,18 @@ public class FlatButtonUI
// toolbar button
if( toolBarButton ) {
return buttonStateColor( c,
fg,
c.getForeground(),
disabledText,
null,
toolbarHoverForeground,
toolbarPressedForeground );
}
boolean def = isDefaultButton( c );
return buttonStateColor( c,
getForegroundBase( c, def ),
disabledText,
isCustomForeground( fg ) ? null : (def ? defaultFocusedForeground : focusedForeground),
isCustomForeground( c.getForeground() ) ? null : (def ? defaultFocusedForeground : focusedForeground),
def ? defaultHoverForeground : hoverForeground,
def ? defaultPressedForeground : pressedForeground );
}
@@ -853,67 +786,6 @@ public class FlatButtonUI
return margin instanceof UIResource && Objects.equals( margin, defaultMargin );
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return getBaselineImpl( c, width, height );
}
/**
* Similar to BasicButtonUI.getBaseline(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
static int getBaselineImpl( JComponent c, int width, int height ) {
if( width < 0 || height < 0 )
throw new IllegalArgumentException();
AbstractButton b = (AbstractButton) c;
String text = b.getText();
if( text == null || text.isEmpty() )
return -1;
FontMetrics fm = b.getFontMetrics( b.getFont() );
layout( b, fm, width, height );
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// HTML text
int baseline = BasicHTML.getHTMLBaseline( view, textR.width, textR.height );
return (baseline >= 0) ? textR.y + baseline : baseline;
} else
return textR.y + fm.getAscent();
}
/**
* Similar to BasicButtonUI.layout(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
private static String layout( AbstractButton b, FontMetrics fm, int width, int height ) {
// compute view rectangle
Insets insets = b.getInsets();
viewR.setBounds( insets.left, insets.top,
width - insets.left - insets.right,
height - insets.top - insets.bottom );
// reset rectangles
textR.setBounds( 0, 0, 0, 0 );
iconR.setBounds( 0, 0, 0, 0 );
String text = b.getText();
return SwingUtilities.layoutCompoundLabel( b, fm, text, b.getIcon(),
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
viewR, iconR, textR,
(text != null) ? b.getIconTextGap() : 0 );
}
private static Rectangle viewR = new Rectangle();
private static Rectangle textR = new Rectangle();
private static Rectangle iconR = new Rectangle();
//---- class FlatButtonListener -------------------------------------------
protected class FlatButtonListener
@@ -934,7 +806,7 @@ public class FlatButtonUI
@Override
public void stateChanged( ChangeEvent e ) {
HiDPIUtils.repaint( b );
super.stateChanged( e );
// if button is in toolbar, repaint button groups
AbstractButton b = (AbstractButton) e.getSource();
@@ -946,17 +818,5 @@ public class FlatButtonUI
((FlatToolBarUI)ui).repaintButtonGroup( b );
}
}
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
HiDPIUtils.repaint( b );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
HiDPIUtils.repaint( b );
}
}
}

View File

@@ -17,7 +17,6 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
@@ -25,9 +24,7 @@ import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
@@ -36,7 +33,6 @@ import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.Utilities;
/**
@@ -52,15 +48,12 @@ public class FlatCaret
{
private static final String KEY_CARET_INFO = "FlatLaf.internal.caretInfo";
// selectAllOnFocusPolicy
private static final int NEVER = 0, ONCE = 1, ALWAYS = 2;
private final String selectAllOnFocusPolicy;
private final boolean selectAllOnMouseClick;
private boolean inInstall;
private boolean wasFocused;
private boolean wasFocusTemporaryLost;
private boolean wasTemporaryLost;
private boolean isMousePressed;
private boolean isWordSelection;
private boolean isLineSelection;
@@ -101,9 +94,6 @@ public class FlatCaret
// restore selection
select( (int) ci[1], (int) ci[0] );
if( ci[4] != 0 )
wasFocused = true;
// if text component is focused, then caret and selection are visible,
// but when switching theme, the component does not yet have
// a highlighter and the selection is not painted
@@ -131,7 +121,6 @@ public class FlatCaret
getMark(),
getBlinkRate(),
System.currentTimeMillis(),
wasFocused ? 1 : 0,
} );
super.deinstall( c );
@@ -151,36 +140,11 @@ public class FlatCaret
super.adjustVisibility( nloc );
}
@Override
public void setDot( int dot ) {
super.setDot( dot );
// mark as focused if invoked from JTextComponent.setCaretPosition()
// to disable SELECT_ALL_ON_FOCUS_POLICY_ONCE if application explicitly changes selection
if( !wasFocused &&
getSelectAllOnFocusPolicy() == ONCE &&
StackUtils.wasInvokedFrom( JTextComponent.class.getName(), "setCaretPosition", 6 ) )
wasFocused = true;
}
@Override
public void moveDot( int dot ) {
super.moveDot( dot );
// mark as focused if invoked from JTextComponent.moveCaretPosition()
// to disable SELECT_ALL_ON_FOCUS_POLICY_ONCE if application explicitly changes selection
if( !wasFocused &&
getSelectAllOnFocusPolicy() == ONCE &&
StackUtils.wasInvokedFrom( JTextComponent.class.getName(), "moveCaretPosition", 6 ) )
wasFocused = true;
}
@Override
public void focusGained( FocusEvent e ) {
if( !inInstall && !wasFocusTemporaryLost && (!isMousePressed || isSelectAllOnMouseClick()) )
if( !inInstall && !wasTemporaryLost && (!isMousePressed || selectAllOnMouseClick) )
selectAllOnFocusGained();
wasFocusTemporaryLost = false;
wasTemporaryLost = false;
wasFocused = true;
super.focusGained( e );
@@ -188,7 +152,7 @@ public class FlatCaret
@Override
public void focusLost( FocusEvent e ) {
wasFocusTemporaryLost = e.isTemporary();
wasTemporaryLost = e.isTemporary();
super.focusLost( e );
}
@@ -268,13 +232,24 @@ public class FlatCaret
if( doc == null || !c.isEnabled() || !c.isEditable() || FlatUIUtils.isCellEditor( c ) )
return;
int selectAllOnFocusPolicy = getSelectAllOnFocusPolicy();
if( selectAllOnFocusPolicy == NEVER )
Object selectAllOnFocusPolicy = c.getClientProperty( SELECT_ALL_ON_FOCUS_POLICY );
if( selectAllOnFocusPolicy == null )
selectAllOnFocusPolicy = this.selectAllOnFocusPolicy;
if( selectAllOnFocusPolicy == null || SELECT_ALL_ON_FOCUS_POLICY_NEVER.equals( selectAllOnFocusPolicy ) )
return;
if( selectAllOnFocusPolicy == ONCE && !isMousePressed ) {
if( !SELECT_ALL_ON_FOCUS_POLICY_ALWAYS.equals( selectAllOnFocusPolicy ) ) {
// policy is "once" (or null or unknown)
// was already focused?
if( wasFocused && !(c instanceof JFormattedTextField) )
if( wasFocused )
return;
// check whether selection was modified before gaining focus
int dot = getDot();
int mark = getMark();
if( dot != mark || dot != doc.getLength() )
return;
}
@@ -290,51 +265,16 @@ public class FlatCaret
select( 0, c2.getDocument().getLength() );
} );
} else
} else {
select( 0, doc.getLength() );
}
}
private void select( int mark, int dot ) {
if( mark != getMark() )
setDot( mark, Position.Bias.Forward );
setDot( mark );
if( dot != getDot() )
moveDot( dot, Position.Bias.Forward );
}
private int getSelectAllOnFocusPolicy() {
Object value = getClientProperty( SELECT_ALL_ON_FOCUS_POLICY );
// Note: using String.valueOf() because selectAllOnFocusPolicy may be null
switch( String.valueOf( value instanceof String ? value : selectAllOnFocusPolicy ) ) {
default:
case SELECT_ALL_ON_FOCUS_POLICY_NEVER: return NEVER;
case SELECT_ALL_ON_FOCUS_POLICY_ONCE: return ONCE;
case SELECT_ALL_ON_FOCUS_POLICY_ALWAYS: return ALWAYS;
}
}
private boolean isSelectAllOnMouseClick() {
Object value = getClientProperty( SELECT_ALL_ON_MOUSE_CLICK );
return (value instanceof Boolean) ? (boolean) value : selectAllOnMouseClick;
}
private Object getClientProperty( String key ) {
JTextComponent c = getComponent();
if( c == null )
return null;
Object value = c.getClientProperty( key );
if( value != null )
return value;
Container parent = c.getParent();
if( parent instanceof JComboBox )
return ((JComboBox<?>)parent).getClientProperty( key );
if( parent instanceof JSpinner.DefaultEditor ) {
parent = parent.getParent();
if( parent instanceof JSpinner )
return ((JSpinner)parent).getClientProperty( key );
}
return null;
moveDot( dot );
}
/** @since 1.4 */

View File

@@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
@@ -103,23 +102,13 @@ public class FlatCheckBoxMenuItemUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */

View File

@@ -78,7 +78,6 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
@@ -113,7 +112,6 @@ 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
@@ -148,7 +146,6 @@ 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;
@@ -223,12 +220,10 @@ public class FlatComboBoxUI
private void repaintArrowButton() {
if( arrowButton != null && !comboBox.isEditable() )
HiDPIUtils.repaint( arrowButton );
arrowButton.repaint();
}
};
comboBox.addMouseListener( hoverListener );
MigLayoutVisualPadding.install( comboBox );
}
@Override
@@ -237,8 +232,6 @@ public class FlatComboBoxUI
comboBox.removeMouseListener( hoverListener );
hoverListener = null;
MigLayoutVisualPadding.uninstall( comboBox );
}
@Override
@@ -260,7 +253,6 @@ 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" );
@@ -281,6 +273,8 @@ public class FlatComboBoxUI
comboBox.setMaximumRowCount( maximumRowCount );
paddingBorder = new CellPaddingBorder( padding );
MigLayoutVisualPadding.install( comboBox );
}
@Override
@@ -296,7 +290,6 @@ public class FlatComboBoxUI
buttonBackground = null;
buttonEditableBackground = null;
buttonFocusedBackground = null;
buttonFocusedEditableBackground = null;
buttonSeparatorColor = null;
buttonDisabledSeparatorColor = null;
buttonArrowColor = null;
@@ -310,6 +303,8 @@ public class FlatComboBoxUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( comboBox );
}
@Override
@@ -356,15 +351,15 @@ public class FlatComboBoxUI
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
if( comboBox != null )
HiDPIUtils.repaint( comboBox );
if( comboBox != null && comboBox.isEditable() )
comboBox.repaint();
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
if( comboBox != null )
HiDPIUtils.repaint( comboBox );
if( comboBox != null && comboBox.isEditable() )
comboBox.repaint();
}
};
}
@@ -391,12 +386,12 @@ public class FlatComboBoxUI
switch( propertyName ) {
case PLACEHOLDER_TEXT:
if( editor != null )
HiDPIUtils.repaint( editor );
editor.repaint();
break;
case COMPONENT_ROUND_RECT:
case OUTLINE:
HiDPIUtils.repaint( comboBox );
comboBox.repaint();
break;
case MINIMUM_WIDTH:
@@ -407,7 +402,7 @@ public class FlatComboBoxUI
case STYLE_CLASS:
installStyle();
comboBox.revalidate();
HiDPIUtils.repaint( comboBox );
comboBox.repaint();
break;
}
}
@@ -589,9 +584,9 @@ public class FlatComboBoxUI
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background
if( enabled && !isCellRenderer && arrowButton.isVisible() ) {
if( enabled && !isCellRenderer ) {
Color buttonColor = paintButton
? (buttonFocusedEditableBackground != null && isPermanentFocusOwner( comboBox ) ? buttonFocusedEditableBackground : buttonEditableBackground)
? buttonEditableBackground
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
: buttonBackground;
@@ -616,7 +611,7 @@ public class FlatComboBoxUI
}
// paint vertical line between value and arrow button
if( paintButton && arrowButton.isVisible() ) {
if( paintButton ) {
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
@@ -886,7 +881,7 @@ public class FlatComboBoxUI
GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
if( gc != null ) {
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right );
} else {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
@@ -931,7 +926,7 @@ public class FlatComboBoxUI
protected void configurePopup() {
super.configurePopup();
// make opaque to avoid that background shines through border (e.g. at 150% scaling)
// make opaque to avoid that background shines thru border (e.g. at 150% scaling)
setOpaque( true );
// set popup border
@@ -949,7 +944,7 @@ public class FlatComboBoxUI
if( popupBackground != null )
list.setBackground( popupBackground );
// set popup background because it may shine through when scaled (e.g. at 150%)
// set popup background because it may shine thru when scaled (e.g. at 150%)
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) );
@@ -1095,7 +1090,7 @@ public class FlatComboBoxUI
}
// using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc.) not on AWT thread (which should be not done)
// (model, selection, etc) not on AWT thread (which should be not done)
synchronized void install( Component c, int focusWidth ) {
if( !(c instanceof JComponent) )
return;
@@ -1247,7 +1242,7 @@ public class FlatComboBoxUI
* Key selection manager that delegates to the default manager.
* Shows the popup if Space key is pressed and "typed characters" buffer is empty.
* If items contain spaces (e.g. "a b") it is still possible to select them
* by pressing keys 'a', 'Space' and 'b'.
* by pressing keys a, Space and b.
*/
private class FlatKeySelectionManager
implements JComboBox.KeySelectionManager, UIResource

View File

@@ -24,8 +24,9 @@ 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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -42,7 +43,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatDropShadowBorder
extends FlatEmptyBorder
implements StyleableObject
implements StyleableBorder
{
@Styleable protected Color shadowColor;
@Styleable protected Insets shadowInsets;
@@ -73,7 +74,7 @@ public class FlatDropShadowBorder
this.shadowColor = shadowColor;
this.shadowInsets = shadowInsets;
this.shadowOpacity = Math.min( Math.max( shadowOpacity, 0f ), 1f );
this.shadowOpacity = shadowOpacity;
shadowSize = maxInset( shadowInsets );
}
@@ -92,7 +93,7 @@ public class FlatDropShadowBorder
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = StyleableObject.super.applyStyleProperty( key, value );
Object oldValue = FlatStylingSupport.applyToAnnotatedObject( this, key, value );
if( key.equals( "shadowInsets" ) ) {
applyStyleProperty( nonNegativeInsets( shadowInsets ) );
shadowSize = maxInset( shadowInsets );
@@ -100,6 +101,18 @@ 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 )

View File

@@ -31,6 +31,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
@@ -145,6 +146,21 @@ public class FlatEditorPaneUI
focusListener = null;
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
installKeyboardActions( getComponent() );
}
static void installKeyboardActions( JTextComponent c ) {
FlatScrollPaneUI.installSmoothScrollingDelegateActions( c, false,
/* page-down */ DefaultEditorKit.pageDownAction, // PAGE_DOWN
/* page-up */ DefaultEditorKit.pageUpAction, // PAGE_UP
/* DefaultEditorKit.selectionPageDownAction */ "selection-page-down", // shift PAGE_DOWN
/* DefaultEditorKit.selectionPageUpAction */ "selection-page-up" // shift PAGE_UP
);
}
@Override
protected Caret createCaret() {
return new FlatCaret( null, false );
@@ -159,6 +175,11 @@ public class FlatEditorPaneUI
super.propertyChange( e );
propertyChange( getComponent(), e, this::installStyle );
// BasicEditorPaneUI.propertyChange() re-applied actions from editor kit,
// which removed our delegate actions
if( "editorKit".equals( propertyName ) )
installKeyboardActions( getComponent() );
}
static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) {
@@ -171,7 +192,7 @@ public class FlatEditorPaneUI
case FlatClientProperties.STYLE_CLASS:
installStyle.run();
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
break;
}
}

View File

@@ -24,16 +24,13 @@ import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.function.Function;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
@@ -48,7 +45,6 @@ import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.filechooser.FileSystemView;
@@ -167,7 +163,6 @@ public class FlatFileChooserUI
{
private final FlatFileView fileView = new FlatFileView();
private FlatShortcutsPanel shortcutsPanel;
private JScrollPane shortcutsScrollPane;
public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c );
@@ -187,10 +182,7 @@ public class FlatFileChooserUI
FlatShortcutsPanel panel = createShortcutsPanel( fc );
if( panel.getComponentCount() > 0 ) {
shortcutsPanel = panel;
shortcutsScrollPane = new JScrollPane( shortcutsPanel,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
shortcutsScrollPane.setBorder( BorderFactory.createEmptyBorder() );
fc.add( shortcutsScrollPane, BorderLayout.LINE_START );
fc.add( shortcutsPanel, BorderLayout.LINE_START );
fc.addPropertyChangeListener( shortcutsPanel );
}
}
@@ -203,7 +195,6 @@ public class FlatFileChooserUI
if( shortcutsPanel != null ) {
fc.removePropertyChangeListener( shortcutsPanel );
shortcutsPanel = null;
shortcutsScrollPane = null;
}
}
@@ -332,7 +323,7 @@ public class FlatFileChooserUI
public Dimension getPreferredSize( JComponent c ) {
Dimension prefSize = super.getPreferredSize( c );
Dimension minSize = getMinimumSize( c );
int shortcutsPanelWidth = (shortcutsScrollPane != null) ? shortcutsScrollPane.getPreferredSize().width : 0;
int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0;
return new Dimension(
Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ),
Math.max( prefSize.height, minSize.height ) );
@@ -376,68 +367,27 @@ public class FlatFileChooserUI
if( icon != null )
return icon;
// new proxy icon
//
// Note: Since this is a super light weight icon object, we do not add it
// to the icon cache here. This keeps cache small in case of large directories
// with thousands of files when icons of all files are only needed to compute
// the layout of list/table, but never painted because located outside of visible area.
// When an icon needs to be painted, the proxy adds it to the icon cache
// and loads the real icon.
return new FlatFileViewIcon( f );
}
// get system icon
if( f != null ) {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
//---- class FlatFileViewIcon -----------------------------------------
/**
* A proxy icon that has a fixed (scaled) width/height (16x16) and
* gets/loads the real (system) icon only for painting.
* Avoids unnecessary getting/loading system icons.
*/
private class FlatFileViewIcon
implements Icon
{
private final File f;
private Icon realIcon;
FlatFileViewIcon( File f ) {
this.f = f;
}
@Override
public int getIconWidth() {
return UIScale.scale( 16 );
}
@Override
public int getIconHeight() {
return UIScale.scale( 16 );
}
@Override
public void paintIcon( Component c, Graphics g, int x, int y ) {
// get icon on demand
if( realIcon == null ) {
// get system icon
try {
if( f != null )
realIcon = getFileChooser().getFileSystemView().getSystemIcon( f );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
}
// get default icon
if( realIcon == null )
realIcon = FlatFileView.super.getIcon( f );
if( realIcon instanceof ImageIcon )
realIcon = new ScaledImageIcon( (ImageIcon) realIcon );
cacheIcon( f, this );
if( icon != null ) {
if( icon instanceof ImageIcon )
icon = new ScaledImageIcon( (ImageIcon) icon );
cacheIcon( f, icon );
return icon;
}
realIcon.paintIcon( c, g, x, y );
}
// get default icon
icon = super.getIcon( f );
if( icon instanceof ImageIcon ) {
icon = new ScaledImageIcon( (ImageIcon) icon );
cacheIcon( f, icon );
}
return icon;
}
}
@@ -446,7 +396,7 @@ public class FlatFileChooserUI
/** @since 2.3 */
public static class FlatShortcutsPanel
extends JToolBar
implements PropertyChangeListener, Scrollable
implements PropertyChangeListener
{
private final JFileChooser fc;
@@ -458,14 +408,13 @@ public class FlatFileChooserUI
protected final File[] files;
protected final JToggleButton[] buttons;
protected final ButtonGroup buttonGroup = new ButtonGroup();
protected final ButtonGroup buttonGroup;
@SuppressWarnings( "unchecked" )
public FlatShortcutsPanel( JFileChooser fc ) {
super( JToolBar.VERTICAL );
this.fc = fc;
setFloatable( false );
putClientProperty( FlatClientProperties.STYLE, "hoverButtonGroupBackground: null" );
buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) );
iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 );
@@ -475,25 +424,22 @@ public class FlatFileChooserUI
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
FileSystemView fsv = fc.getFileSystemView();
File[] files = JavaCompatibility2.getChooserShortcutPanelFiles( fsv );
File[] files = getChooserShortcutPanelFiles( fsv );
if( filesFunction != null )
files = filesFunction.apply( files );
this.files = files;
// create toolbar buttons
ArrayList<File> filesList = new ArrayList<>();
ArrayList<JToggleButton> buttonsList = new ArrayList<>();
for( File file : files ) {
if( file == null )
continue;
buttons = new JToggleButton[files.length];
buttonGroup = new ButtonGroup();
for( int i = 0; i < files.length; i++ ) {
// wrap drive path
if( fsv.isFileSystemRoot( file ) )
file = fsv.createFileObject( file.getAbsolutePath() );
if( fsv.isFileSystemRoot( files[i] ) )
files[i] = fsv.createFileObject( files[i].getAbsolutePath() );
File file = files[i];
String name = getDisplayName( fsv, file );
Icon icon = getIcon( fsv, file );
if( name == null )
continue;
// remove path from name
int lastSepIndex = name.lastIndexOf( File.separatorChar );
@@ -507,22 +453,16 @@ public class FlatFileChooserUI
icon = new ShortcutIcon( icon, iconSize.width, iconSize.height );
// create button
JToggleButton button = createButton( name, icon, file.toString() );
File f = file;
JToggleButton button = createButton( name, icon );
button.addActionListener( e -> {
fc.setCurrentDirectory( f );
fc.setCurrentDirectory( file );
} );
add( button );
buttonGroup.add( button );
filesList.add( file );
buttonsList.add( button );
buttons[i] = button;
}
this.files = filesList.toArray( new File[filesList.size()] );
this.buttons = buttonsList.toArray( new JToggleButton[buttonsList.size()] );
directoryChanged( fc.getCurrentDirectory() );
}
@@ -533,10 +473,8 @@ public class FlatFileChooserUI
return size;
}
/** @since 3.5 */
protected JToggleButton createButton( String name, Icon icon, String toolTip ) {
protected JToggleButton createButton( String name, Icon icon ) {
JToggleButton button = new JToggleButton( name, icon );
button.setToolTipText( toolTip );
button.setVerticalTextPosition( SwingConstants.BOTTOM );
button.setHorizontalTextPosition( SwingConstants.CENTER );
button.setAlignmentX( Component.CENTER_ALIGNMENT );
@@ -546,6 +484,32 @@ public class FlatFileChooserUI
return button;
}
protected File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
protected String getDisplayName( FileSystemView fsv, File file ) {
if( displayNameFunction != null ) {
String name = displayNameFunction.apply( file );
@@ -566,36 +530,29 @@ public class FlatFileChooserUI
if( doNotUseSystemIcons() )
return new FlatFileViewDirectoryIcon();
// Java 17+ supports getting larger system icons
try {
// Java 17+ supports getting larger system icons
try {
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
}
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
}
} catch( Exception ex ) {
// do not log InaccessibleObjectException because access
// may be denied via VM option '--illegal-access=deny' (default in Java 16)
// (not catching InaccessibleObjectException here because it is new in Java 9, but FlatLaf also runs on Java 8)
if( !"java.lang.reflect.InaccessibleObjectException".equals( ex.getClass().getName() ) )
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
return new FlatFileViewDirectoryIcon();
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
}
protected void directoryChanged( File file ) {
@@ -614,8 +571,6 @@ public class FlatFileChooserUI
buttonGroup.clearSelection();
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
@@ -624,41 +579,6 @@ public class FlatFileChooserUI
break;
}
}
//---- interface Scrollable ----
@Override
public Dimension getPreferredScrollableViewportSize() {
if( getComponentCount() > 0 ) {
Insets insets = getInsets();
int height = (getComponent( 0 ).getPreferredSize().height * 5) + insets.top + insets.bottom;
return new Dimension( getPreferredSize().width, height );
}
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) {
if( orientation == SwingConstants.VERTICAL && getComponentCount() > 0 )
return getComponent( 0 ).getPreferredSize().height;
return getScrollableBlockIncrement( visibleRect, orientation, direction ) / 10;
}
@Override
public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) {
return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
//---- class ShortcutIcon -------------------------------------------------

View File

@@ -1,290 +0,0 @@
/*
* 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.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Font;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JToolTip;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.LabelView;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.html.CSS;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.StyleSheet;
/**
* @author Karl Tauber
* @since 3.5
*/
public class FlatHTML
{
private FlatHTML() {}
/**
* Adds CSS rule BASE_SIZE to the style sheet of the HTML view,
* which re-calculates font sizes based on current component font size.
* This is necessary for "absolute-size" keywords (e.g. "x-large")
* for "font-size" attributes in default style sheet (see javax/swing/text/html/default.css).
* See also <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#values">CSS font-size</a>.
* <p>
* This method should be invoked after {@link BasicHTML#updateRenderer(JComponent, String)}.
*/
public static void updateRendererCSSFontBaseSize( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view == null )
return;
// dumpViews( view, 0 );
Document doc = view.getDocument();
if( !(doc instanceof HTMLDocument) )
return;
// add BASE_SIZE rule if necessary
// - if point size at index 7 is not 36, then probably HTML text contains BASE_SIZE rule
// - if point size at index 4 is equal to given font size, then it is not necessary to add BASE_SIZE rule
StyleSheet styleSheet = ((HTMLDocument)doc).getStyleSheet();
/*debug
for( int i = 1; i <= 7; i++ )
System.out.println( i+": "+ styleSheet.getPointSize( i ) );
debug*/
Font font = c.getFont();
if( styleSheet.getPointSize( 7 ) != 36f ||
font == null || styleSheet.getPointSize( 4 ) == font.getSize() )
return;
// check whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
if( !usesAbsoluteSizeKeywordForFontSize( view ) )
return;
// get HTML text from component
String text;
if( c instanceof JLabel )
text = ((JLabel)c).getText();
else if( c instanceof AbstractButton )
text = ((AbstractButton)c).getText();
else if( c instanceof JToolTip )
text = ((JToolTip)c).getTipText();
else
return;
if( text == null || !BasicHTML.isHTMLString( text ) )
return;
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + font.getSize() + "</style>";
String openTag = "";
String closeTag = "";
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = indexOfTag( text, "head", true )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex;
} else if( (styleIndex = indexOfTag( text, "style", false )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
// no <head> or <style> tag --> insert <head> tag after <html> tag
insertIndex = "<html>".length();
openTag = "<head>";
closeTag = "</head>";
}
String newText = text.substring( 0, insertIndex )
+ openTag + style + closeTag
+ text.substring( insertIndex );
BasicHTML.updateRenderer( c, newText );
// for unit tests
if( testUpdateRenderer != null )
testUpdateRenderer.accept( c, newText );
}
// for unit tests
static BiConsumer<JComponent, String> testUpdateRenderer;
/**
* Returns start or end index of a HTML tag.
* Checks only for leading '<' character and (case-ignore) tag name.
*/
private static int indexOfTag( String html, String tag, boolean endIndex ) {
int tagLength = tag.length();
int maxLength = html.length() - tagLength - 2;
char lastTagChar = tag.charAt( tagLength - 1 );
for( int i = "<html>".length(); i < maxLength; i++ ) {
// check for leading '<' and last tag name character
if( html.charAt( i ) == '<' && Character.toLowerCase( html.charAt( i + tagLength ) ) == lastTagChar ) {
// compare tag characters from last to first
for( int j = tagLength - 2; j >= 0; j-- ) {
if( Character.toLowerCase( html.charAt( i + 1 + j ) ) != tag.charAt( j ) )
break; // not equal
if( j == 0 ) {
// tag found
return endIndex ? html.indexOf( '>', i + tagLength ) + 1 : i;
}
}
}
}
return -1;
}
private static final Set<String> absoluteSizeKeywordsSet = new HashSet<>( Arrays.asList(
"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large" ) );
/**
* Checks whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
* (see javax/swing/text/html/default.css).
*/
private static boolean usesAbsoluteSizeKeywordForFontSize( View view ) {
AttributeSet attributes = view.getAttributes();
if( attributes != null ) {
Object fontSize = attributes.getAttribute( CSS.Attribute.FONT_SIZE );
if( fontSize != null ) {
if( absoluteSizeKeywordsSet.contains( fontSize.toString() ) )
return true;
}
}
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ ) {
if( usesAbsoluteSizeKeywordForFontSize( view.getView( i ) ) )
return true;
}
return false;
}
/**
* Updates foreground in style sheet of the HTML view.
* Adds "body { color: #&lt;foreground-hex&gt;; }"
*/
public static void updateRendererCSSForeground( View view, Color foreground ) {
Document doc = view.getDocument();
if( !(doc instanceof HTMLDocument) || foreground == null )
return;
// add foreground rule if necessary
// - use tag 'body' because BasicHTML.createHTMLView() also uses this tag
// to set font and color styles to component font/color
// see: SwingUtilities2.displayPropertiesToCSS()
// - this color is not used if component is disabled;
// JTextComponent.getDisabledTextColor() is used for disabled text components;
// UIManager.getColor("textInactiveText") is used for other disabled components
// see: javax.swing.text.GlyphView.paint()
Style bodyStyle = ((HTMLDocument)doc).getStyle( "body" );
if( bodyStyle == null ) {
StyleSheet styleSheet = ((HTMLDocument)doc).getStyleSheet();
styleSheet.addRule( String.format( "body { color: #%06x; }", foreground.getRGB() & 0xffffff ) );
clearViewCaches( view );
} else if( !foreground.equals( bodyStyle.getAttribute( StyleConstants.Foreground ) ) ) {
bodyStyle.addAttribute( StyleConstants.Foreground, foreground );
clearViewCaches( view );
}
}
/**
* Clears cached values in view so that CSS changes take effect.
*/
private static void clearViewCaches( View view ) {
if( view instanceof LabelView )
((LabelView)view).changedUpdate( null, null, null );
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ )
clearViewCaches( view.getView( i ) );
}
public static PropertyChangeListener createPropertyChangeListener( PropertyChangeListener superListener ) {
return e -> {
if( superListener != null )
superListener.propertyChange( e );
propertyChange( e );
};
}
/**
* Invokes {@link #updateRendererCSSFontBaseSize(JComponent)}
* for {@link BasicHTML#propertyKey} property change events,
* which are fired when {@link BasicHTML#updateRenderer(JComponent, String)}
* updates the HTML view.
*/
public static void propertyChange( PropertyChangeEvent e ) {
if( BasicHTML.propertyKey.equals( e.getPropertyName() ) && e.getNewValue() instanceof View )
updateRendererCSSFontBaseSize( (JComponent) e.getSource() );
}
/*debug
public static void dumpView( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view != null )
dumpViews( view, 0 );
}
public static void dumpViews( View view, int indent ) {
for( int i = 0; i < indent; i++ )
System.out.print( " " );
System.out.printf( "%s @%-8x %3d,%2d",
view.getClass().isAnonymousClass() ? view.getClass().getName() : view.getClass().getSimpleName(),
System.identityHashCode( view ),
(int) view.getPreferredSpan( View.X_AXIS ),
(int) view.getPreferredSpan( View.Y_AXIS ) );
AttributeSet attrs = view.getAttributes();
if( attrs != null ) {
Object fontSize = attrs.getAttribute( CSS.Attribute.FONT_SIZE );
System.out.printf( " %-8s", fontSize );
}
if( view instanceof javax.swing.text.GlyphView ) {
javax.swing.text.GlyphView gview = ((javax.swing.text.GlyphView)view);
java.awt.Font font = gview.getFont();
System.out.printf( " %3d-%-3d %s %2d (@%x) #%06x '%s'",
gview.getStartOffset(), gview.getEndOffset() - 1,
font.getName(), font.getSize(), System.identityHashCode( font ),
gview.getForeground().getRGB() & 0xffffff,
gview.getText( gview.getStartOffset(), gview.getEndOffset() ) );
}
System.out.println();
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ ) {
View child = view.getView( i );
dumpViews( child, indent + 1 );
}
}
debug*/
}

View File

@@ -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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
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 StyleableObject
implements StyleableBorder
{
@Styleable protected Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
@Styleable protected Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );

View File

@@ -22,7 +22,11 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
@@ -60,9 +64,6 @@ public class FlatLabelUI
{
@Styleable protected Color disabledForeground;
// only used via styling (not in UI defaults)
/** @since 3.5 */ @Styleable protected int arc = -1;
private final boolean shared;
private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
@@ -109,13 +110,16 @@ public class FlatLabelUI
super.installComponents( c );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( c );
updateHTMLRenderer( c, c.getText(), false );
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
String name = e.getPropertyName();
if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
if( name == "text" || name == "font" || name == "foreground" ) {
JLabel label = (JLabel) e.getSource();
updateHTMLRenderer( label, label.getText(), true );
} else if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
JLabel label = (JLabel) e.getSource();
if( shared && FlatStylingSupport.hasStyleProperty( label ) ) {
// unshare component UI if necessary
@@ -124,11 +128,9 @@ public class FlatLabelUI
} else
installStyle( label );
label.revalidate();
HiDPIUtils.repaint( label );
}
super.propertyChange( e );
FlatHTML.propertyChange( e );
label.repaint();
} else
super.propertyChange( e );
}
/** @since 2 */
@@ -163,10 +165,83 @@ public class FlatLabelUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void update( Graphics g, JComponent c ) {
FlatPanelUI.fillRoundedBackground( g, c, arc );
paint( g, c );
/**
* Checks whether text contains HTML tags that use "absolute-size" keywords
* (e.g. "x-large") for font-size in default style sheet
* (see javax/swing/text/html/default.css).
* If yes, adds a special CSS rule (BASE_SIZE) to the HTML text, which
* re-calculates font sizes based on current component font size.
*/
static void updateHTMLRenderer( JComponent c, String text, boolean always ) {
if( BasicHTML.isHTMLString( text ) &&
c.getClientProperty( "html.disable" ) != Boolean.TRUE &&
needsFontBaseSize( text ) )
{
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length();
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
// no <head> or <style> tag --> insert <head> tag after <html> tag
style = "<head>" + style + "</head>";
insertIndex = "<html>".length();
}
text = text.substring( 0, insertIndex )
+ style
+ text.substring( insertIndex );
} else if( !always )
return; // not necessary to invoke BasicHTML.updateRenderer()
BasicHTML.updateRenderer( c, text );
}
private static Set<String> tagsUseFontSizeSet;
private static boolean needsFontBaseSize( String text ) {
if( tagsUseFontSizeSet == null ) {
// tags that use font-size in javax/swing/text/html/default.css
tagsUseFontSizeSet = new HashSet<>( Arrays.asList(
"h1", "h2", "h3", "h4", "h5", "h6", "code", "kbd", "big", "small", "samp" ) );
}
// search for tags in HTML text
int textLength = text.length();
for( int i = 6; i < textLength - 1; i++ ) {
if( text.charAt( i ) == '<' ) {
switch( text.charAt( i + 1 ) ) {
// first letters of tags in tagsUseFontSizeSet
case 'b': case 'B':
case 'c': case 'C':
case 'h': case 'H':
case 'k': case 'K':
case 's': case 'S':
int tagBegin = i + 1;
for( i += 2; i < textLength; i++ ) {
if( !Character.isLetterOrDigit( text.charAt( i ) ) ) {
String tag = text.substring( tagBegin, i ).toLowerCase( Locale.ENGLISH );
if( tagsUseFontSizeSet.contains( tag ) )
return true;
break;
}
}
break;
}
}
}
return false;
}
static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) {

View File

@@ -22,21 +22,13 @@ import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.plaf.ComponentUI;
/**
* Line border for various components.
* <p>
*
* Paints a scaled (usually 1px thick) line around the component.
* The line thickness is not added to the border insets.
* The insets should be at least have line thickness (usually 1,1,1,1).
* <p>
* For {@link javax.swing.JPanel} and {@link javax.swing.JLabel}, this border
* can be used paint rounded background (if line color is {@code null}) or
* paint rounded line border with rounded background.
*
* @author Karl Tauber
*/
@@ -48,7 +40,7 @@ public class FlatLineBorder
/** @since 2 */ private final int arc;
public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f, -1 );
this( insets, lineColor, 1f, 0 );
}
/** @since 2 */
@@ -59,62 +51,26 @@ public class FlatLineBorder
this.arc = arc;
}
/** @since 3.5 */
public FlatLineBorder( Insets insets, int arc ) {
this( insets, null, 0, arc );
}
public Color getLineColor() {
return lineColor;
}
/**
* Returns the (unscaled) line thickness used to paint the border.
* The line thickness does not affect the border insets.
*/
public float getLineThickness() {
return lineThickness;
}
/**
* Returns the (unscaled) arc diameter of the border corners.
*
* @since 2
*/
/** @since 2 */
public int getArc() {
return arc;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null )
return;
Color lineColor = getLineColor();
float lineThickness = getLineThickness();
if( lineColor == null || lineThickness <= 0 )
return;
int arc = getArc();
if( arc < 0 ) {
// get arc from label or panel
ComponentUI ui = (c instanceof JLabel)
? ((JLabel)c).getUI()
: (c instanceof JPanel ? ((JPanel)c).getUI() : null);
if( ui instanceof FlatLabelUI )
arc = ((FlatLabelUI)ui).arc;
else if( ui instanceof FlatPanelUI )
arc = ((FlatPanelUI)ui).arc;
if( arc < 0 )
arc = 0;
}
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
0, 0, 0, scale( lineThickness ), scale( arc ), null, lineColor, null );
0, 0, 0, scale( getLineThickness() ), scale( getArc() ), null, getLineColor(), null );
} finally {
g2.dispose();
}

View File

@@ -43,7 +43,6 @@ import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -57,7 +56,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault List.foreground Color
* @uiDefault List.selectionBackground Color
* @uiDefault List.selectionForeground Color
* @uiDefault List.alternateRowColor Color
* @uiDefault List.dropLineColor Color
* @uiDefault List.border Border
* @uiDefault List.cellRenderer ListCellRenderer
@@ -94,7 +92,6 @@ public class FlatListUI
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
/** @since 3.6 */ @Styleable protected Color alternateRowColor;
/** @since 3 */ @Styleable protected Insets selectionInsets;
/** @since 3 */ @Styleable protected int selectionArc;
@@ -131,7 +128,6 @@ public class FlatListUI
selectionForeground = UIManager.getColor( "List.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" );
alternateRowColor = UIManager.getColor( "List.alternateRowColor" );
selectionInsets = UIManager.getInsets( "List.selectionInsets" );
selectionArc = UIManager.getInt( "List.selectionArc" );
@@ -146,7 +142,6 @@ public class FlatListUI
selectionForeground = null;
selectionInactiveBackground = null;
selectionInactiveForeground = null;
alternateRowColor = null;
oldStyleValues = null;
}
@@ -187,7 +182,7 @@ public class FlatListUI
case FlatClientProperties.STYLE_CLASS:
installStyle();
list.revalidate();
HiDPIUtils.repaint( list );
list.repaint();
break;
}
};
@@ -210,7 +205,7 @@ public class FlatListUI
Rectangle r = getCellBounds( list, firstIndex, lastIndex );
if( r != null ) {
int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
HiDPIUtils.repaint( list, r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) );
list.repaint( r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) );
}
}
};
@@ -302,18 +297,6 @@ public class FlatListUI
ListModel dataModel, ListSelectionModel selModel, int leadIndex )
{
boolean isSelected = selModel.isSelectedIndex( row );
boolean isDropRow = isDropRow( row );
// paint alternating rows
if( alternateRowColor != null && row % 2 != 0 &&
!"ComboBox.list".equals( list.getName() ) ) // combobox does not support alternate row color
{
g.setColor( alternateRowColor );
float arc = UIScale.scale( selectionArc / 2f );
FlatUIUtils.paintSelection( (Graphics2D) g, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height,
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
}
// get renderer component
@SuppressWarnings( "unchecked" )
@@ -321,7 +304,7 @@ public class FlatListUI
dataModel.getElementAt( row ), row, isSelected,
FlatUIUtils.isPermanentFocusOwner( list ) && (row == leadIndex) );
// use smaller cell width if list is used in JFileChooser
//
boolean isFileList = Boolean.TRUE.equals( list.getClientProperty( "List.isFileList" ) );
int cx, cw;
if( isFileList ) {
@@ -336,12 +319,13 @@ public class FlatListUI
}
// rounded selection or selection insets
if( (isSelected || isDropRow) &&
if( isSelected &&
!isFileList && // rounded selection is not supported for file list
(rendererComponent instanceof DefaultListCellRenderer ||
rendererComponent instanceof BasicComboBoxRenderer) &&
(selectionArc > 0 ||
(selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets ))) )
(selectionInsets != null &&
(selectionInsets.top != 0 || selectionInsets.left != 0 || selectionInsets.bottom != 0 || selectionInsets.right != 0))) )
{
// Because selection painting is done in the cell renderer, it would be
// necessary to require a FlatLaf specific renderer to implement rounded selection.
@@ -377,22 +361,7 @@ public class FlatListUI
this.getColor() == rendererComponent.getBackground() )
{
inPaintSelection = true;
if( isDropRow ) {
// for rounded drop background, it is necessary to first
// paint selection background because may be not rounded on some corners
if( isSelected ) {
Color oldColor = getColor();
setColor( list.getSelectionBackground() );
paintCellSelection( this, row, x, y, width, height );
setColor( oldColor );
}
// paint drop background
float arc = UIScale.scale( selectionArc / 2f );
FlatUIUtils.paintSelection( this, x, y, width, height,
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
} else
paintCellSelection( this, row, x, y, width, height );
paintCellSelection( this, row, x, y, width, height );
inPaintSelection = false;
} else
super.fillRect( x, y, width, height );
@@ -405,15 +374,7 @@ public class FlatListUI
rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true );
}
/**
* Paints (rounded) cell selection.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3
*/
/** @since 3 */
protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) {
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
@@ -450,7 +411,7 @@ public class FlatListUI
int leftIndex = locationToIndex( list, new Point( r.x - 1, r.y ) );
int rightIndex = locationToIndex( list, new Point( r.x + r.width, r.y ) );
// special handling for the case that last column contains fewer cells than the other columns
// special handling for the case that last column contains less cells than the other columns
boolean ltr = list.getComponentOrientation().isLeftToRight();
if( !ltr && leftIndex >= 0 && leftIndex != row && leftIndex == locationToIndex( list, new Point( r.x - 1, r.y - 1 ) ) )
leftIndex = -1;
@@ -479,8 +440,7 @@ public class FlatListUI
* Paints a cell selection at the given coordinates.
* The selection color must be set on the graphics context.
* <p>
* This method is intended for use in custom cell renderers
* to support {@link #selectionArc} and {@link #selectionInsets}.
* This method is intended for use in custom cell renderers.
*
* @since 3
*/
@@ -491,15 +451,4 @@ public class FlatListUI
FlatListUI ui = (FlatListUI) list.getUI();
ui.paintCellSelection( g, row, x, y, width, height );
}
/**
* Checks whether dropping on a row.
* See DefaultListCellRenderer.getListCellRendererComponent().
*/
private boolean isDropRow( int row ) {
JList.DropLocation dropLocation = list.getDropLocation();
return dropLocation != null &&
!dropLocation.isInsert() &&
dropLocation.getIndex() == row;
}
}

View File

@@ -21,10 +21,11 @@ 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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
/**
* Border for {@link javax.swing.JMenuBar}.
@@ -35,10 +36,27 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
*/
public class FlatMenuBarBorder
extends FlatMarginBorder
implements StyleableObject
implements StyleableBorder
{
@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 ) )

View File

@@ -38,6 +38,7 @@ import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicMenuBarUI;
@@ -142,10 +143,12 @@ public class FlatMenuBarUI
protected void installKeyboardActions() {
super.installKeyboardActions();
// get shared action map, used for all menu bars
ActionMap map = SwingUtilities.getUIActionMap( menuBar );
if( map != null && !(map.get( "takeFocus" ) instanceof TakeFocusAction) )
map.put( "takeFocus", new TakeFocusAction( "takeFocus" ) );
if( map == null ) {
map = new ActionMapUIResource();
SwingUtilities.replaceUIActionMap( menuBar, map );
}
map.put( "takeFocus", new TakeFocus( "takeFocus" ) );
}
/** @since 2 */
@@ -361,17 +364,17 @@ public class FlatMenuBarUI
}
}
//---- class TakeFocusAction ----------------------------------------------
//---- class TakeFocus ----------------------------------------------------
/**
* Activates the menu bar and shows mnemonics.
* On Windows, the popup of the first menu is not shown.
* On other platforms, the popup of the first menu is shown.
*/
private static class TakeFocusAction
private static class TakeFocus
extends FlatUIAction
{
TakeFocusAction( String name ) {
public TakeFocus( String name ) {
super( name );
}

View File

@@ -25,9 +25,7 @@ import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
@@ -37,7 +35,6 @@ import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
@@ -225,7 +222,7 @@ public class FlatMenuItemRenderer
}
// arrow size
if( arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem )) ) {
if( !isTopLevelMenu && arrowIcon != null ) {
// gap between text and arrow
if( accelText == null )
width += scale( textNoAcceleratorGap );
@@ -257,8 +254,7 @@ public class FlatMenuItemRenderer
boolean isTopLevelMenu = isTopLevelMenu( menuItem );
// layout arrow
boolean showArrowIcon = (arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem )));
if( showArrowIcon ) {
if( !isTopLevelMenu && arrowIcon != null ) {
arrowRect.width = arrowIcon.getIconWidth();
arrowRect.height = arrowIcon.getIconHeight();
} else
@@ -292,7 +288,7 @@ public class FlatMenuItemRenderer
int accelArrowWidth = accelRect.width + arrowRect.width;
if( accelText != null )
accelArrowWidth += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() );
if( showArrowIcon ) {
if( !isTopLevelMenu && arrowIcon != null ) {
if( accelText == null )
accelArrowWidth += scale( textNoAcceleratorGap );
accelArrowWidth += scale( acceleratorArrowGap );
@@ -359,7 +355,7 @@ debug*/
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground );
paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground );
paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground );
if( arrowIcon != null && (!isTopLevelMenu( menuItem ) || isInVerticalMenuBar( menuItem )) )
if( !isTopLevelMenu( menuItem ) )
paintArrowIcon( g, arrowRect, arrowIcon );
}
@@ -524,15 +520,6 @@ debug*/
return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu();
}
/** @since 3.5 */
public static boolean isInVerticalMenuBar( JMenuItem menuItem ) {
if( !(menuItem instanceof JMenu) || !(menuItem.getParent() instanceof JMenuBar) )
return false;
LayoutManager layout = menuItem.getParent().getLayout();
return layout instanceof GridLayout && ((GridLayout)layout).getRows() != 1;
}
protected boolean isUnderlineSelection() {
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
}

View File

@@ -103,23 +103,13 @@ public class FlatMenuItemUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */

View File

@@ -17,7 +17,6 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
@@ -47,7 +46,6 @@ import javax.swing.plaf.basic.BasicMenuUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -137,14 +135,6 @@ public class FlatMenuUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@@ -168,7 +158,7 @@ public class FlatMenuUI
JMenu menu = (JMenu) e.getSource();
if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) {
menu.getModel().setRollover( rollover );
HiDPIUtils.repaint( menu );
menu.repaint();
}
}
};
@@ -176,9 +166,7 @@ public class FlatMenuUI
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
@@ -283,7 +271,7 @@ public class FlatMenuUI
if( !isHover() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground );
Container menuBar = menuItem.getParent();
JMenuBar menuBar = (JMenuBar) menuItem.getParent();
JRootPane rootPane = SwingUtilities.getRootPane( menuBar );
if( rootPane != null && rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == menuBar &&
@@ -333,17 +321,12 @@ public class FlatMenuUI
}
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
Container menuItemParent = menuItem.getParent();
if( menuItemParent instanceof JMenuBar ) {
MenuBarUI ui = ((JMenuBar) menuItemParent).getUI();
if( ui instanceof FlatMenuBarUI ) {
T value = f.apply( (FlatMenuBarUI) ui );
if( value != null ) {
return value;
}
}
}
return defaultValue;
MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI();
if( !(ui instanceof FlatMenuBarUI) )
return defaultValue;
T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue;
}
}
}

View File

@@ -22,7 +22,6 @@ import java.security.CodeSource;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.NativeLibrary;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -37,18 +36,16 @@ class FlatNativeLibrary
private static boolean initialized;
private static NativeLibrary nativeLibrary;
private native static int getApiVersion();
/**
* Loads native library (if available) and returns whether loaded successfully.
* Returns {@code false} if no native library is available.
*/
static synchronized boolean isLoaded( int apiVersion ) {
initialize( apiVersion );
static synchronized boolean isLoaded() {
initialize();
return (nativeLibrary != null) ? nativeLibrary.isLoaded() : false;
}
private static void initialize( int apiVersion ) {
private static void initialize() {
if( initialized )
return;
initialized = true;
@@ -58,7 +55,6 @@ class FlatNativeLibrary
String classifier;
String ext;
boolean unknownArch = false;
if( SystemInfo.isWindows_10_orLater && (SystemInfo.isX86 || SystemInfo.isX86_64 || SystemInfo.isAARCH64) ) {
// Windows: requires Windows 10/11 (x86, x86_64 or aarch64)
@@ -82,23 +78,14 @@ class FlatNativeLibrary
//
// To avoid this, flatlaf.dll is not linked to jawt.dll,
// which avoids loading jawt.dll when flatlaf.dll is loaded.
// Instead, flatlaf.dll dynamically loads jawt.dll when first used,
// Instead flatlaf.dll dynamically loads jawt.dll when first used,
// which is guaranteed after AWT initialization.
} else if( SystemInfo.isMacOS_10_14_Mojave_orLater && (SystemInfo.isAARCH64 || SystemInfo.isX86_64) ) {
// macOS: requires macOS 10.14 or later (arm64 or x86_64)
} else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) {
// Linux: requires x86_64
classifier = SystemInfo.isAARCH64 ? "macos-arm64" : "macos-x86_64";
ext = "dylib";
} else if( SystemInfo.isLinux ) {
// Linux: x86_64 or aarch64 (but also supports unknown architectures)
classifier = SystemInfo.isAARCH64 ? "linux-arm64"
: (SystemInfo.isX86_64 ? "linux-x86_64"
: "linux-" + sanitize( System.getProperty( "os.arch" ) ));
classifier = "linux-x86_64";
ext = "so";
unknownArch = !SystemInfo.isX86_64 && !SystemInfo.isAARCH64;
// Load libjawt.so (part of JRE) explicitly because it is not found
// in all Java versions/distributions.
@@ -110,29 +97,10 @@ class FlatNativeLibrary
return; // no native library available for current OS or CPU architecture
// load native library
NativeLibrary nativeLibrary = createNativeLibrary( classifier, ext, unknownArch );
if( !nativeLibrary.isLoaded() )
return;
// check API version (and check whether library works)
try {
int actualApiVersion = getApiVersion();
if( actualApiVersion != apiVersion ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Wrong API version in native library (expected "
+ apiVersion + ", actual " + actualApiVersion + "). Ignoring native library.", null );
return;
}
} catch( Throwable ex ) {
// could be a UnsatisfiedLinkError in case that loading native library
// from temp directory was blocked by some OS security mechanism
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to get API version of native library. Ignoring native library.", ex );
return;
}
FlatNativeLibrary.nativeLibrary = nativeLibrary;
nativeLibrary = createNativeLibrary( classifier, ext );
}
private static NativeLibrary createNativeLibrary( String classifier, String ext, boolean unknownArch ) {
private static NativeLibrary createNativeLibrary( String classifier, String ext ) {
String libraryName = "flatlaf-" + classifier;
// load from "java.library.path" or from path specified in system property "flatlaf.nativeLibraryPath"
@@ -143,36 +111,13 @@ class FlatNativeLibrary
if( library.isLoaded() )
return library;
if( !unknownArch ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Native library '" + System.mapLibraryName( libraryName )
+ "' not found in java.library.path '" + System.getProperty( "java.library.path" )
+ "'. Using extracted native library instead.", null );
}
LoggingFacade.INSTANCE.logSevere( "Did not find library " + libraryName + " in java.library.path, using extracted library instead", null );
} else {
// try standard library naming scheme
// (same as in flatlaf.jar in package 'com/formdev/flatlaf/natives')
File libraryFile = new File( libraryPath, System.mapLibraryName( libraryName ) );
if( libraryFile.exists() )
return new NativeLibrary( libraryFile, true );
// try Maven naming scheme
// (see https://www.formdev.com/flatlaf/native-libraries/)
String libraryName2 = null;
File jarFile = getJarFile();
if( jarFile != null ) {
libraryName2 = buildLibraryName( jarFile, classifier, ext );
File libraryFile2 = new File( libraryPath, libraryName2 );
if( libraryFile2.exists() )
return new NativeLibrary( libraryFile2, true );
}
if( !unknownArch ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Native library '"
+ libraryFile.getName()
+ (libraryName2 != null ? ("' or '" + libraryName2) : "")
+ "' not found in '" + libraryFile.getParentFile().getAbsolutePath()
+ "'. Using extracted native library instead.", null );
}
LoggingFacade.INSTANCE.logSevere( "Did not find external library " + libraryFile + ", using extracted library instead", null );
}
}
@@ -183,7 +128,7 @@ class FlatNativeLibrary
return new NativeLibrary( libraryFile, true );
// load from FlatLaf jar (extract native library to temp folder)
return new NativeLibrary( "com/formdev/flatlaf/natives/" + libraryName, null, !unknownArch );
return new NativeLibrary( "com/formdev/flatlaf/natives/" + libraryName, null, true );
}
/**
@@ -200,51 +145,6 @@ class FlatNativeLibrary
* flatlaf-3.1-linux-x86_64.so
*/
private static File findLibraryBesideJar( String classifier, String ext ) {
// get location of FlatLaf jar (or fat/uber application jar)
File jarFile = getJarFile();
if( jarFile == null )
return null;
// build library file
String libraryName = buildLibraryName( jarFile, classifier, ext );
File jarDir = jarFile.getParentFile();
// check whether native library exists in same directory as jar
File libraryFile = new File( jarDir, libraryName );
if( libraryFile.isFile() )
return libraryFile;
// if jar is in "lib" directory, then also check whether native library exists
// in "../bin" directory
if( jarDir.getName().equalsIgnoreCase( "lib" ) ) {
libraryFile = new File( jarDir.getParentFile(), "bin/" + libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
// special case: support Gradle cache when running in development environment
// <user-home>/.gradle/caches/modules-2/files-2.1/com.formdev/flatlaf/<version>/<hash-1>/flatlaf-<version>.jar
// <user-home>/.gradle/caches/modules-2/files-2.1/com.formdev/flatlaf/<version>/<hash-2>/flatlaf-<version>-windows-x86_64.dll
String path = jarDir.getAbsolutePath().replace( '\\', '/' );
if( path.contains( "/.gradle/caches/" ) ) {
File versionDir = jarDir.getParentFile();
if( libraryName.contains( versionDir.getName() ) ) {
File[] dirs = versionDir.listFiles();
if( dirs != null ) {
for( File dir : dirs ) {
libraryFile = new File( dir, libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
}
}
}
// native library not found
return null;
}
private static File getJarFile() {
try {
// get location of FlatLaf jar
CodeSource codeSource = FlatNativeLibrary.class.getProtectionDomain().getCodeSource();
@@ -262,30 +162,31 @@ class FlatNativeLibrary
if( !jarFile.isFile() )
return null;
return jarFile;
// build library file
String jarName = jarFile.getName();
String jarBasename = jarName.substring( 0, jarName.lastIndexOf( '.' ) );
File parent = jarFile.getParentFile();
String libraryName = jarBasename
+ (jarBasename.contains( "flatlaf" ) ? "" : "-flatlaf")
+ '-' + classifier + '.' + ext;
// check whether native library exists in same directory as jar
File libraryFile = new File( parent, libraryName );
if( libraryFile.isFile() )
return libraryFile;
// if jar is in "lib" directory, then also check whether library exists
// in "../bin" directory
if( parent.getName().equalsIgnoreCase( "lib" ) ) {
libraryFile = new File( parent.getParentFile(), "bin/" + libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( ex.getMessage(), ex );
return null;
}
}
private static String buildLibraryName( File jarFile, String classifier, String ext ) {
String jarName = jarFile.getName();
String jarBasename = jarName.substring( 0, jarName.lastIndexOf( '.' ) );
// remove classifier "no-natives" (if used)
jarBasename = StringUtils.removeTrailing( jarBasename, "-no-natives" );
return jarBasename
+ (jarBasename.contains( "flatlaf" ) ? "" : "-flatlaf")
+ '-' + classifier + '.' + ext;
}
/**
* Allow only 'a'-'z', 'A'-'Z', '0'-'9', '_' and '-' in filenames.
*/
private static String sanitize( String s ) {
return s.replaceAll( "[^a-zA-Z0-9_-]", "_" );
return null;
}
private static void loadJAWT() {

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
@@ -24,7 +23,6 @@ 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;
/**
@@ -35,35 +33,15 @@ import com.formdev.flatlaf.util.SystemInfo;
* @author Karl Tauber
* @since 2.5
*/
public class FlatNativeLinuxLibrary
class FlatNativeLinuxLibrary
{
private static int API_VERSION_LINUX = 3003;
/**
* Checks whether native library is loaded/available.
* <p>
* <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.
*/
public static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded();
}
//---- X Window System ----------------------------------------------------
// direction for _NET_WM_MOVERESIZE message
// see https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html
static final int
SIZE_TOPLEFT = 0,
SIZE_TOP = 1,
SIZE_TOPRIGHT = 2,
SIZE_RIGHT = 3,
SIZE_BOTTOMRIGHT = 4,
SIZE_BOTTOM = 5,
SIZE_BOTTOMLEFT = 6,
SIZE_LEFT = 7,
MOVE = 8;
// see https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html
static final int MOVE = 8;
private static Boolean isXWindowSystem;
@@ -110,11 +88,7 @@ public class FlatNativeLinuxLibrary
}
private static Point scale( Window window, Point pt ) {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
if( gc == null )
return pt;
AffineTransform transform = gc.getDefaultTransform();
AffineTransform transform = window.getGraphicsConfiguration().getDefaultTransform();
int x = (int) Math.round( pt.x * transform.getScaleX() );
int y = (int) Math.round( pt.y * transform.getScaleY() );
return new Point( x, y );
@@ -128,110 +102,4 @@ public 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 );
}

View File

@@ -1,156 +0,0 @@
/*
* Copyright 2023 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Native methods for macOS.
* <p>
* <b>Note</b>: This is private API. Do not use!
*
* <h2>Methods that use windows as parameter</h2>
*
* For all methods that accept a {@link java.awt.Window} as parameter,
* the underlying macOS window must be already created,
* otherwise the method fails. You can use following to ensure this:
* <pre>{@code
* if( !window.isDisplayable() )
* window.addNotify();
* }</pre>
* or invoke the method after packing the window. E.g.
* <pre>{@code
* window.pack();
* }</pre>
*
* @author Karl Tauber
* @since 3.3
*/
public class FlatNativeMacLibrary
{
private static int API_VERSION_MACOS = 2002;
/**
* Checks whether native library is loaded/available.
* <p>
* <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.
*/
public static boolean isLoaded() {
return SystemInfo.isMacOS && FlatNativeLibrary.isLoaded( API_VERSION_MACOS );
}
public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor );
/** @since 3.4 */
public static final int
BUTTONS_SPACING_DEFAULT = 0,
BUTTONS_SPACING_MEDIUM = 1,
BUTTONS_SPACING_LARGE = 2;
/** @since 3.4 */ public native static boolean setWindowButtonsSpacing( Window window, int buttonsSpacing );
/** @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 );
}

View File

@@ -17,27 +17,20 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.beans.PropertyChangeListener;
import java.util.function.Predicate;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.BorderUIResource;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -61,15 +54,27 @@ public class FlatNativeWindowBorder
!SystemInfo.isWinPE &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true );
// check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
private static final boolean canUseJBRCustomDecorations =
canUseWindowDecorations &&
SystemInfo.isJetBrainsJVM_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false );
private static Boolean supported;
private static Provider nativeProvider;
public static boolean isSupported() {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.isSupported();
initialize();
return supported;
}
static Object install( JRootPane rootPane ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.install( rootPane );
if( !isSupported() )
return null;
@@ -158,6 +163,11 @@ public class FlatNativeWindowBorder
}
static void uninstall( JRootPane rootPane, Object data ) {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.uninstall( rootPane, data );
return;
}
if( !isSupported() )
return;
@@ -205,6 +215,9 @@ public class FlatNativeWindowBorder
}
public static boolean hasCustomDecoration( Window window ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.hasCustomDecoration( window );
if( !isSupported() )
return false;
@@ -212,6 +225,11 @@ public class FlatNativeWindowBorder
}
public static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setHasCustomDecoration( window, hasCustomDecoration );
return;
}
if( !isSupported() )
return;
@@ -219,18 +237,23 @@ public class FlatNativeWindowBorder
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
Predicate<Point> captionHitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
return;
}
if( !isSupported() )
return;
nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback,
nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
static boolean showWindow( Window window, int cmd ) {
if( !isSupported() )
if( canUseJBRCustomDecorations || !isSupported() )
return false;
return nativeProvider.showWindow( window, cmd );
@@ -271,7 +294,7 @@ public class FlatNativeWindowBorder
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void updateTitleBarInfo( Window window, int titleBarHeight, Predicate<Point> captionHitTestCallback,
void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );
@@ -297,36 +320,20 @@ public class FlatNativeWindowBorder
* No longer needed since Windows 11.
*/
static class WindowTopBorder
extends BorderUIResource.EmptyBorderUIResource
extends JBRCustomDecorations.JBRWindowTopBorder
{
private static WindowTopBorder instance;
private final Color activeLightColor = new Color( 0x707070 );
private final Color activeDarkColor = new Color( 0x2D2E2F );
private final Color inactiveLightColor = new Color( 0xaaaaaa );
private final Color inactiveDarkColor = new Color( 0x494A4B );
static JBRWindowTopBorder getInstance() {
if( canUseJBRCustomDecorations )
return JBRWindowTopBorder.getInstance();
private boolean colorizationAffectsBorders;
private Color activeColor;
static WindowTopBorder getInstance() {
if( instance == null )
instance = new WindowTopBorder();
return instance;
}
WindowTopBorder() {
super( 1, 0, 0, 0 );
update();
installListeners();
}
void update() {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
}
@Override
void installListeners() {
nativeProvider.addChangeListener( e -> {
update();
@@ -339,69 +346,19 @@ public class FlatNativeWindowBorder
} );
}
@Override
boolean isColorizationColorAffectsBorders() {
return nativeProvider.isColorizationColorAffectsBorders();
}
@Override
Color getColorizationColor() {
return nativeProvider.getColorizationColor();
}
@Override
int getColorizationColorBalance() {
return nativeProvider.getColorizationColorBalance();
}
private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders )
return null;
Color colorizationColor = getColorizationColor();
if( colorizationColor != null ) {
int colorizationColorBalance = getColorizationColorBalance();
if( colorizationColorBalance < 0 || colorizationColorBalance > 100 )
colorizationColorBalance = 100;
if( colorizationColorBalance == 0 )
return new Color( 0xD9D9D9 );
if( colorizationColorBalance == 100 )
return colorizationColor;
float alpha = colorizationColorBalance / 100.0f;
float remainder = 1 - alpha;
int r = Math.round( colorizationColor.getRed() * alpha + 0xD9 * remainder );
int g = Math.round( colorizationColor.getGreen() * alpha + 0xD9 * remainder );
int b = Math.round( colorizationColor.getBlue() * alpha + 0xD9 * remainder );
// avoid potential IllegalArgumentException in Color constructor
r = Math.min( Math.max( r, 0 ), 255 );
g = Math.min( Math.max( g, 0 ), 255 );
b = Math.min( Math.max( b, 0 ), 255 );
return new Color( r, g, b );
}
Color activeBorderColor = (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.frame.activeBorderColor" );
return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Window window = SwingUtilities.windowForComponent( c );
boolean active = window != null && window.isActive();
boolean dark = FlatLaf.isLafDark();
g.setColor( active
? (activeColor != null ? activeColor : (dark ? activeDarkColor : activeLightColor))
: (dark ? inactiveDarkColor : inactiveLightColor) );
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.fillRect( x, y, width, 1 );
}
void repaintBorder( Component c ) {
c.repaint( 0, 0, c.getWidth(), 1 );
}
}
}

View File

@@ -16,9 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Window;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -31,18 +29,10 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeWindowsLibrary
{
private static int API_VERSION_WINDOWS = 1002;
private static long osBuildNumber = Long.MIN_VALUE;
/**
* Checks whether native library is loaded/available.
* <p>
* <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.
*/
public static boolean isLoaded() {
return SystemInfo.isWindows && FlatNativeLibrary.isLoaded( API_VERSION_WINDOWS );
return SystemInfo.isWindows && FlatNativeLibrary.isLoaded();
}
/**
@@ -103,181 +93,15 @@ public class FlatNativeWindowsLibrary
public native static boolean setWindowCornerPreference( long hwnd, int cornerPreference );
/**
* DWMWINDOWATTRIBUTE
* see https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
*
* @since 3.3
*/
public static final int
DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
DWMWA_BORDER_COLOR = 34,
DWMWA_CAPTION_COLOR = 35,
DWMWA_TEXT_COLOR = 36;
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code BOOL} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
*
* @since 3.3
*/
public native static boolean dwmSetWindowAttributeBOOL( long hwnd, int attribute, boolean value );
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code DWORD} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
*
* @since 3.3
*/
public native static boolean dwmSetWindowAttributeDWORD( long hwnd, int attribute, int value );
/** @since 3.3 */
public static final int
// use this constant to reset any window part colors to the system default behavior
DWMWA_COLOR_DEFAULT = 0xFFFFFFFF,
// use this constant to specify that a window part should not be rendered
DWMWA_COLOR_NONE = 0xFFFFFFFE;
/** @since 3.3 */
public static final Color COLOR_NONE = new Color( 0, true );
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code COLORREF} attribute value.
* Sets the color of the window border.
* The red/green/blue values must be in range {@code 0 - 255}.
* If red is {@code -1}, then the system default border color is used (useful to reset the border color).
* If red is {@code -2}, then no border is painted.
* <p>
* Invokes Win32 API method {@code DwmSetWindowAttribute(DWMWA_BORDER_COLOR)}.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
* <p>
* Supported since Windows 11 Build 22000.
*
* @since 3.3
*/
public static boolean dwmSetWindowAttributeCOLORREF( long hwnd, int attribute, Color color ) {
// convert color to Windows RGB value
int rgb = (color == COLOR_NONE)
? DWMWA_COLOR_NONE
: (color != null
? (color.getRed() | (color.getGreen() << 8) | (color.getBlue() << 16))
: DWMWA_COLOR_DEFAULT);
// 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 '&amp;' for mnemonics (e.g. "&amp;Choose").
* Use '&amp;&amp;' for '&amp;' character (e.g. "Choose &amp;&amp; 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 '&amp;' for mnemonics (e.g. "&amp;Choose").
* Use '&amp;&amp;' for '&amp;' character (e.g. "Choose &amp;&amp; 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 );
public native static boolean setWindowBorderColor( long hwnd, int red, int green, int blue );
}

View File

@@ -30,7 +30,9 @@ import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicOptionPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
@@ -113,6 +115,13 @@ public class FlatOptionPaneUI
sameSizeButtons = FlatUIUtils.getUIBoolean( "OptionPane.sameSizeButtons", true );
}
@Override
protected void installComponents() {
super.installComponents();
updateChildPanels( optionPane );
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -146,13 +155,6 @@ public class FlatOptionPaneUI
protected Container createMessageArea() {
Container messageArea = super.createMessageArea();
// use non-UIResource OptionPane.messageAreaBorder to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
updateAreaPanel( messageArea );
// make known sub-panels non-opaque for OptionPane.background
updateKnownChildPanels( messageArea );
// set icon-message gap
if( iconMessageGap > 0 ) {
Component iconMessageSeparator = SwingUtils.getComponentByName( messageArea, "OptionPane.separator" );
@@ -167,10 +169,6 @@ public class FlatOptionPaneUI
protected Container createButtonArea() {
Container buttonArea = super.createButtonArea();
// use non-UIResource OptionPane.buttonAreaBorder to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
updateAreaPanel( buttonArea );
// scale button padding and subtract focusWidth
if( buttonArea.getLayout() instanceof ButtonAreaLayout ) {
ButtonAreaLayout layout = (ButtonAreaLayout) buttonArea.getLayout();
@@ -220,33 +218,22 @@ public class FlatOptionPaneUI
super.addMessageComponents( container, cons, msg, maxll, internallyCreated );
}
private void updateAreaPanel( Container area ) {
if( !(area instanceof JPanel) )
return;
// use non-UIResource border to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
JPanel panel = (JPanel) area;
panel.setBorder( FlatUIUtils.nonUIResource( panel.getBorder() ) );
panel.setOpaque( false );
}
private void updateKnownChildPanels( Container c ) {
private void updateChildPanels( Container c ) {
for( Component child : c.getComponents() ) {
if( child instanceof JPanel && child.getName() != null ) {
switch( child.getName() ) {
case "OptionPane.realBody":
case "OptionPane.body":
case "OptionPane.separator":
case "OptionPane.break":
// make known sub-panels non-opaque for OptionPane.background
((JPanel)child).setOpaque( false );
break;
}
if( child.getClass() == JPanel.class ) {
JPanel panel = (JPanel)child;
// make sub-panel non-opaque for OptionPane.background
panel.setOpaque( false );
// use non-UIResource borders to avoid that they are replaced when switching LaF
Border border = panel.getBorder();
if( border instanceof UIResource )
panel.setBorder( FlatUIUtils.nonUIResource( border ) );
}
if( child instanceof Container )
updateKnownChildPanels( (Container) child );
updateChildPanels( (Container) child );
}
}

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
@@ -24,14 +23,11 @@ import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -73,8 +69,6 @@ public class FlatPanelUI
super.installUI( c );
c.addPropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.registerPlaceholder( c );
installStyle( (JPanel) c );
}
@@ -84,20 +78,10 @@ public class FlatPanelUI
super.uninstallUI( c );
c.removePropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.unregisterPlaceholder( c );
oldStyleValues = null;
}
@Override
protected void installDefaults( JPanel p ) {
super.installDefaults( p );
if( p.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
LookAndFeel.installProperty( p, "opaque", false );
}
/** @since 2.0.1 */
@Override
public void propertyChange( PropertyChangeEvent e ) {
@@ -112,18 +96,7 @@ public class FlatPanelUI
} else
installStyle( c );
c.revalidate();
HiDPIUtils.repaint( c );
break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER:
JPanel p = (JPanel) e.getSource();
if( e.getOldValue() != null )
FullWindowContentSupport.unregisterPlaceholder( p );
if( e.getNewValue() != null )
FullWindowContentSupport.registerPlaceholder( p );
// make panel non-opaque for placeholders
LookAndFeel.installProperty( p, "opaque", e.getNewValue() == null );
c.repaint();
break;
}
}
@@ -162,52 +135,31 @@ public class FlatPanelUI
@Override
public void update( Graphics g, JComponent c ) {
fillRoundedBackground( g, c, arc );
paint( g, c );
}
/** @since 3.5 */
public static void fillRoundedBackground( Graphics g, JComponent c, int arc ) {
if( arc < 0 ) {
Border border = c.getBorder();
arc = ((border instanceof FlatLineBorder)
? ((FlatLineBorder)border).getArc()
: 0);
}
// fill background
if( c.isOpaque() ) {
if( arc > 0 ) {
// fill background with parent color to avoid garbage in rounded corners
int width = c.getWidth();
int height = c.getHeight();
int arc = (this.arc >= 0)
? this.arc
: ((c.getBorder() instanceof FlatLineBorder)
? ((FlatLineBorder)c.getBorder()).getArc()
: 0);
// fill background with parent color to avoid garbage in rounded corners
if( arc > 0 )
FlatUIUtils.paintParentBackground( g, c );
} else {
g.setColor( c.getBackground() );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
}
// fill rounded rectangle if having rounded corners
if( arc > 0 ) {
g.setColor( c.getBackground() );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(),
0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
if( arc > 0 ) {
// fill rounded rectangle if having rounded corners
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height,
0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} else
g.fillRect( 0, 0, width, height );
}
}
@Override
public Dimension getPreferredSize( JComponent c ) {
Object value = c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER );
if( value != null )
return FullWindowContentSupport.getPlaceholderPreferredSize( c, (String) value );
return super.getPreferredSize( c );
}
@Override
public void paint( Graphics g, JComponent c ) {
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.debugPaint( g, c );
paint( g, c );
}
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
@@ -40,9 +41,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;
/**
@@ -163,7 +163,7 @@ public class FlatPasswordFieldUI
}
private void repaint( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) {
HiDPIUtils.repaint( e.getComponent() );
e.getComponent().repaint();
scrollCaretToVisible();
}
}
@@ -214,12 +214,12 @@ public class FlatPasswordFieldUI
/** @since 2 */
@Override
protected Object applyStyleProperty( String key, Object value ) {
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) {
if( capsLockIconShared ) {
capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon );
capsLockIconShared = false;
}
return ((StyleableObject)capsLockIcon).applyStyleProperty( key, value );
return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value );
}
return super.applyStyleProperty( key, value );
@@ -229,15 +229,14 @@ public class FlatPasswordFieldUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
if( capsLockIcon instanceof StyleableObject )
infos.putAll( ((StyleableObject)capsLockIcon).getStyleableInfos() );
infos.put( "capsLockIconColor", Color.class );
return infos;
}
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject )
return ((StyleableObject)capsLockIcon).getStyleableValue( key );
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon )
return ((FlatCapsLockIcon)capsLockIcon).getStyleableValue( key );
return super.getStyleableValue( c, key );
}
@@ -327,7 +326,7 @@ public class FlatPasswordFieldUI
if( visible != revealButton.isVisible() ) {
revealButton.setVisible( visible );
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
if( !visible ) {
revealButton.setSelected( false );

View File

@@ -17,7 +17,6 @@
package com.formdev.flatlaf.ui;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
@@ -32,19 +31,15 @@ import java.awt.Panel;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowFocusListener;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
@@ -62,7 +57,6 @@ import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicComboPopup;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -76,13 +70,9 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatPopupFactory
extends PopupFactory
{
static final String KEY_POPUP_USES_NATIVE_BORDER = "FlatLaf.internal.FlatPopupFactory.popupUsesNativeBorder";
private MethodHandle java8getPopupMethod;
private MethodHandle java9getPopupMethod;
private final ArrayList<NonFlashingPopup> stillShownHeavyWeightPopups = new ArrayList<>();
@Override
public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException
@@ -93,48 +83,28 @@ public class FlatPopupFactory
y = pt.y;
}
fixLinuxWaylandJava21focusIssue( owner );
// reuse a heavy weight popup window, which is still shown on screen,
// to avoid flicker when popup (e.g. tooltip) is moving while mouse is moved
for( NonFlashingPopup popup : stillShownHeavyWeightPopups ) {
if( popup.delegate != null &&
popup.owner == owner &&
(popup.contents == contents ||
(popup.contents instanceof JToolTip && contents instanceof JToolTip)) )
{
stillShownHeavyWeightPopups.remove( popup );
return reuseStillShownHeavyWeightPopups( popup, contents, x, y );
}
}
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, isForceHeavyWeight( owner, contents, x, y ) ), owner, contents );
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), contents );
// macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
if( popup.popupWindow != null && isMacOSBorderSupported() )
setupRoundedBorder( popup.popupWindow, owner, contents );
return popup;
}
if( SystemInfo.isMacOS || SystemInfo.isLinux )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
// Windows 11 with FlatLaf native library can use rounded corners and shows drop shadow for heavy weight popups
int borderCornerRadius;
if( isWindows11BorderSupported() &&
getBorderCornerRadius( owner, contents ) > 0 )
(borderCornerRadius = getBorderCornerRadius( owner, contents )) > 0 )
{
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( popup.popupWindow != null )
setupRoundedBorder( popup.popupWindow, owner, contents );
setupWindows11Border( popup.popupWindow, contents, borderCornerRadius );
return popup;
}
// create drop shadow popup
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 )
: new NonFlashingPopup( popupForScreenOfOwner, owner, contents );
return new DropShadowPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
}
/**
@@ -185,6 +155,67 @@ public class FlatPopupFactory
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
private boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
private int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/**
* There is no API in Java 8 to force creation of heavy weight popups,
* but it is possible with reflection. Java 9 provides a new method.
@@ -220,56 +251,6 @@ 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;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private static Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/**
* Reuse a heavy weight popup window, which is still shown on screen,
* by updating window location and contents.
* This avoid flicker when popup (e.g. a tooltip) is moving while mouse is moved.
* E.g. overridden JComponent.getToolTipLocation(MouseEvent).
* See ToolTipManager.checkForTipChange(MouseEvent).
*/
private static NonFlashingPopup reuseStillShownHeavyWeightPopups(
NonFlashingPopup reusePopup, Component contents, int ownerX, int ownerY )
{
// clone popup because PopupFactory.getPopup() should not return old instance
NonFlashingPopup popup = reusePopup.cloneForReuse();
// update popup location, size and contents
popup.reset( contents, ownerX, ownerY );
return popup;
}
//---- tooltips -----------------------------------------------------------
/**
* Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20).
* In case that the tooltip would be partly outside of the screen,
@@ -304,13 +285,13 @@ public class FlatPopupFactory
break;
}
}
if( gc == null && owner != null )
if( gc == null )
gc = owner.getGraphicsConfiguration();
if( gc == null )
return null;
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
int screenTop = screenBounds.y + screenInsets.top;
// place tooltip above mouse location if there is enough space
@@ -354,84 +335,48 @@ public class FlatPopupFactory
((JComponent)owner).getToolTipLocation( me ) != null;
}
//---- native rounded border ----------------------------------------------
private static boolean isWindows11BorderSupported() {
return SystemInfo.isWindows_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, true ) &&
FlatNativeWindowsLibrary.isLoaded();
return SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded();
}
private static boolean isMacOSBorderSupported() {
return SystemInfo.isMacOS &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, true ) &&
FlatNativeMacLibrary.isLoaded();
}
private static void setupWindows11Border( Window popupWindow, Component contents, int borderCornerRadius ) {
// make sure that the Windows 11 window is created
if( !popupWindow.isDisplayable() )
popupWindow.addNotify();
private static void setupRoundedBorder( Window popupWindow, Component owner, Component contents ) {
int borderCornerRadius = getBorderCornerRadius( owner, contents );
float borderWidth = getRoundedBorderWidth( owner, contents );
// get window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
// get Swing border color
Color borderColor;
// set corner preference
int cornerPreference = (borderCornerRadius <= 4)
? FlatNativeWindowsLibrary.DWMWCP_ROUNDSMALL // 4px
: FlatNativeWindowsLibrary.DWMWCP_ROUND; // 8px
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
// set border color
int red = -1; // use system default color
int green = 0;
int blue = 0;
if( contents instanceof JComponent ) {
Border border = ((JComponent)contents).getBorder();
border = FlatUIUtils.unwrapNonUIResourceBorder( border );
// get color from border of contents (e.g. JPopupMenu or JToolTip)
Color borderColor = null;
if( border instanceof FlatLineBorder )
borderColor = ((FlatLineBorder)border).getLineColor();
else if( border instanceof LineBorder )
borderColor = ((LineBorder)border).getLineColor();
else if( border instanceof EmptyBorder )
borderColor = FlatNativeWindowsLibrary.COLOR_NONE; // do not paint border
else
borderColor = null; // use system default color
red = -2; // do not paint border
// avoid that FlatLineBorder paints the Swing border
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, true );
} else
borderColor = null; // use system default color
if( popupWindow.isDisplayable() ) {
// native window already created
setupRoundedBorderImpl( popupWindow, borderCornerRadius, borderWidth, borderColor );
} else {
// native window not yet created --> add listener to set native border after window creation
AtomicReference<HierarchyListener> l = new AtomicReference<>();
l.set( e -> {
if( e.getID() == HierarchyEvent.HIERARCHY_CHANGED &&
(e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0 )
{
setupRoundedBorderImpl( popupWindow, borderCornerRadius, borderWidth, borderColor );
popupWindow.removeHierarchyListener( l.get() );
}
} );
popupWindow.addHierarchyListener( l.get() );
}
}
private static void setupRoundedBorderImpl( Window popupWindow, int borderCornerRadius, float borderWidth, Color borderColor ) {
if( SystemInfo.isWindows ) {
// get native window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
// set corner preference
int cornerPreference = (borderCornerRadius <= 4)
? FlatNativeWindowsLibrary.DWMWCP_ROUNDSMALL // 4px
: FlatNativeWindowsLibrary.DWMWCP_ROUND; // 8px
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
// set border color
FlatNativeWindowsLibrary.dwmSetWindowAttributeCOLORREF( hwnd, FlatNativeWindowsLibrary.DWMWA_BORDER_COLOR, borderColor );
} else if( SystemInfo.isMacOS ) {
if( borderColor == null || borderColor == FlatNativeWindowsLibrary.COLOR_NONE )
borderWidth = 0;
// set corner radius, border width and color
FlatNativeMacLibrary.setWindowRoundedBorder( popupWindow, borderCornerRadius,
borderWidth, (borderColor != null) ? borderColor.getRGB() : 0 );
if( borderColor != null ) {
red = borderColor.getRed();
green = borderColor.getGreen();
blue = borderColor.getBlue();
}
}
FlatNativeWindowsLibrary.setWindowBorderColor( hwnd, red, green, blue );
}
private static void resetWindows11Border( Window popupWindow ) {
@@ -444,164 +389,20 @@ public class FlatPopupFactory
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, FlatNativeWindowsLibrary.DWMWCP_DONOTROUND );
}
private static int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
private static float getRoundedBorderWidth( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.roundedBorderWidth" :
(contents instanceof JPopupMenu) ? "PopupMenu.roundedBorderWidth" :
(contents instanceof JToolTip) ? "ToolTip.roundedBorderWidth" :
"Popup.roundedBorderWidth";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_ROUNDED_BORDER_WIDTH, uiKey );
return (value instanceof Number) ? ((Number)value).floatValue() : 0;
}
//---- 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;
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return false;
Rectangle r = new Rectangle( new Point( x, y ), contents.getPreferredSize() );
return overlapsHeavyWeightComponent( window, r );
}
private static boolean overlapsHeavyWeightComponent( Component parent, Rectangle r ) {
if( !parent.isVisible() || !r.intersects( parent.getBounds() ) )
return false;
if( !parent.isLightweight() && !(parent instanceof Window) )
return true;
if( parent instanceof Container ) {
Rectangle r2 = new Rectangle( r.x - parent.getX(), r.y - parent.getY(), r.width, r.height );
for( Component c : ((Container)parent).getComponents() ) {
if( overlapsHeavyWeightComponent( c, r2 ) )
return true;
}
}
return false;
}
/**
* On Linux with Wayland, since Java 21, Swing adds a window focus listener to popup owner/invoker window,
* which hides the popup as soon as the owner/invoker window looses focus.
* This works fine for light-weight popups.
* It also works for heavy-weight popups if they do not request focus.
* Because FlatLaf always uses heavy-weight popups, all popups that request focus
* are broken since Java 21.
*
* This method removes the problematic window focus listener.
*
* https://bugs.openjdk.org/browse/JDK-8280993
* https://github.com/openjdk/jdk/pull/13830
*/
private static void fixLinuxWaylandJava21focusIssue( Component owner ) {
// only necessary on Linux when running in Java 21+
if( owner == null || !SystemInfo.isLinux || SystemInfo.javaVersion < SystemInfo.toVersion( 21, 0, 0, 0 ) )
return;
// get window
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return;
// remove window focus listener, which was added from class sun.awt.UNIXToolkit since Java 21
for( WindowFocusListener l : window.getWindowFocusListeners() ) {
if( "sun.awt.UNIXToolkit$1".equals( l.getClass().getName() ) ) {
window.removeWindowFocusListener( l );
break;
}
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
if( !popupWindow.isVisible() )
popup.show();
else {
// if the popup window is already visible (because it is reused),
// do not invoke Popup.show() because this would invoke Window.toFront(),
// which may have the side effect that an inactive owner window
// would be also moved to front and maybe hide previously active window
popupWindow.pack();
}
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
//---- class NonFlashingPopup ---------------------------------------------
/**
* Fixes popup background flashing effect when using dark theme on light platform theme,
* where the light popup background is shown for a fraction of a second before
* the dark popup content is shown.
* This is fixed by setting popup background to content background.
* <p>
* Defers hiding of heavy weight popup window for an event cycle,
* which allows reusing popup window to avoid flicker when "moving" popup.
*/
private class NonFlashingPopup
private static class NonFlashingPopup
extends Popup
{
private Popup delegate;
Component owner;
private Component contents;
// heavy weight
Window popupWindow;
protected Window popupWindow;
private Color oldPopupWindowBackground;
private boolean disposed;
NonFlashingPopup( Popup delegate, Component owner, Component contents ) {
NonFlashingPopup( Popup delegate, Component contents ) {
this.delegate = delegate;
this.owner = owner;
this.contents = contents;
popupWindow = SwingUtilities.windowForComponent( contents );
@@ -615,52 +416,10 @@ public class FlatPopupFactory
}
}
private NonFlashingPopup( NonFlashingPopup reusePopup ) {
delegate = reusePopup.delegate;
owner = reusePopup.owner;
contents = reusePopup.contents;
popupWindow = reusePopup.popupWindow;
oldPopupWindowBackground = reusePopup.oldPopupWindowBackground;
}
NonFlashingPopup cloneForReuse() {
return new NonFlashingPopup( this );
}
@Override
public final void show() {
if( disposed )
return;
showImpl();
}
void showImpl() {
public void show() {
if( delegate != null ) {
// On macOS and Linux, the empty popup window is shown in popup.show()
// (in peer.setVisible(true) invoked from Component.show()),
// but the popup content is painted later via repaint manager.
// This may cause some flicker, especially during JVM warm-up or
// when running JVM in interpreter mode (option -Xint).
// To reduce flicker, immediately paint popup content as soon as popup window becomes visible.
// This also fixes a problem with JetBrainsRuntime JVM, where sometimes the popups were empty.
if( (SystemInfo.isMacOS || SystemInfo.isLinux) && popupWindow instanceof JWindow ) {
HierarchyListener l = e -> {
if( e.getID() == HierarchyEvent.HIERARCHY_CHANGED &&
(e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 )
{
((JWindow)popupWindow).getRootPane().paintImmediately(
0, 0, popupWindow.getWidth(), popupWindow.getHeight() );
}
};
popupWindow.addHierarchyListener( l );
try {
showPopupAndFixLocation( delegate, popupWindow );
} finally {
popupWindow.removeHierarchyListener( l );
}
} else
showPopupAndFixLocation( delegate, popupWindow );
showPopupAndFixLocation( delegate, popupWindow );
// increase tooltip size if necessary because it may be too small on HiDPI screens
// https://bugs.openjdk.java.net/browse/JDK-8213535
@@ -682,39 +441,10 @@ public class FlatPopupFactory
}
@Override
public final void hide() {
if( disposed )
return;
disposed = true;
// immediately hide non-heavy weight popups, popup menus and combobox popups
// of if system property is false
if( !(popupWindow instanceof JWindow) || contents instanceof JPopupMenu ||
!FlatSystemProperties.getBoolean( FlatSystemProperties.REUSE_VISIBLE_POPUP_WINDOW, true ) )
{
hideImpl();
return;
}
// defer hiding of heavy weight popup window for an event cycle,
// which allows reusing popup window to avoid flicker when "moving" popup
((JWindow)popupWindow).getContentPane().removeAll();
stillShownHeavyWeightPopups.add( this );
EventQueue.invokeLater( () -> {
// hide popup if it was not reused
if( stillShownHeavyWeightPopups.remove( this ) )
hideImpl();
} );
}
void hideImpl() {
if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
public void hide() {
if( delegate != null ) {
delegate.hide();
delegate = null;
owner = null;
contents = null;
}
@@ -722,54 +452,18 @@ public class FlatPopupFactory
// restore background so that it can not affect other LaFs (when switching)
// because popup windows are cached and reused
popupWindow.setBackground( oldPopupWindowBackground );
// On macOS, popupWindow.setBackground(...) invoked from constructor,
// has no affect if the popup window peer (a NSWindow) is already created,
// which is the case when reusing a cached popup window
// (see class PopupFactory.HeavyWeightPopup).
// This may result in flicker when e.g. showing a popup in a light theme,
// then switching to a dark theme and again showing a popup,
// because the underling NSWindow still has a light background,
// which may be shown shortly before the actual dark popup content is shown.
// To fix this, dispose the popup window, which disposes the NSWindow.
// The AWT popup window stays in the popup cache, but when reusing it later,
// a new peer and a new NSWindow is created and gets the correct background.
if( SystemInfo.isMacOS )
popupWindow.dispose();
popupWindow = null;
}
}
void reset( Component contents, int ownerX, int ownerY ) {
// update popup window location
popupWindow.setLocation( ownerX, ownerY );
// replace component in content pane
Container contentPane = ((JWindow)popupWindow).getContentPane();
contentPane.removeAll();
contentPane.add( contents, BorderLayout.CENTER );
popupWindow.pack();
// update client property on contents
if( this.contents != contents ) {
Object old = (this.contents instanceof JComponent)
? ((JComponent)this.contents).getClientProperty( KEY_POPUP_USES_NATIVE_BORDER )
: null;
if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, old );
this.contents = contents;
}
}
}
//---- class DropShadowPopup ----------------------------------------------
private class DropShadowPopup
extends NonFlashingPopup
implements ComponentListener
{
private final Component owner;
// light weight
private JComponent lightComp;
private Border oldBorder;
@@ -784,11 +478,11 @@ public class FlatPopupFactory
// heavy weight
private Popup dropShadowDelegate;
private Window dropShadowWindow;
private JPanel dropShadowPanel2;
private Color oldDropShadowWindowBackground;
DropShadowPopup( Popup delegate, Component owner, Component contents ) {
super( delegate, owner, contents );
super( delegate, contents );
this.owner = owner;
Dimension size = contents.getPreferredSize();
if( size.width <= 0 || size.height <= 0 )
@@ -804,31 +498,31 @@ public class FlatPopupFactory
// the drop shadow and is positioned behind the popup window.
// create panel that paints the drop shadow
dropShadowPanel2 = new JPanel();
dropShadowPanel2.setBorder( createDropShadowBorder() );
dropShadowPanel2.setOpaque( false );
JPanel dropShadowPanel = new JPanel();
dropShadowPanel.setBorder( createDropShadowBorder() );
dropShadowPanel.setOpaque( false );
// set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel2.getInsets();
dropShadowPanel2.setPreferredSize( new Dimension(
Insets insets = dropShadowPanel.getInsets();
dropShadowPanel.setPreferredSize( new Dimension(
prefSize.width + insets.left + insets.right,
prefSize.height + insets.top + insets.bottom ) );
// create heavy weight popup for drop shadow
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel2, x, y, true );
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel, x, y, true );
// make drop shadow popup window translucent
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel2 );
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel );
if( dropShadowWindow != null ) {
oldDropShadowWindowBackground = dropShadowWindow.getBackground();
dropShadowWindow.setBackground( new Color( 0, true ) );
}
// Windows 11: reset corner preference on reused heavy weight popups
if( SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded() ) {
if( isWindows11BorderSupported() ) {
resetWindows11Border( popupWindow );
if( dropShadowWindow != null )
resetWindows11Border( dropShadowWindow );
@@ -858,23 +552,6 @@ public class FlatPopupFactory
}
}
private DropShadowPopup( DropShadowPopup reusePopup ) {
super( reusePopup );
// not necessary to clone fields used for light/medium weight popups
// heavy weight
dropShadowDelegate = reusePopup.dropShadowDelegate;
dropShadowWindow = reusePopup.dropShadowWindow;
dropShadowPanel2 = reusePopup.dropShadowPanel2;
oldDropShadowWindowBackground = reusePopup.oldDropShadowWindowBackground;
}
@Override
NonFlashingPopup cloneForReuse() {
return new DropShadowPopup( this );
}
private Border createDropShadowBorder() {
return new FlatDropShadowBorder(
UIManager.getColor( "Popup.dropShadowColor" ),
@@ -883,14 +560,14 @@ public class FlatPopupFactory
}
@Override
void showImpl() {
public void show() {
if( dropShadowDelegate != null )
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
if( mediumWeightPanel != null )
showMediumWeightDropShadow();
super.showImpl();
super.show();
// fix location of light weight popup in case it has left or top drop shadow
if( lightComp != null ) {
@@ -898,22 +575,13 @@ public class FlatPopupFactory
if( insets.left != 0 || insets.top != 0 )
lightComp.setLocation( lightComp.getX() - insets.left, lightComp.getY() - insets.top );
}
if( popupWindow != null ) {
removeAllPopupWindowComponentListeners();
popupWindow.addComponentListener( this );
}
}
@Override
void hideImpl() {
if( popupWindow != null )
removeAllPopupWindowComponentListeners();
public void hide() {
if( dropShadowDelegate != null ) {
dropShadowDelegate.hide();
dropShadowDelegate = null;
dropShadowPanel2 = null;
}
if( mediumWeightPanel != null ) {
@@ -922,7 +590,7 @@ public class FlatPopupFactory
mediumWeightPanel = null;
}
super.hideImpl();
super.hide();
if( dropShadowWindow != null ) {
dropShadowWindow.setBackground( oldDropShadowWindowBackground );
@@ -1006,58 +674,5 @@ public class FlatPopupFactory
if( dropShadowPanel != null && mediumWeightPanel != null )
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
}
@Override
void reset( Component contents, int ownerX, int ownerY ) {
if( popupWindow != null )
removeAllPopupWindowComponentListeners();
super.reset( contents, ownerX, ownerY );
updateDropShadowWindowBounds();
}
private void updateDropShadowWindowBounds() {
if( dropShadowWindow == null )
return;
// calculate size of drop shadow window
Dimension size = popupWindow.getSize();
Insets insets = dropShadowPanel2.getInsets();
int w = size.width + insets.left + insets.right;
int h = size.height + insets.top + insets.bottom;
// update drop shadow popup window bounds
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowWindow.setBounds( x, y, w, h );
dropShadowWindow.validate();
}
private void removeAllPopupWindowComponentListeners() {
// make sure that there is no old component listener
// necessary because this class is cloned if reusing popup windows
for( ComponentListener l : popupWindow.getComponentListeners() ) {
if( l instanceof DropShadowPopup )
popupWindow.removeComponentListener( l );
}
}
//---- interface ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
if( e.getSource() == popupWindow )
updateDropShadowWindowBounds();
}
@Override
public void componentMoved( ComponentEvent e ) {
if( e.getSource() == popupWindow )
updateDropShadowWindowBounds();
}
@Override public void componentShown( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
}

View File

@@ -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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
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 StyleableObject
implements StyleableBorder
{
private Color borderColor;

View File

@@ -239,14 +239,12 @@ public class FlatPopupMenuUI
if( gc == null && popupMenu.getInvoker() != null )
gc = popupMenu.getInvoker().getGraphicsConfiguration();
if( gc == null )
return new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
// compute screen bounds
// compute screen height
// (always subtract screen insets because there is no API to detect whether
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
Toolkit toolkit = Toolkit.getDefaultToolkit();
Rectangle screenBounds = (gc != null) ? gc.getBounds() : new Rectangle( toolkit.getScreenSize() );
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
return FlatUIUtils.subtractInsets( screenBounds, screenInsets );
}

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -87,17 +86,6 @@ public class FlatProgressBarUI
installStyle();
}
@Override
public void uninstallUI( JComponent c ) {
if( !EventQueue.isDispatchThread() && progressBar.isIndeterminate() ) {
LoggingFacade.INSTANCE.logSevere(
"FlatLaf: Uninstalling indeterminate progress bar UI not on AWT thread may throw NPE in FlatProgressBarUI.paint(). Use SwingUtilities.invokeLater().",
new IllegalStateException() );
}
super.uninstallUI( c );
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -122,25 +110,17 @@ public class FlatProgressBarUI
propertyChangeListener = e -> {
switch( e.getPropertyName() ) {
case "indeterminate":
if( !EventQueue.isDispatchThread() && !progressBar.isIndeterminate() ) {
LoggingFacade.INSTANCE.logSevere(
"FlatLaf: Using JProgressBar.setIndeterminate(false) not on AWT thread may throw NPE in FlatProgressBarUI.paint(). Use SwingUtilities.invokeLater().",
new IllegalStateException() );
}
break;
case PROGRESS_BAR_LARGE_HEIGHT:
case PROGRESS_BAR_SQUARE:
progressBar.revalidate();
HiDPIUtils.repaint( progressBar );
progressBar.repaint();
break;
case STYLE:
case STYLE_CLASS:
installStyle();
progressBar.revalidate();
HiDPIUtils.repaint( progressBar );
progressBar.repaint();
break;
}
};
@@ -294,6 +274,6 @@ public class FlatProgressBarUI
// Only solution is to repaint whole progress bar.
double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() );
if( (int) systemScaleFactor != systemScaleFactor )
HiDPIUtils.repaint( progressBar );
progressBar.repaint();
}
}

View File

@@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
@@ -103,23 +102,13 @@ public class FlatRadioButtonMenuItemUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */

View File

@@ -35,20 +35,16 @@ import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicHTML;
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;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -162,10 +158,6 @@ public class FlatRadioButtonUI
/** @since 2 */
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
@@ -175,7 +167,7 @@ public class FlatRadioButtonUI
} else
installStyle( b );
b.revalidate();
HiDPIUtils.repaint( b );
b.repaint();
break;
}
}
@@ -204,17 +196,16 @@ public class FlatRadioButtonUI
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) ) {
Icon icon = getRealIcon( b );
if( !(icon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( !(icon instanceof FlatCheckBoxIcon) )
return new UnknownStyleException( key );
if( icon == this.icon && iconShared ) {
this.icon = icon = FlatStylingSupport.cloneIcon( icon );
if( iconShared ) {
icon = FlatStylingSupport.cloneIcon( icon );
iconShared = false;
}
key = key.substring( "icon.".length() );
return ((StyleableObject)icon).applyStyleProperty( key, value );
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
}
if( "iconTextGap".equals( key ) && value instanceof Integer )
@@ -227,9 +218,10 @@ public class FlatRadioButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
Icon icon = getRealIcon( c );
if( icon instanceof StyleableObject )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((StyleableObject)icon).getStyleableInfos() );
if( icon instanceof FlatCheckBoxIcon ) {
for( Map.Entry<String, Class<?>> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() )
infos.put( "icon.".concat( e.getKey() ), e.getValue() );
}
return infos;
}
@@ -238,9 +230,8 @@ public class FlatRadioButtonUI
public Object getStyleableValue( JComponent c, String key ) {
// style icon
if( key.startsWith( "icon." ) ) {
Icon icon = getRealIcon( c );
return (icon instanceof StyleableObject)
? ((StyleableObject)icon).getStyleableValue( key.substring( "icon.".length() ) )
return (icon instanceof FlatCheckBoxIcon)
? ((FlatCheckBoxIcon)icon).getStyleableValue( key.substring( "icon.".length() ) )
: null;
}
@@ -271,7 +262,7 @@ public class FlatRadioButtonUI
@Override
public void paint( Graphics g, JComponent c ) {
// fill background even if not opaque and if:
// fill background even if not opaque if
// - contentAreaFilled is true and
// - if background color is different to default background color
// (this paints selection if using the component as cell renderer)
@@ -287,27 +278,20 @@ public class FlatRadioButtonUI
int focusWidth = getIconFocusWidth( c );
if( focusWidth > 0 ) {
boolean ltr = c.getComponentOrientation().isLeftToRight();
int halign = ((AbstractButton)c).getHorizontalAlignment();
if( halign == SwingConstants.LEADING )
halign = ltr ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = ltr ? SwingConstants.RIGHT : SwingConstants.LEFT;
Insets insets = c.getInsets( tempInsets );
if( (focusWidth > insets.left || focusWidth > insets.right) &&
(halign == SwingConstants.LEFT || halign == SwingConstants.RIGHT) )
{
int leftOrRightInset = ltr ? insets.left : insets.right;
if( focusWidth > leftOrRightInset ) {
// The left (or right) inset is smaller than the focus width, which may be
// the case if insets were explicitly reduced (e.g. with an EmptyBorder).
// In this case the width has been increased in getPreferredSize() and
// here it is necessary to fix icon and text painting location.
int offset = (halign == SwingConstants.LEFT)
? Math.max( focusWidth - insets.left, 0 )
: -Math.max( focusWidth - insets.right, 0 );
int offset = focusWidth - leftOrRightInset;
if( !ltr )
offset = -offset;
// move the graphics origin to the left (or right)
g.translate( offset, 0 );
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
super.paint( g, c );
g.translate( -offset, 0 );
return;
}
@@ -334,23 +318,16 @@ public class FlatRadioButtonUI
}
private int getIconFocusWidth( JComponent c ) {
Icon icon = getRealIcon( c );
AbstractButton b = (AbstractButton) c;
Icon icon = b.getIcon();
if( icon == null )
icon = getDefaultIcon();
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 );
}
//---- class FlatRadioButtonListener --------------------------------------
/** @since 2 */

View File

@@ -20,7 +20,6 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -29,10 +28,11 @@ import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import java.util.function.Function;
import javax.swing.JComponent;
import javax.swing.JDialog;
@@ -41,6 +41,7 @@ import javax.swing.JLayeredPane;
import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.BorderUIResource;
@@ -87,8 +88,8 @@ public class FlatRootPaneUI
private Object nativeWindowBorderData;
private LayoutManager oldLayout;
private ComponentListener macFullWindowContentListener;
private PropertyChangeListener macWindowBackgroundListener;
private PropertyChangeListener ancestorListener;
private ComponentListener componentListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI();
@@ -106,7 +107,6 @@ public class FlatRootPaneUI
installBorder();
installNativeWindowBorder();
macInstallFullWindowContentSupport();
}
protected void installBorder() {
@@ -123,7 +123,6 @@ public class FlatRootPaneUI
uninstallNativeWindowBorder();
uninstallClientDecorations();
macUninstallFullWindowContentSupport();
rootPane = null;
}
@@ -154,31 +153,13 @@ public class FlatRootPaneUI
Container parent = c.getParent();
if( parent instanceof JFrame || parent instanceof JDialog ) {
Color background = parent.getBackground();
if( background == null || background instanceof UIResource ) {
if( SystemInfo.isMacOS ) {
// Setting window background on macOS immediately fills the whole window
// with that color, and slightly delayed, the Swing repaint manager
// repaints the actual window content. This results in some flashing
// when switching from a light to a dark theme (or vice versa).
// --> delay setting window background and immediately repaint window content
Runnable r = () -> {
parent.setBackground( UIManager.getColor( "control" ) );
c.paintImmediately( 0, 0, c.getWidth(), c.getHeight() );
};
// for class FlatAnimatedLafChange:
// if animated Laf change is in progress, set background color when
// animation has finished to avoid/reduce flashing
if( c.getClientProperty( "FlatLaf.internal.animatedLafChange" ) != null )
c.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", r );
else
EventQueue.invokeLater( r );
} else
parent.setBackground( UIManager.getColor( "control" ) );
}
if( background == null || background instanceof UIResource )
parent.setBackground( UIManager.getColor( "control" ) );
}
macClearBackgroundForTranslucentWindow( c );
// enable dark window appearance on macOS when running in JetBrains Runtime
if( SystemInfo.isJetBrainsJVM && SystemInfo.isMacOS_10_14_Mojave_orLater )
c.putClientProperty( "jetbrains.awt.windowDarkAppearance", FlatLaf.isLafDark() );
}
@Override
@@ -198,20 +179,55 @@ public class FlatRootPaneUI
protected void installListeners( JRootPane root ) {
super.installListeners( root );
if( SystemInfo.isMacFullWindowContentSupported )
macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root );
macInstallWindowBackgroundListener( root );
if( SystemInfo.isJava_9_orLater ) {
// On HiDPI screens, where scaling is used, there may be white lines on the
// bottom and on the right side of the window when it is initially shown.
// This is very disturbing in dark themes, but hard to notice in light themes.
// Seems to be a rounding issue when Swing adds dirty region of window
// using RepaintManager.nativeAddDirtyRegion().
//
// Note: Not using a HierarchyListener here, which would be much easier,
// because this causes problems with mouse clicks in heavy-weight popups.
// Instead, add a listener to the root pane that waits until it is added
// to a window, then add a component listener to the window.
// See: https://github.com/JFormDesigner/FlatLaf/issues/371
ancestorListener = e -> {
Object oldValue = e.getOldValue();
Object newValue = e.getNewValue();
if( newValue instanceof Window ) {
if( componentListener == null ) {
componentListener = new ComponentAdapter() {
@Override
public void componentShown( ComponentEvent e ) {
// add whole root pane to dirty regions when window is initially shown
root.getParent().repaint( root.getX(), root.getY(), root.getWidth(), root.getHeight() );
}
};
}
((Window)newValue).addComponentListener( componentListener );
} else if( newValue == null && oldValue instanceof Window ) {
if( componentListener != null )
((Window)oldValue).removeComponentListener( componentListener );
}
};
root.addPropertyChangeListener( "ancestor", ancestorListener );
}
}
@Override
protected void uninstallListeners( JRootPane root ) {
super.uninstallListeners( root );
if( SystemInfo.isMacFullWindowContentSupported ) {
FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener );
macFullWindowContentListener = null;
if( SystemInfo.isJava_9_orLater ) {
if( componentListener != null ) {
Window window = SwingUtilities.windowForComponent( root );
if( window != null )
window.removeComponentListener( componentListener );
componentListener = null;
}
root.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null;
}
macUninstallWindowBackgroundListener( root );
}
/** @since 1.1.2 */
@@ -291,141 +307,19 @@ public class FlatRootPaneUI
// layer title pane under frame content layer to allow placing menu bar over title pane
protected final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
private final static Integer TITLE_PANE_MOUSE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 2;
private final static Integer WINDOW_TOP_BORDER_LAYER = Integer.MAX_VALUE;
// for fullWindowContent mode, layer title pane over frame content layer to allow placing title bar buttons over content
/** @since 3.4 */
protected final static Integer TITLE_PANE_FULL_WINDOW_CONTENT_LAYER = JLayeredPane.FRAME_CONTENT_LAYER + 1;
private Integer getLayerForTitlePane() {
return isFullWindowContent( rootPane ) ? TITLE_PANE_FULL_WINDOW_CONTENT_LAYER : TITLE_PANE_LAYER;
}
protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane != null ) {
if( titlePane != null )
layeredPane.remove( titlePane );
layeredPane.remove( titlePane.mouseLayer );
if( titlePane.windowTopBorderLayer != null )
layeredPane.remove( titlePane.windowTopBorderLayer );
}
if( newTitlePane != null ) {
layeredPane.add( newTitlePane, getLayerForTitlePane() );
layeredPane.add( newTitlePane.mouseLayer, TITLE_PANE_MOUSE_LAYER );
if( newTitlePane.windowTopBorderLayer != null && newTitlePane.isWindowTopBorderNeeded() && isFullWindowContent( rootPane ) )
layeredPane.add( newTitlePane.windowTopBorderLayer, WINDOW_TOP_BORDER_LAYER );
}
if( newTitlePane != null )
layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
titlePane = newTitlePane;
}
private void macInstallFullWindowContentSupport() {
if( !SystemInfo.isMacOS )
return;
// set window buttons spacing
if( isMacButtonsSpacingSupported() && rootPane.isDisplayable() ) {
int buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT;
String value = (String) rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING );
if( value != null ) {
switch( value ) {
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM:
buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_MEDIUM;
break;
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE:
buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_LARGE;
break;
}
}
FlatNativeMacLibrary.setWindowButtonsSpacing( getParentWindow( rootPane ), buttonsSpacing );
}
// update buttons bounds client property
FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
}
private void macUninstallFullWindowContentSupport() {
if( !SystemInfo.isMacOS )
return;
// do not uninstall when switching to another FlatLaf theme
if( UIManager.getLookAndFeel() instanceof FlatLaf )
return;
// reset window buttons spacing
if( isMacButtonsSpacingSupported() )
FlatNativeMacLibrary.setWindowButtonsSpacing( getParentWindow( rootPane ), FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT );
// remove buttons bounds client property
FullWindowContentSupport.macUninstallFullWindowContentButtonsBoundsProperty( rootPane );
}
private boolean isMacButtonsSpacingSupported() {
return SystemInfo.isMacOS && SystemInfo.isJava_17_orLater && FlatNativeMacLibrary.isLoaded();
}
private void macInstallWindowBackgroundListener( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null && macWindowBackgroundListener == null ) {
macWindowBackgroundListener = e -> macClearBackgroundForTranslucentWindow( c );
window.addPropertyChangeListener( "background", macWindowBackgroundListener );
}
}
private void macUninstallWindowBackgroundListener( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null && macWindowBackgroundListener != null ) {
window.removePropertyChangeListener( "background", macWindowBackgroundListener );
macWindowBackgroundListener = null;
}
}
/**
* When setting window background to translucent color (alpha < 255),
* Swing paints that window translucent on Windows and Linux, but not on macOS.
* The reason for this is that FlatLaf sets the background color of the root pane,
* and Swing behaves a bit differently on macOS than on other platforms in that case.
* Other L&Fs do not set root pane background, which is {@code null} by default.
* <p>
* To fix this problem, set the root pane background to {@code null}
* if windows uses a translucent background.
*/
private void macClearBackgroundForTranslucentWindow( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null ) {
Color windowBackground = window.getBackground();
if( windowBackground != null &&
windowBackground.getAlpha() < 255 &&
c.getBackground() instanceof UIResource )
{
// clear root pane background
c.setBackground( null );
}
}
}
private Window getParentWindow( JRootPane c ) {
// not using SwingUtilities.windowForComponent() or SwingUtilities.getWindowAncestor()
// here because root panes may be nested and used anywhere (e.g. in JInternalFrame)
// but we're only interested in the "root" root pane, which is a direct child of the window
Container parent = c.getParent();
return (parent instanceof Window) ? (Window) parent : null;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
@@ -470,33 +364,6 @@ public class FlatRootPaneUI
titlePane.titleBarColorsChanged();
break;
case FlatClientProperties.TITLE_BAR_HEIGHT:
if( titlePane != null )
titlePane.revalidate();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT:
if( titlePane != null ) {
rootPane.getLayeredPane().setLayer( titlePane, getLayerForTitlePane() );
if( titlePane.windowTopBorderLayer != null ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane.isWindowTopBorderNeeded() && isFullWindowContent( rootPane ) )
layeredPane.add( titlePane.windowTopBorderLayer, WINDOW_TOP_BORDER_LAYER );
else
layeredPane.remove( titlePane.windowTopBorderLayer );
}
titlePane.updateIcon();
titlePane.updateVisibility();
titlePane.updateFullWindowContentButtonsBoundsProperty();
}
FullWindowContentSupport.revalidatePlaceholders( rootPane );
rootPane.revalidate();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS:
FullWindowContentSupport.revalidatePlaceholders( rootPane );
break;
case FlatClientProperties.GLASS_PANE_FULL_HEIGHT:
rootPane.revalidate();
break;
@@ -505,47 +372,14 @@ public class FlatRootPaneUI
if( rootPane.isDisplayable() )
throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." );
break;
case "ancestor":
if( e.getNewValue() instanceof Window ) {
if( titlePane != null && !Objects.equals( titlePane.windowStyle, FlatTitlePane.getWindowStyle( rootPane ) ) )
setTitlePane( createTitlePane() );
macClearBackgroundForTranslucentWindow( rootPane );
}
macUninstallWindowBackgroundListener( rootPane );
macInstallWindowBackgroundListener( rootPane );
// FlatNativeMacLibrary.setWindowButtonsSpacing() and
// FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty()
// require a native window, but setting the client properties
// "apple.awt.fullWindowContent" or FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING
// is usually done before the native window is created
// --> try again when native window is created
if( e.getNewValue() instanceof Window )
macInstallFullWindowContentSupport();
break;
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING:
macInstallFullWindowContentSupport();
break;
case "apple.awt.fullWindowContent":
if( SystemInfo.isMacFullWindowContentSupported )
FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
break;
}
}
/** @since 3.4 */
protected static boolean isFullWindowContent( JRootPane rootPane ) {
return FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.FULL_WINDOW_CONTENT, false );
}
protected static boolean isMenuBarEmbedded( JRootPane rootPane ) {
FlatTitlePane titlePane = getTitlePane( rootPane );
return titlePane != null && titlePane.isMenuBarEmbedded();
RootPaneUI ui = rootPane.getUI();
return ui instanceof FlatRootPaneUI &&
((FlatRootPaneUI)ui).titlePane != null &&
((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
}
/** @since 2.4 */
@@ -581,21 +415,23 @@ public class FlatRootPaneUI
private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent;
Dimension titlePaneSize = (titlePane != null)
? getSizeFunc.apply( titlePane )
: new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() )
: rootPane.getSize(); // same as in JRootPane.RootLayout.preferredLayoutSize()
: rootPane.getSize();
int width = contentSize.width; // title pane width is not considered here
int height = contentSize.height;
if( titlePane != null && !isFullWindowContent( rootPane ) )
height += getSizeFunc.apply( titlePane ).height;
int height = titlePaneSize.height + contentSize.height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) {
Dimension menuBarSize = getSizeFunc.apply( menuBar );
width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
}
Dimension menuBarSize = (menuBar != null && menuBar.isVisible())
? getSizeFunc.apply( menuBar )
: new Dimension();
width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
}
Insets insets = rootPane.getInsets();
@@ -620,29 +456,12 @@ public class FlatRootPaneUI
if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height );
// title pane (is a child of layered pane)
// title pane
int nextY = 0;
if( titlePane != null ) {
int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0;
boolean isFullWindowContent = isFullWindowContent( rootPane );
if( isFullWindowContent && !UIManager.getBoolean( FlatTitlePane.KEY_DEBUG_SHOW_RECTANGLES ) ) {
// place title bar into top-right corner
int tw = Math.min( titlePane.getPreferredSize().width, width );
int tx = titlePane.getComponentOrientation().isLeftToRight() ? width - tw : 0;
titlePane.setBounds( tx, 0, tw, prefHeight );
} else
titlePane.setBounds( 0, 0, width, prefHeight );
titlePane.mouseLayer.setBounds( 0, 0, width, prefHeight );
if( titlePane.windowTopBorderLayer != null ) {
boolean show = isFullWindowContent && !titlePane.isWindowMaximized() && !isFullScreen;
if( show )
titlePane.windowTopBorderLayer.setBounds( 0, 0, width, 1 );
titlePane.windowTopBorderLayer.setVisible( show );
}
if( !isFullWindowContent )
nextY += prefHeight;
titlePane.setBounds( 0, 0, width, prefHeight );
nextY += prefHeight;
}
// glass pane
@@ -653,7 +472,7 @@ public class FlatRootPaneUI
rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset );
}
// menu bar (is a child of layered pane)
// menu bar
JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) {
boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded();
@@ -661,23 +480,13 @@ public class FlatRootPaneUI
titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() );
} else {
int mx = 0;
int mw = width;
if( titlePane != null && isFullWindowContent( rootPane ) ) {
// make menu bar width smaller to avoid that it overlaps title bar buttons
int tw = Math.min( titlePane.getPreferredSize().width, width );
mw -= tw;
if( !titlePane.getComponentOrientation().isLeftToRight() )
mx = tw;
}
Dimension prefSize = menuBar.getPreferredSize();
menuBar.setBounds( mx, nextY, mw, prefSize.height );
menuBar.setBounds( 0, nextY, width, prefSize.height );
nextY += prefSize.height;
}
}
// content pane (is a child of layered pane)
// content pane
Container contentPane = rootPane.getContentPane();
if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
@@ -690,7 +499,7 @@ public class FlatRootPaneUI
@Override
public void invalidateLayout( Container parent ) {
if( titlePane != null )
titlePane.menuBarInvalidate();
titlePane.menuBarChanged();
}
@Override
@@ -710,7 +519,7 @@ public class FlatRootPaneUI
* Window border used for non-native window decorations.
*/
public static class FlatWindowBorder
extends FlatEmptyBorder
extends BorderUIResource.EmptyBorderUIResource
{
protected final Color activeBorderColor = UIManager.getColor( "RootPane.activeBorderColor" );
protected final Color inactiveBorderColor = UIManager.getColor( "RootPane.inactiveBorderColor" );
@@ -743,10 +552,7 @@ public class FlatRootPaneUI
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
float lineWidth = (float) (UIScale.scale( 1f ) * scaleFactor);
g.fill( FlatUIUtils.createRectangle( x, y, width, height, lineWidth ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
g.drawRect( x, y, width - 1, height - 1 );
}
protected boolean isWindowMaximized( Component c ) {

View File

@@ -22,6 +22,7 @@ import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
@@ -39,11 +40,13 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -204,6 +207,16 @@ public class FlatScrollBarUI
oldStyleValues = null;
}
@Override
protected TrackListener createTrackListener() {
return new FlatTrackListener();
}
@Override
protected ScrollListener createScrollListener() {
return new FlatScrollListener();
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -213,14 +226,14 @@ public class FlatScrollBarUI
switch( e.getPropertyName() ) {
case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS:
scrollbar.revalidate();
HiDPIUtils.repaint( scrollbar );
scrollbar.repaint();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollbar.revalidate();
HiDPIUtils.repaint( scrollbar );
scrollbar.repaint();
break;
case "componentOrientation":
@@ -246,7 +259,7 @@ public class FlatScrollBarUI
// because scroll bars do not receive mouse exited event.
// The scroll pane, including its scroll bars, is not part
// of the component hierarchy and does not receive mouse events
// directly. Instead, LWComponentPeer receives mouse events
// directly. Instead LWComponentPeer receives mouse events
// and delegates them to peers, but entered/exited events
// are sent only for the whole scroll pane.
// Exited event is only sent when mouse leaves scroll pane.
@@ -432,6 +445,197 @@ public class FlatScrollBarUI
return allowsAbsolutePositioning;
}
@Override
protected void scrollByBlock( int direction ) {
runAndSetValueAnimated( () -> {
super.scrollByBlock( direction );
} );
}
@Override
protected void scrollByUnit( int direction ) {
runAndSetValueAnimated( () -> {
super.scrollByUnit( direction );
} );
}
/**
* Runs the given runnable, which should modify the scroll bar value,
* and then animate scroll bar value from old value to new value.
*/
public void runAndSetValueAnimated( Runnable r ) {
if( inRunAndSetValueAnimated || !isSmoothScrollingEnabled() ) {
r.run();
return;
}
inRunAndSetValueAnimated = true;
if( animator != null )
animator.cancel();
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( true );
// remember current scrollbar value so that we can start scroll animation from there
int oldValue = scrollbar.getValue();
// run given runnable, which computes and sets the new scrollbar value
FlatScrollPaneUI.runWithoutBlitting( scrollbar.getParent(), () ->{
// if invoked while animation is running, calculation of new value
// should start at the previous target value
if( targetValue != Integer.MIN_VALUE )
scrollbar.setValue( targetValue );
r.run();
} );
// do not use animation if started dragging thumb
if( isDragging ) {
// do not clear valueIsAdjusting here
inRunAndSetValueAnimated = false;
return;
}
int newValue = scrollbar.getValue();
if( newValue != oldValue ) {
// start scroll animation if value has changed
setValueAnimated( oldValue, newValue );
} else {
// clear valueIsAdjusting if value has not changed
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( false );
}
inRunAndSetValueAnimated = false;
}
private boolean inRunAndSetValueAnimated;
private Animator animator;
private int startValue = Integer.MIN_VALUE;
private int targetValue = Integer.MIN_VALUE;
private boolean useValueIsAdjusting = true;
int getTargetValue() {
return targetValue;
}
public void setValueAnimated( int initialValue, int value ) {
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( true );
// (always) set scrollbar value to initial value
scrollbar.setValue( initialValue );
// do some check if animation already running
if( animator != null && animator.isRunning() && targetValue != Integer.MIN_VALUE ) {
// Ignore requests if animation still running and scroll direction is the same
// and new value is within currently running animation.
// Without this check, repeating-scrolling via keyboard would become
// very slow when reaching the top/bottom/left/right of the viewport,
// because it would start a new 200ms animation to scroll a few pixels.
if( value == targetValue ||
(value > startValue && value < targetValue) || // scroll down/right
(value < startValue && value > targetValue) ) // scroll up/left
return;
}
startValue = initialValue;
targetValue = value;
// create animator
if( animator == null ) {
int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 );
int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 );
Object interpolator = UIManager.get( "ScrollPane.smoothScrolling.interpolator" );
animator = new Animator( duration, fraction -> {
if( scrollbar == null || !scrollbar.isShowing() ) {
animator.stop();
return;
}
// re-enable valueIsAdjusting if disabled while animation is running
// (e.g. in mouse released listener)
if( useValueIsAdjusting && !scrollbar.getValueIsAdjusting() )
scrollbar.setValueIsAdjusting( true );
scrollbar.setValue( startValue + Math.round( (targetValue - startValue) * fraction ) );
}, () -> {
startValue = targetValue = Integer.MIN_VALUE;
if( useValueIsAdjusting && scrollbar != null )
scrollbar.setValueIsAdjusting( false );
});
animator.setResolution( resolution );
animator.setInterpolator( (interpolator instanceof Animator.Interpolator)
? (Animator.Interpolator) interpolator
: new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) );
}
// restart animator
animator.cancel();
animator.start();
}
protected boolean isSmoothScrollingEnabled() {
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( FlatSystemProperties.SMOOTH_SCROLLING, true ) )
return false;
// if scroll bar is child of scroll pane, check only client property of scroll pane
Container parent = scrollbar.getParent();
JComponent c = (parent instanceof JScrollPane) ? (JScrollPane) parent : scrollbar;
Object smoothScrolling = c.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING );
if( smoothScrolling instanceof Boolean )
return (Boolean) smoothScrolling;
// Note: Getting UI value "ScrollPane.smoothScrolling" here to allow
// applications to turn smooth scrolling on or off at any time
// (e.g. in application options dialog).
return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
}
//---- class FlatTrackListener --------------------------------------------
protected class FlatTrackListener
extends TrackListener
{
@Override
public void mousePressed( MouseEvent e ) {
// Do not use valueIsAdjusting here (in runAndSetValueAnimated())
// for smooth scrolling because super.mousePressed() enables this itself
// and super.mouseRelease() disables it later.
// If we would disable valueIsAdjusting here (in runAndSetValueAnimated())
// and move the thumb with the mouse, then the thumb location is not updated
// if later scrolled with a key (e.g. HOME key).
useValueIsAdjusting = false;
runAndSetValueAnimated( () -> {
super.mousePressed( e );
} );
}
@Override
public void mouseReleased( MouseEvent e ) {
super.mouseReleased( e );
useValueIsAdjusting = true;
}
}
//---- class FlatScrollListener -------------------------------------------
protected class FlatScrollListener
extends ScrollListener
{
@Override
public void actionPerformed( ActionEvent e ) {
runAndSetValueAnimated( () -> {
super.actionPerformed( e );
} );
}
}
//---- class ScrollBarHoverListener ---------------------------------------
// using static field to disabling hover for other scroll bars
@@ -493,7 +697,7 @@ public class FlatScrollBarUI
private void repaint() {
if( scrollbar.isEnabled() )
HiDPIUtils.repaint( scrollbar );
scrollbar.repaint();
}
}

View File

@@ -1,118 +0,0 @@
/*
* Copyright 2023 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Insets;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Border for {@link javax.swing.JScrollPane}.
*
* @uiDefault ScrollPane.arc int
* @uiDefault ScrollPane.List.arc int
* @uiDefault ScrollPane.Table.arc int
* @uiDefault ScrollPane.TextComponent.arc int
* @uiDefault ScrollPane.Tree.arc int
* @author Karl Tauber
* @since 3.3
*/
public class FlatScrollPaneBorder
extends FlatBorder
{
@Styleable protected int arc = UIManager.getInt( "ScrollPane.arc" );
private boolean isArcStyled;
private final int listArc = FlatUIUtils.getUIInt( "ScrollPane.List.arc", -1 );
private final int tableArc = FlatUIUtils.getUIInt( "ScrollPane.Table.arc", -1 );
private final int textComponentArc = FlatUIUtils.getUIInt( "ScrollPane.TextComponent.arc", -1 );
private final int treeArc = FlatUIUtils.getUIInt( "ScrollPane.Tree.arc", -1 );
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = super.applyStyleProperty( key, value );
if( "arc".equals( key ) )
isArcStyled = true;
return oldValue;
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
insets = super.getBorderInsets( c, insets );
// if view is rounded, increase left and right insets to avoid that the viewport
// is painted over the rounded border on the corners
int padding = getLeftRightPadding( c );
if( padding > 0 ) {
insets.left += padding;
insets.right += padding;
}
return insets;
}
@Override
protected int getArc( Component c ) {
if( isCellEditor( c ) )
return 0;
if( isArcStyled )
return arc;
if( c instanceof JScrollPane ) {
Component view = FlatScrollPaneUI.getView( (JScrollPane) c );
if( listArc >= 0 && view instanceof JList )
return listArc;
if( tableArc >= 0 && view instanceof JTable )
return tableArc;
if( textComponentArc >= 0&& view instanceof JTextComponent )
return textComponentArc;
if( treeArc >= 0 && view instanceof JTree )
return treeArc;
}
return arc;
}
/**
* Returns the scaled left/right padding used when arc is larger than zero.
* <p>
* This is the distance from the inside of the left border to the left side of the view component.
* On the right side, this is the distance between the right side of the view component and
* the vertical scrollbar. Or the inside of the right border if the scrollbar is hidden.
*/
public int getLeftRightPadding( Component c ) {
// Subtract lineWidth from radius because radius is given for the outside
// of the painted line, but insets from super already include lineWidth.
// Reduce padding by 10% to make padding slightly smaller because it is not recognizable
// when the view is minimally painted over the beginning of the border curve.
int arc = getArc( c );
return (arc > 0)
? Math.max( Math.round( UIScale.scale( ((arc / 2f) - getLineWidth( c )) * 0.9f ) ), 0 )
: 0;
}
}

View File

@@ -19,11 +19,10 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent;
@@ -34,6 +33,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
@@ -44,20 +45,18 @@ import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.ScrollPaneLayout;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
@@ -104,13 +103,7 @@ public class FlatScrollPaneUI
super.installUI( c );
int focusWidth = UIManager.getInt( "Component.focusWidth" );
int arc = UIManager.getInt( "ScrollPane.arc" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 && arc == 0 );
// install layout manager
LayoutManager layout = c.getLayout();
if( layout != null && layout.getClass() == ScrollPaneLayout.UIResource.class )
c.setLayout( createScrollPaneLayout() );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
installStyle();
@@ -121,10 +114,6 @@ public class FlatScrollPaneUI
public void uninstallUI( JComponent c ) {
MigLayoutVisualPadding.uninstall( scrollpane );
// uninstall layout manager
if( c.getLayout() instanceof FlatScrollPaneLayout )
c.setLayout( new ScrollPaneLayout.UIResource() );
super.uninstallUI( c );
oldStyleValues = null;
@@ -147,30 +136,38 @@ public class FlatScrollPaneUI
handler = null;
}
/**
* @since 3.3
*/
protected FlatScrollPaneLayout createScrollPaneLayout() {
return new FlatScrollPaneLayout();
}
@Override
protected MouseWheelListener createMouseWheelListener() {
MouseWheelListener superListener = super.createMouseWheelListener();
return e -> {
if( isSmoothScrollingEnabled() &&
scrollpane.isWheelScrollingEnabled() &&
e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
e.getPreciseWheelRotation() != 0 &&
e.getPreciseWheelRotation() != e.getWheelRotation() )
scrollpane.isWheelScrollingEnabled() )
{
mouseWheelMovedSmooth( e );
if( e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
isPreciseWheelEvent( e ) )
{
// precise scrolling
mouseWheelMovedPrecise( e );
} else {
// smooth scrolling
JScrollBar scrollBar = findScrollBarToScroll( e );
if( scrollBar != null && scrollBar.getUI() instanceof FlatScrollBarUI ) {
FlatScrollBarUI ui = (FlatScrollBarUI) scrollBar.getUI();
ui.runAndSetValueAnimated( () -> {
superListener.mouseWheelMoved( e );
} );
} else
superListener.mouseWheelMoved( e );
}
} else
superListener.mouseWheelMoved( e );
};
}
protected boolean isSmoothScrollingEnabled() {
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( FlatSystemProperties.SMOOTH_SCROLLING, true ) )
return false;
Object smoothScrolling = scrollpane.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING );
if( smoothScrolling instanceof Boolean )
return (Boolean) smoothScrolling;
@@ -181,19 +178,40 @@ public class FlatScrollPaneUI
return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
}
private void mouseWheelMovedSmooth( MouseWheelEvent e ) {
private long lastPreciseWheelWhen;
private boolean isPreciseWheelEvent( MouseWheelEvent e ) {
double preciseWheelRotation = e.getPreciseWheelRotation();
if( preciseWheelRotation != 0 && preciseWheelRotation != e.getWheelRotation() ) {
// precise wheel event
lastPreciseWheelWhen = e.getWhen();
return true;
}
// If a non-precise wheel event occurs shortly after a precise wheel event,
// then it is probably still a precise wheel but the precise value
// is by chance an integer value (e.g. 1.0 or 2.0).
// Not handling this special case, would start an animation for smooth scrolling,
// which would be interrupted soon when the next precise wheel event occurs.
// This would result in jittery scrolling. E.g. on a MacBook using Trackpad or Magic Mouse.
if( e.getWhen() - lastPreciseWheelWhen < 1000 )
return true;
// non-precise wheel event
lastPreciseWheelWhen = 0;
return false;
}
private void mouseWheelMovedPrecise( MouseWheelEvent e ) {
// return if there is no viewport
JViewport viewport = scrollpane.getViewport();
if( viewport == null )
return;
// find scrollbar to scroll
JScrollBar scrollbar = scrollpane.getVerticalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() || e.isShiftDown() ) {
scrollbar = scrollpane.getHorizontalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() )
return;
}
JScrollBar scrollbar = findScrollBarToScroll( e );
if( scrollbar == null )
return;
// consume event
e.consume();
@@ -210,7 +228,7 @@ public class FlatScrollPaneUI
// Use (0, 0) view position to obtain a constant unit increment of first item.
// Unit increment may be different for each item.
Rectangle visibleRect = new Rectangle( viewport.getExtentSize() );
Rectangle visibleRect = new Rectangle( viewport.getViewSize() );
unitIncrement = scrollable.getScrollableUnitIncrement( visibleRect, orientation, 1 );
if( unitIncrement > 0 ) {
@@ -286,6 +304,16 @@ public class FlatScrollPaneUI
*/
}
private JScrollBar findScrollBarToScroll( MouseWheelEvent e ) {
JScrollBar scrollBar = scrollpane.getVerticalScrollBar();
if( scrollBar == null || !scrollBar.isVisible() || e.isShiftDown() ) {
scrollBar = scrollpane.getHorizontalScrollBar();
if( scrollBar == null || !scrollBar.isVisible() )
return null;
}
return scrollBar;
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -298,11 +326,11 @@ public class FlatScrollPaneUI
JScrollBar hsb = scrollpane.getHorizontalScrollBar();
if( vsb != null ) {
vsb.revalidate();
HiDPIUtils.repaint( vsb );
vsb.repaint();
}
if( hsb != null ) {
hsb.revalidate();
HiDPIUtils.repaint( hsb );
hsb.repaint();
}
break;
@@ -314,7 +342,8 @@ public class FlatScrollPaneUI
Object corner = e.getNewValue();
if( corner instanceof JButton &&
((JButton)corner).getBorder() instanceof FlatButtonBorder &&
getView( scrollpane ) instanceof JTable )
scrollpane.getViewport() != null &&
scrollpane.getViewport().getView() instanceof JTable )
{
((JButton)corner).setBorder( BorderFactory.createEmptyBorder() );
((JButton)corner).setFocusable( false );
@@ -322,26 +351,14 @@ public class FlatScrollPaneUI
break;
case FlatClientProperties.OUTLINE:
HiDPIUtils.repaint( scrollpane );
scrollpane.repaint();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollpane.revalidate();
HiDPIUtils.repaint( scrollpane );
break;
case "border":
Object newBorder = e.getNewValue();
if( newBorder != null && newBorder == UIManager.getBorder( "Table.scrollPaneBorder" ) ) {
// JTable.configureEnclosingScrollPaneUI() replaces the scrollpane border
// with another one --> re-apply style on new border
borderShared = null;
installStyle();
scrollpane.revalidate();
HiDPIUtils.repaint( scrollpane );
}
scrollpane.repaint();
break;
}
};
@@ -369,10 +386,9 @@ public class FlatScrollPaneUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "focusWidth" ) || key.equals( "arc" ) ) {
if( key.equals( "focusWidth" ) ) {
int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" );
int arc = (value instanceof Integer) ? (int) value : UIManager.getInt( "ScrollPane.arc" );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 && arc == 0 );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 );
}
if( borderShared == null )
@@ -438,46 +454,13 @@ public class FlatScrollPaneUI
c.getHeight() - insets.top - insets.bottom );
}
// if view is rounded, paint rounded background with view background color
// to ensure that free areas at left and right have same color as view
Component view;
float arc = getBorderArc( scrollpane );
if( arc > 0 && (view = getView( scrollpane )) != null ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
g.setColor( view.getBackground() );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
paint( g, c );
}
@Override
public void paint( Graphics g, JComponent c ) {
Border viewportBorder = scrollpane.getViewportBorder();
if( viewportBorder != null ) {
Rectangle r = scrollpane.getViewportBorderBounds();
int padding = getBorderLeftRightPadding( scrollpane );
JScrollBar vsb = scrollpane.getVerticalScrollBar();
if( padding > 0 &&
vsb != null && vsb.isVisible() &&
scrollpane.getLayout() instanceof FlatScrollPaneLayout &&
((FlatScrollPaneLayout)scrollpane.getLayout()).canIncreaseViewportWidth( scrollpane ) )
{
boolean ltr = scrollpane.getComponentOrientation().isLeftToRight();
int extraWidth = Math.min( padding, vsb.getWidth() );
viewportBorder.paintBorder( scrollpane, g, r.x - (ltr ? 0 : extraWidth), r.y, r.width + extraWidth, r.height );
} else
viewportBorder.paintBorder( scrollpane, g, r.x, r.y, r.width, r.height );
}
}
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) {
Component view = getView( scrollPane );
JViewport viewport = scrollPane.getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view == null )
return false;
@@ -497,23 +480,117 @@ public class FlatScrollPaneUI
return false;
}
static Component getView( JScrollPane scrollPane ) {
JViewport viewport = scrollPane.getViewport();
return (viewport != null) ? viewport.getView() : null;
@Override
protected void syncScrollPaneWithViewport() {
// if the viewport has been scrolled by using JComponent.scrollRectToVisible()
// (e.g. by moving selection), then it is necessary to update the scroll bar values
if( isSmoothScrollingEnabled() ) {
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, false, () -> {
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, false, () -> {
super.syncScrollPaneWithViewport();
} );
} );
} else
super.syncScrollPaneWithViewport();
}
private static float getBorderArc( JScrollPane scrollPane ) {
Border border = scrollPane.getBorder();
return (border instanceof FlatScrollPaneBorder)
? UIScale.scale( (float) ((FlatScrollPaneBorder)border).getArc( scrollPane ) )
: 0;
/**
* Runs the given runnable, if smooth scrolling is enabled, with disabled
* viewport blitting mode and with scroll bar value set to "target" value.
* This is necessary when calculating new view position during animation.
* Otherwise calculation would use wrong view position and (repeating) scrolling
* would be much slower than without smooth scrolling.
*/
private void runWithScrollBarsTargetValues( boolean blittingOnly, Runnable r ) {
if( isSmoothScrollingEnabled() ) {
runWithoutBlitting( scrollpane, () -> {
if( blittingOnly )
r.run();
else {
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, true, () -> {
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, true, r );
} );
}
} );
} else
r.run();
}
private static int getBorderLeftRightPadding( JScrollPane scrollPane ) {
Border border = scrollPane.getBorder();
return (border instanceof FlatScrollPaneBorder)
? ((FlatScrollPaneBorder)border).getLeftRightPadding( scrollPane )
: 0;
private void runAndSyncScrollBarValueAnimated( JScrollBar sb, int i, boolean useTargetValue, Runnable r ) {
if( inRunAndSyncValueAnimated[i] || sb == null || !(sb.getUI() instanceof FlatScrollBarUI) ) {
r.run();
return;
}
inRunAndSyncValueAnimated[i] = true;
int oldValue = sb.getValue();
int oldVisibleAmount = sb.getVisibleAmount();
int oldMinimum = sb.getMinimum();
int oldMaximum = sb.getMaximum();
FlatScrollBarUI ui = (FlatScrollBarUI) sb.getUI();
if( useTargetValue && ui.getTargetValue() != Integer.MIN_VALUE )
sb.setValue( ui.getTargetValue() );
r.run();
int newValue = sb.getValue();
if( newValue != oldValue &&
sb.getVisibleAmount() == oldVisibleAmount &&
sb.getMinimum() == oldMinimum &&
sb.getMaximum() == oldMaximum &&
sb.getUI() instanceof FlatScrollBarUI )
{
ui.setValueAnimated( oldValue, newValue );
}
inRunAndSyncValueAnimated[i] = false;
}
private final boolean[] inRunAndSyncValueAnimated = new boolean[2];
/**
* Runs the given runnable with disabled viewport blitting mode.
* If blitting mode is enabled, the viewport immediately repaints parts of the
* view if the view position is changed via JViewport.setViewPosition().
* This causes scrolling artifacts if smooth scrolling is enabled and the view position
* is "temporary" changed to its new target position, changed back to its old position
* and again moved animated to the target position.
*/
static void runWithoutBlitting( Container scrollPane, Runnable r ) {
// prevent the viewport to immediately repaint using blitting
JViewport viewport = (scrollPane instanceof JScrollPane) ? ((JScrollPane)scrollPane).getViewport() : null;
boolean isBlitScrollMode = (viewport != null) ? viewport.getScrollMode() == JViewport.BLIT_SCROLL_MODE : false;
if( isBlitScrollMode )
viewport.setScrollMode( JViewport.SIMPLE_SCROLL_MODE );
try {
r.run();
} finally {
if( isBlitScrollMode )
viewport.setScrollMode( JViewport.BLIT_SCROLL_MODE );
}
}
public static void installSmoothScrollingDelegateActions( JComponent c, boolean blittingOnly, String... actionKeys ) {
// get shared action map, used for all components of same type
ActionMap map = SwingUtilities.getUIActionMap( c );
if( map == null )
return;
// install actions, but only if not already installed
for( String actionKey : actionKeys )
installSmoothScrollingDelegateAction( map, blittingOnly, actionKey );
}
private static void installSmoothScrollingDelegateAction( ActionMap map, boolean blittingOnly, String actionKey ) {
Action oldAction = map.get( actionKey );
if( oldAction == null || oldAction instanceof SmoothScrollingDelegateAction )
return; // not found or already installed
map.put( actionKey, new SmoothScrollingDelegateAction( oldAction, blittingOnly ) );
}
//---- class Handler ------------------------------------------------------
@@ -538,71 +615,43 @@ public class FlatScrollPaneUI
@Override
public void focusGained( FocusEvent e ) {
// necessary to update focus border
if( scrollpane.getBorder() instanceof FlatBorder )
HiDPIUtils.repaint( scrollpane );
scrollpane.repaint();
}
@Override
public void focusLost( FocusEvent e ) {
// necessary to update focus border
if( scrollpane.getBorder() instanceof FlatBorder )
HiDPIUtils.repaint( scrollpane );
scrollpane.repaint();
}
}
//---- class FlatScrollPaneLayout -----------------------------------------
//---- class SmoothScrollingDelegateAction --------------------------------
/**
* @since 3.3
* Used to run component actions with disabled blitting mode and
* with scroll bar target values.
*/
protected static class FlatScrollPaneLayout
extends ScrollPaneLayout.UIResource
private static class SmoothScrollingDelegateAction
extends FlatUIAction
{
private final boolean blittingOnly;
private SmoothScrollingDelegateAction( Action delegate, boolean blittingOnly ) {
super( delegate );
this.blittingOnly = blittingOnly;
}
@Override
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
JScrollPane scrollPane = (JScrollPane) parent;
int padding = getBorderLeftRightPadding( scrollPane );
if( padding > 0 && vsb != null && vsb.isVisible() ) {
// move vertical scrollbar to trailing edge
Insets insets = scrollPane.getInsets();
Rectangle r = vsb.getBounds();
int y = Math.max( r.y, insets.top + padding );
int y2 = Math.min( r.y + r.height, scrollPane.getHeight() - insets.bottom - padding );
boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y );
// increase width of viewport, column header and horizontal scrollbar
if( canIncreaseViewportWidth( scrollPane ) ) {
int extraWidth = Math.min( padding, vsb.getWidth() );
resizeViewport( viewport, extraWidth, ltr );
resizeViewport( colHead, extraWidth, ltr );
resizeViewport( hsb, extraWidth, ltr );
}
}
}
boolean canIncreaseViewportWidth( JScrollPane scrollPane ) {
return scrollPane.getComponentOrientation().isLeftToRight()
? !isCornerVisible( upperRight ) && !isCornerVisible( lowerRight )
: !isCornerVisible( upperLeft ) && !isCornerVisible( lowerLeft );
}
private static boolean isCornerVisible( Component corner ) {
return corner != null &&
corner.getWidth() > 0 &&
corner.getHeight() > 0 &&
corner.isVisible();
}
private static void resizeViewport( Component c, int extraWidth, boolean ltr ) {
if( c == null )
return;
Rectangle vr = c.getBounds();
c.setBounds( vr.x - (ltr ? 0 : extraWidth), vr.y, vr.width + extraWidth, vr.height );
public void actionPerformed( ActionEvent e ) {
Object source = e.getSource();
JScrollPane scrollPane = (source instanceof Component)
? (JScrollPane) SwingUtilities.getAncestorOfClass( JScrollPane.class, (Component) source )
: null;
if( scrollPane != null && scrollPane.getUI() instanceof FlatScrollPaneUI ) {
((FlatScrollPaneUI)scrollPane.getUI()).runWithScrollBarsTargetValues( blittingOnly,
() -> delegate.actionPerformed( e ) );
} else
delegate.actionPerformed( e );
}
}
}

View File

@@ -32,7 +32,6 @@ import javax.swing.plaf.basic.BasicSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -135,7 +134,7 @@ public class FlatSeparatorUI
} else
installStyle( s );
s.revalidate();
HiDPIUtils.repaint( s );
s.repaint();
break;
}
}

View File

@@ -25,8 +25,6 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
@@ -193,23 +191,6 @@ public class FlatSliderUI
return new FlatTrackListener();
}
@Override
protected FocusListener createFocusListener( JSlider slider ) {
return new BasicSliderUI.FocusHandler() {
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
HiDPIUtils.repaint( slider );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
HiDPIUtils.repaint( slider );
}
};
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle,
@@ -227,13 +208,7 @@ 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 */
@@ -447,7 +422,7 @@ debug*/
Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth )
{
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
if( systemScaleFactor != (int) systemScaleFactor ) {
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -604,15 +579,15 @@ debug*/
@Override
public void setThumbLocation( int x, int y ) {
// set new thumb location and compute union of old and new thumb bounds
Rectangle r = new Rectangle( thumbRect );
thumbRect.setLocation( x, y );
SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r );
if( !isRoundThumb() ) {
// the needle of the directional thumb is painted outside of thumbRect
// --> must increase repaint rectangle
// set new thumb location and compute union of old and new thumb bounds
Rectangle r = new Rectangle( thumbRect );
thumbRect.setLocation( x, y );
SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r );
// increase union rectangle for repaint
int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f );
if( slider.getOrientation() == JSlider.HORIZONTAL )
@@ -622,9 +597,10 @@ debug*/
if( !slider.getComponentOrientation().isLeftToRight() )
r.x -= extra;
}
}
HiDPIUtils.repaint( slider, r );
slider.repaint( r );
} else
super.setThumbLocation( x, y );
}
//---- class FlatTrackListener --------------------------------------------
@@ -712,21 +688,21 @@ debug*/
!UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) )
{
calculateThumbLocation();
HiDPIUtils.repaint( slider );
slider.repaint();
}
}
protected void setThumbHover( boolean hover ) {
if( hover != thumbHover ) {
thumbHover = hover;
HiDPIUtils.repaint( slider, thumbRect );
slider.repaint( thumbRect );
}
}
protected void setThumbPressed( boolean pressed ) {
if( pressed != thumbPressed ) {
thumbPressed = pressed;
HiDPIUtils.repaint( slider, thumbRect );
slider.repaint( thumbRect );
}
}

View File

@@ -47,7 +47,6 @@ import javax.swing.plaf.basic.BasicSpinnerUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -140,6 +139,8 @@ public class FlatSpinnerUI
buttonHoverArrowColor = UIManager.getColor( "Spinner.buttonHoverArrowColor" );
buttonPressedArrowColor = UIManager.getColor( "Spinner.buttonPressedArrowColor" );
padding = UIManager.getInsets( "Spinner.padding" );
MigLayoutVisualPadding.install( spinner );
}
@Override
@@ -160,6 +161,8 @@ public class FlatSpinnerUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( spinner );
}
@Override
@@ -169,8 +172,6 @@ public class FlatSpinnerUI
addEditorFocusListener( spinner.getEditor() );
spinner.addFocusListener( getHandler() );
spinner.addPropertyChangeListener( getHandler() );
MigLayoutVisualPadding.install( spinner );
}
@Override
@@ -182,8 +183,6 @@ public class FlatSpinnerUI
spinner.removePropertyChangeListener( getHandler() );
handler = null;
MigLayoutVisualPadding.uninstall( spinner );
}
private Handler getHandler() {
@@ -587,7 +586,7 @@ public class FlatSpinnerUI
@Override
public void focusGained( FocusEvent e ) {
// necessary to update focus border
HiDPIUtils.repaint( spinner );
spinner.repaint();
// if spinner gained focus, transfer it to the editor text field
if( e.getComponent() == spinner ) {
@@ -600,7 +599,7 @@ public class FlatSpinnerUI
@Override
public void focusLost( FocusEvent e ) {
// necessary to update focus border
HiDPIUtils.repaint( spinner );
spinner.repaint();
}
//---- interface PropertyChangeListener ----
@@ -615,7 +614,7 @@ public class FlatSpinnerUI
case FlatClientProperties.COMPONENT_ROUND_RECT:
case FlatClientProperties.OUTLINE:
HiDPIUtils.repaint( spinner );
spinner.repaint();
break;
case FlatClientProperties.MINIMUM_WIDTH:
@@ -626,7 +625,7 @@ public class FlatSpinnerUI
case FlatClientProperties.STYLE_CLASS:
installStyle();
spinner.revalidate();
HiDPIUtils.repaint( spinner );
spinner.repaint();
break;
}
}

View File

@@ -16,9 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Graphics;
@@ -69,8 +67,6 @@ import com.formdev.flatlaf.util.UIScale;
* <!-- FlatSplitPaneUI -->
*
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault SplitPaneDivider.hoverColor Color optional
* @uiDefault SplitPaneDivider.pressedColor Color optional
* @uiDefault SplitPaneDivider.oneTouchArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchHoverArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchPressedArrowColor Color
@@ -84,10 +80,9 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatSplitPaneUI
extends BasicSplitPaneUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
implements StyleableUI
{
@Styleable protected String arrowType;
/** @since 3.3 */ @Styleable protected Color draggingColor;
@Styleable protected Color oneTouchArrowColor;
@Styleable protected Color oneTouchHoverArrowColor;
@Styleable protected Color oneTouchPressedArrowColor;
@@ -109,8 +104,6 @@ public class FlatSplitPaneUI
protected void installDefaults() {
arrowType = UIManager.getString( "Component.arrowType" );
draggingColor = UIManager.getColor( "SplitPaneDivider.draggingColor" );
// get one-touch colors before invoking super.installDefaults() because they are
// used in there on LaF switching
oneTouchArrowColor = UIManager.getColor( "SplitPaneDivider.oneTouchArrowColor" );
@@ -124,8 +117,6 @@ public class FlatSplitPaneUI
protected void uninstallDefaults() {
super.uninstallDefaults();
draggingColor = null;
oneTouchArrowColor = null;
oneTouchHoverArrowColor = null;
oneTouchPressedArrowColor = null;
@@ -192,58 +183,12 @@ public class FlatSplitPaneUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected Component createDefaultNonContinuousLayoutDivider() {
// only used for non-continuous layout if left or right component is heavy weight
return new Canvas() {
@Override
public void paint( Graphics g ) {
if( !isContinuousLayout() && getLastDragLocation() != -1 )
paintDragDivider( g, 0 );
}
};
}
@Override
public void finishedPaintingChildren( JSplitPane sp, Graphics g ) {
if( sp == splitPane && getLastDragLocation() != -1 && !isContinuousLayout() && !draggingHW )
paintDragDivider( g, getLastDragLocation() );
}
private void paintDragDivider( Graphics g, int dividerLocation ) {
// divider bounds
boolean horizontal = (getOrientation() == JSplitPane.HORIZONTAL_SPLIT);
int x = horizontal ? dividerLocation : 0;
int y = !horizontal ? dividerLocation : 0;
int width = horizontal ? dividerSize : splitPane.getWidth();
int height = !horizontal ? dividerSize : splitPane.getHeight();
// paint background
g.setColor( FlatUIUtils.deriveColor( draggingColor, splitPane.getBackground() ) );
g.fillRect( x, y, width, height );
// paint divider style (e.g. grip)
if( divider instanceof FlatSplitPaneDivider )
((FlatSplitPaneDivider)divider).paintStyle( g, x, y, width, height );
}
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicSplitPaneDivider adds some mouse listeners for dragging divider
return null; // check children
}
//---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider
extends BasicSplitPaneDivider
{
@Styleable protected String style = UIManager.getString( "SplitPaneDivider.style" );
/** @since 3.3 */ @Styleable protected Color hoverColor = UIManager.getColor( "SplitPaneDivider.hoverColor" );
/** @since 3.3 */ @Styleable protected Color pressedColor = UIManager.getColor( "SplitPaneDivider.pressedColor" );
@Styleable protected Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" );
@Styleable protected int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 );
@Styleable protected int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 );
@@ -301,40 +246,20 @@ public class FlatSplitPaneUI
// necessary to show/hide one-touch buttons on expand/collapse
doLayout();
break;
case FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE:
revalidate();
break;
}
}
@Override
public void paint( Graphics g ) {
// paint hover or pressed background
Color hoverOrPressedColor = (isContinuousLayout() && dragger != null)
? pressedColor
: (isMouseOver() && dragger == null
? hoverColor
: null);
if( hoverOrPressedColor != null ) {
g.setColor( FlatUIUtils.deriveColor( hoverOrPressedColor, splitPane.getBackground() ) );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
super.paint( g );
paintStyle( g, 0, 0, getWidth(), getHeight() );
}
/** @since 3.3 */
protected void paintStyle( Graphics g, int x, int y, int width, int height ) {
if( "plain".equals( style ) )
return;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( gripColor );
paintGrip( g, x, y, width, height );
paintGrip( g, 0, 0, getWidth(), getHeight() );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
@@ -361,29 +286,6 @@ public class FlatSplitPaneUI
: location == (splitPane.getWidth() - getWidth() - insets.right);
}
@Override
protected void setMouseOver( boolean mouseOver ) {
super.setMouseOver( mouseOver );
repaintIfNecessary();
}
@Override
protected void prepareForDragging() {
super.prepareForDragging();
repaintIfNecessary();
}
@Override
protected void finishDraggingTo( int location ) {
super.finishDraggingTo( location );
repaintIfNecessary();
}
private void repaintIfNecessary() {
if( hoverColor != null || pressedColor != null )
repaint();
}
//---- class FlatOneTouchButton ---------------------------------------
protected class FlatOneTouchButton

View File

@@ -40,8 +40,8 @@ import javax.swing.UIManager;
import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Support for styling components in CSS syntax.
@@ -51,9 +51,6 @@ import com.formdev.flatlaf.util.StringUtils;
*/
public class FlatStylingSupport
{
//---- annotations --------------------------------------------------------
/**
* Indicates that a field is intended to be used by FlatLaf styling support.
* <p>
@@ -101,56 +98,17 @@ 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;
}
/**
* 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 */
public interface StyleableBorder {
Object applyStyleProperty( String key, Object value );
Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException;
/** @since 2.5 */ Object getStyleableValue( String key ) throws IllegalArgumentException;
}
/** @since 2.5 */
@@ -159,8 +117,6 @@ public class FlatStylingSupport
}
//---- methods ------------------------------------------------------------
/**
* Returns the style specified in client property {@link FlatClientProperties#STYLE}.
*/
@@ -368,24 +324,22 @@ public class FlatStylingSupport
return null;
Map<String, Object> oldValues = new HashMap<>();
outer:
for( Map.Entry<String, Object> e : style.entrySet() ) {
String key = e.getKey();
Object newValue = e.getValue();
// handle key prefix
while( key.startsWith( "[" ) ) {
int closeIndex = key.indexOf( ']' );
if( closeIndex < 0 )
continue outer;
String prefix = key.substring( 0, closeIndex + 1 );
String lightOrDarkPrefix = FlatLaf.getUIKeyLightOrDarkPrefix( FlatLaf.isLafDark() );
if( !lightOrDarkPrefix.equals( prefix ) && !FlatLaf.getUIKeyPlatformPrefixes().contains( prefix ) )
continue outer;
// prefix is known and enabled --> remove prefix
key = key.substring( closeIndex + 1 );
if( key.startsWith( "[" ) ) {
if( (SystemInfo.isWindows && key.startsWith( "[win]" )) ||
(SystemInfo.isMacOS && key.startsWith( "[mac]" )) ||
(SystemInfo.isLinux && key.startsWith( "[linux]" )) ||
(key.startsWith( "[light]" ) && !FlatLaf.isLafDark()) ||
(key.startsWith( "[dark]" ) && FlatLaf.isLafDark()) )
{
// prefix is known and enabled --> remove prefix
key = key.substring( key.indexOf( ']' ) + 1 );
} else
continue;
}
Object oldValue = applyProperty.apply( key, newValue );
@@ -719,7 +673,7 @@ public class FlatStylingSupport
} catch( UnknownStyleException ex ) {
// apply to border
Border border = c.getBorder();
if( border instanceof StyleableObject ) {
if( border instanceof StyleableBorder ) {
if( borderShared.get() ) {
border = cloneBorder( border );
c.setBorder( border );
@@ -727,7 +681,7 @@ public class FlatStylingSupport
}
try {
return ((StyleableObject)border).applyStyleProperty( key, value );
return ((StyleableBorder)border).applyStyleProperty( key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
@@ -755,7 +709,7 @@ public class FlatStylingSupport
case FlatClientProperties.STYLE_CLASS:
installStyle.run();
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
break;
}
};
@@ -877,8 +831,8 @@ public class FlatStylingSupport
}
public static void collectStyleableInfos( Border border, Map<String, Class<?>> infos ) {
if( border instanceof StyleableObject )
infos.putAll( ((StyleableObject)border).getStyleableInfos() );
if( border instanceof StyleableBorder )
infos.putAll( ((StyleableBorder)border).getStyleableInfos() );
}
public static void putAllPrefixKey( Map<String, Class<?>> infos, String keyPrefix, Map<String, Class<?>> infos2 ) {
@@ -926,8 +880,8 @@ public class FlatStylingSupport
}
public static Object getAnnotatedStyleableValue( Object obj, Border border, String key ) {
if( border instanceof StyleableObject ) {
Object value = ((StyleableObject)border).getStyleableValue( key );
if( border instanceof StyleableBorder ) {
Object value = ((StyleableBorder)border).getStyleableValue( key );
if( value != null )
return value;
}

View File

@@ -65,28 +65,8 @@ public class FlatTableCellBorder
return super.getLineColor();
}
@Override
public int getArc() {
if( c != null ) {
Integer selectionArc = getStyleFromTableUI( c, ui -> ui.selectionArc );
if( selectionArc != null )
return selectionArc;
}
return super.getArc();
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c != null ) {
Insets selectionInsets = getStyleFromTableUI( c, ui -> ui.selectionInsets );
if( selectionInsets != null ) {
x += selectionInsets.left;
y += selectionInsets.top;
width -= selectionInsets.left + selectionInsets.right;
height -= selectionInsets.top + selectionInsets.bottom;
}
}
this.c = c;
super.paintBorder( c, g, x, y, width, height );
this.c = null;

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
@@ -29,15 +28,16 @@ import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
@@ -45,7 +45,6 @@ import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -115,11 +114,6 @@ public class FlatTableHeaderUI
public void installUI( JComponent c ) {
super.installUI( c );
// replace cell renderer pane
header.remove( rendererPane );
rendererPane = new FlatTableHeaderCellRendererPane();
header.add( rendererPane );
installStyle();
}
@@ -235,8 +229,8 @@ public class FlatTableHeaderUI
@Override
protected void rolloverColumnUpdated( int oldColumn, int newColumn ) {
HiDPIUtils.repaint( header, header.getHeaderRect( oldColumn ) );
HiDPIUtils.repaint( header, header.getHeaderRect( newColumn ) );
header.repaint( header.getHeaderRect( oldColumn ) );
header.repaint( header.getHeaderRect( newColumn ) );
}
@Override
@@ -271,8 +265,16 @@ public class FlatTableHeaderUI
}
}
// temporary use own default renderer
FlatTableCellHeaderRenderer tempRenderer = new FlatTableCellHeaderRenderer( header.getDefaultRenderer() );
header.setDefaultRenderer( tempRenderer );
// paint header
super.paint( g, c );
// restore default renderer
tempRenderer.reset();
header.setDefaultRenderer( tempRenderer.delegate );
}
private boolean isSystemDefaultRenderer( Object headerRenderer ) {
@@ -330,129 +332,119 @@ public class FlatTableHeaderUI
return false;
}
//---- class FlatTableHeaderCellRendererPane ------------------------------
//---- class FlatTableCellHeaderRenderer ----------------------------------
/**
* Cell renderer pane that is used to paint hover and pressed background/foreground
* and to paint sort arrows at top, bottom or left position.
* A delegating header renderer that is only used to paint hover and pressed
* background/foreground and to paint sort arrows at top, bottom or left position.
*/
private class FlatTableHeaderCellRendererPane
extends CellRendererPane
private class FlatTableCellHeaderRenderer
implements TableCellRenderer, Border, UIResource
{
private final Icon ascendingSortIcon;
private final Icon descendingSortIcon;
private final TableCellRenderer delegate;
public FlatTableHeaderCellRendererPane() {
ascendingSortIcon = UIManager.getIcon( "Table.ascendingSortIcon" );
descendingSortIcon = UIManager.getIcon( "Table.descendingSortIcon" );
private JLabel l;
private Color oldBackground;
private Color oldForeground;
private Boolean oldOpaque;
private int oldHorizontalTextPosition = -1;
private Border origBorder;
private Icon sortIcon;
FlatTableCellHeaderRenderer( TableCellRenderer delegate ) {
this.delegate = delegate;
}
@Override
public void paintComponent( Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate ) {
if( !(c instanceof JLabel) ) {
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );
return;
}
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column )
{
Component c = delegate.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
if( !(c instanceof JLabel) )
return c;
JLabel l = (JLabel) c;
Color oldBackground = null;
Color oldForeground = null;
boolean oldOpaque = false;
Icon oldIcon = null;
int oldHorizontalTextPosition = -1;
l = (JLabel) c;
// hover and pressed background/foreground
TableColumn draggedColumn = header.getDraggedColumn();
Color background = null;
Color foreground = null;
if( draggedColumn != null &&
header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() )
== getColumn( x - header.getDraggedDistance(), w ) )
{
if( draggedColumn != null && header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() ) == column ) {
background = pressedBackground;
foreground = pressedForeground;
} else if( getRolloverColumn() >= 0 && getRolloverColumn() == getColumn( x, w ) ) {
} else if( getRolloverColumn() == column ) {
background = hoverBackground;
foreground = hoverForeground;
}
if( background != null ) {
oldBackground = l.getBackground();
oldOpaque = l.isOpaque();
if( oldBackground == null )
oldBackground = l.getBackground();
if( oldOpaque == null )
oldOpaque = l.isOpaque();
l.setBackground( FlatUIUtils.deriveColor( background, header.getBackground() ) );
l.setOpaque( true );
}
if( foreground != null ) {
oldForeground = l.getForeground();
if( oldForeground == null )
oldForeground = l.getForeground();
l.setForeground( FlatUIUtils.deriveColor( foreground, header.getForeground() ) );
}
// sort icon position
Icon icon = l.getIcon();
boolean isSortIcon = (icon != null && (icon == ascendingSortIcon || icon == descendingSortIcon));
if( isSortIcon ) {
if( sortIconPosition == SwingConstants.LEFT ) {
// left
// sort icon
if( sortIconPosition == SwingConstants.LEFT ) {
// left
if( oldHorizontalTextPosition < 0 )
oldHorizontalTextPosition = l.getHorizontalTextPosition();
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom
oldIcon = icon;
l.setIcon( null );
}
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom
sortIcon = l.getIcon();
origBorder = l.getBorder();
l.setIcon( null );
l.setBorder( this );
}
// paint renderer component
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );
return l;
}
// paint top or bottom sort icon
if( isSortIcon && (sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM) ) {
int xi = x + ((w - icon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - icon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
icon.paintIcon( c, g, xi, yi );
}
void reset() {
if( l == null )
return;
// restore modified renderer component properties
if( background != null ) {
if( oldBackground != null )
l.setBackground( oldBackground );
l.setOpaque( oldOpaque );
}
if( foreground != null )
if( oldForeground != null )
l.setForeground( oldForeground );
if( oldIcon != null )
l.setIcon( oldIcon );
if( oldOpaque != null )
l.setOpaque( oldOpaque );
if( oldHorizontalTextPosition >= 0 )
l.setHorizontalTextPosition( oldHorizontalTextPosition );
}
/**
* Get column index for given coordinates.
*/
private int getColumn( int x, int width ) {
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
boolean ltr = header.getComponentOrientation().isLeftToRight();
int cx = ltr ? 0 : getWidthInRightToLef();
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( origBorder != null )
origBorder.paintBorder( c, g, x, y, width, height );
for( int i = 0; i < columnCount; i++ ) {
int cw = columnModel.getColumn( i ).getWidth();
if( x == cx - (ltr ? 0 : cw) && width == cw )
return i;
cx += ltr ? cw : -cw;
if( sortIcon != null ) {
int xi = x + ((width - sortIcon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - sortIcon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
sortIcon.paintIcon( c, g, xi, yi );
}
return -1;
}
// similar to JTableHeader.getWidthInRightToLeft()
private int getWidthInRightToLef() {
JTable table = header.getTable();
return (table != null && table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF)
? table.getWidth()
: header.getWidth();
@Override
public Insets getBorderInsets( Component c ) {
return (origBorder != null) ? origBorder.getBorderInsets( c ) : new Insets( 0, 0, 0, 0 );
}
@Override
public boolean isBorderOpaque() {
return (origBorder != null) ? origBorder.isBorderOpaque() : false;
}
}

View File

@@ -17,57 +17,30 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
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;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -107,10 +80,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.intercellSpacing Dimension
* @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.selectionInsets Insets
* @uiDefault Table.selectionArc int
* @uiDefault Table.paintOutsideAlternateRows boolean
* @uiDefault Table.editorSelectAllOnStartEditing boolean
*
* <!-- FlatTableCellBorder -->
*
@@ -137,8 +107,6 @@ public class FlatTableUI
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
/** @since 3.5 */ @Styleable protected Insets selectionInsets;
/** @since 3.5 */ @Styleable protected int selectionArc;
// for FlatTableCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins;
@@ -148,12 +116,8 @@ public class FlatTableUI
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing;
private TableCellRenderer oldBooleanRenderer;
private PropertyChangeListener propertyChangeListener;
private ComponentListener outsideAlternateRowsListener;
private ListSelectionListener rowSelectionListener;
private TableColumnModelListener columnSelectionListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
@@ -180,8 +144,6 @@ public class FlatTableUI
selectionForeground = UIManager.getColor( "Table.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" );
selectionInsets = UIManager.getInsets( "Table.selectionInsets" );
selectionArc = UIManager.getInt( "Table.selectionArc" );
toggleSelectionColors();
@@ -190,31 +152,18 @@ public class FlatTableUI
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
if( !showHorizontalLines ) {
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
} );
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
}
if( !showVerticalLines ) {
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
} );
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
}
if( intercellSpacing != null ) {
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
} );
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
}
// install boolean renderer
oldBooleanRenderer = table.getDefaultRenderer( Boolean.class );
if( oldBooleanRenderer instanceof UIResource )
table.setDefaultRenderer( Boolean.class, new FlatBooleanRenderer() );
else
oldBooleanRenderer = null;
}
@Override
@@ -229,34 +178,14 @@ public class FlatTableUI
oldStyleValues = null;
// restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() ) {
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
table.setShowHorizontalLines( true );
} );
}
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() ) {
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
table.setShowVerticalLines( true );
} );
}
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() )
table.setShowVerticalLines( true );
// restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) ) {
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
table.setIntercellSpacing( oldIntercellSpacing );
} );
}
// uninstall boolean renderer
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
if( oldBooleanRenderer instanceof Component ) {
// because the old renderer component was not attached to any component hierarchy,
// its UI was not yet updated, and it is necessary to do it here
SwingUtilities.updateComponentTreeUI( (Component) oldBooleanRenderer );
}
table.setDefaultRenderer( Boolean.class, oldBooleanRenderer );
}
oldBooleanRenderer = null;
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) )
table.setIntercellSpacing( oldIntercellSpacing );
}
@Override
@@ -265,28 +194,6 @@ public class FlatTableUI
propertyChangeListener = e -> {
switch( e.getPropertyName() ) {
case "selectionModel":
if( rowSelectionListener != null ) {
Object oldModel = e.getOldValue();
Object newModel = e.getNewValue();
if( oldModel != null )
((ListSelectionModel)oldModel).removeListSelectionListener( rowSelectionListener );
if( newModel != null )
((ListSelectionModel)newModel).addListSelectionListener( rowSelectionListener );
}
break;
case "columnModel":
if( columnSelectionListener != null ) {
Object oldModel = e.getOldValue();
Object newModel = e.getNewValue();
if( oldModel != null )
((TableColumnModel)oldModel).removeColumnModelListener( columnSelectionListener );
if( newModel != null )
((TableColumnModel)newModel).addColumnModelListener( columnSelectionListener );
}
break;
case FlatClientProperties.COMPONENT_FOCUS_OWNER:
toggleSelectionColors();
break;
@@ -295,14 +202,11 @@ public class FlatTableUI
case FlatClientProperties.STYLE_CLASS:
installStyle();
table.revalidate();
HiDPIUtils.repaint( table );
table.repaint();
break;
}
};
table.addPropertyChangeListener( propertyChangeListener );
if( selectionArc > 0 )
installRepaintRoundedSelectionListeners();
}
@Override
@@ -311,19 +215,6 @@ public class FlatTableUI
table.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
if( outsideAlternateRowsListener != null ) {
table.removeComponentListener( outsideAlternateRowsListener );
outsideAlternateRowsListener = null;
}
if( rowSelectionListener != null ) {
table.getSelectionModel().removeListSelectionListener( rowSelectionListener );
rowSelectionListener = null;
}
if( columnSelectionListener != null ) {
table.getColumnModel().removeColumnModelListener( columnSelectionListener );
columnSelectionListener = null;
}
}
@Override
@@ -347,18 +238,6 @@ public class FlatTableUI
};
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
if( UIManager.getBoolean( "Table.editorSelectAllOnStartEditing" ) ) {
// get shared action map, used for all tables
ActionMap map = SwingUtilities.getUIActionMap( table );
if( map != null )
StartEditingAction.install( map, "startEditing" );
}
}
/** @since 2 */
protected void installStyle() {
try {
@@ -400,8 +279,6 @@ public class FlatTableUI
protected Object applyStyleProperty( String key, Object value ) {
if( "rowHeight".equals( key ) && value instanceof Integer )
value = UIScale.scale( (Integer) value );
else if( "selectionArc".equals( key ) && value instanceof Integer && (Integer) value > 0 )
installRepaintRoundedSelectionListeners();
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value );
}
@@ -464,7 +341,6 @@ public class FlatTableUI
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
double lineThickness = (1. / systemScaleFactor) * (int) systemScaleFactor;
double lineOffset = (1. - lineThickness) + 0.05; // adding 0.05 to fix line location in some cases
// Java 8 uses drawLine() to paint grid lines
// Java 9+ uses fillRect() to paint grid lines (except for dragged column)
@@ -507,11 +383,11 @@ public class FlatTableUI
// reduce line thickness to avoid unstable painted line thickness
if( lineThickness != 1 ) {
if( horizontalLines && height == 1 && wasInvokedFromPaintGrid() ) {
super.fill( new Rectangle2D.Double( x, y + lineOffset, width, lineThickness ) );
super.fill( new Rectangle2D.Double( x, y, width, lineThickness ) );
return;
}
if( verticalLines && width == 1 && y == 0 && wasInvokedFromPaintGrid() ) {
super.fill( new Rectangle2D.Double( x + lineOffset, y, lineThickness, height ) );
super.fill( new Rectangle2D.Double( x, y, lineThickness, height ) );
return;
}
}
@@ -529,10 +405,6 @@ public class FlatTableUI
};
}
// rounded selection or selection insets
if( selectionArc > 0 || (selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets )) )
g = new RoundedSelectionGraphics( g, UIManager.getColor( "Table.alternateRowColor" ) );
super.paint( g, c );
}
@@ -578,6 +450,8 @@ public class FlatTableUI
boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" );
Color alternateColor;
if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) {
g.setColor( alternateColor );
int rowCount = table.getRowCount();
// paint alternating empty rows below the table
@@ -586,409 +460,11 @@ public class FlatTableUI
int tableWidth = table.getWidth();
int rowHeight = table.getRowHeight();
g.setColor( alternateColor );
int x = viewport.getComponentOrientation().isLeftToRight() ? 0 : viewportWidth - tableWidth;
for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) {
if( row % 2 != 0 )
paintAlternateRowBackground( g, -1, -1, x, y, tableWidth, rowHeight );
}
// add listener on demand
if( outsideAlternateRowsListener == null && table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF ) {
outsideAlternateRowsListener = new FlatOutsideAlternateRowsListener();
table.addComponentListener( outsideAlternateRowsListener );
g.fillRect( 0, y, tableWidth, rowHeight );
}
}
}
}
/**
* Paints (rounded) alternate row background.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3.5
*/
protected void paintAlternateRowBackground( Graphics g, int row, int column, int x, int y, int width, int height ) {
Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null;
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( column >= 0 ) {
// selection insets
// selection arc
if( column > 0 ) {
if( insets != null )
insets.left = 0;
if( table.getComponentOrientation().isLeftToRight() )
arcTopLeft = arcBottomLeft = 0;
else
arcTopRight = arcBottomRight = 0;
}
if( column < table.getColumnCount() - 1 ) {
if( insets != null )
insets.right = 0;
if( table.getComponentOrientation().isLeftToRight() )
arcTopRight = arcBottomRight = 0;
else
arcTopLeft = arcBottomLeft = 0;
}
}
FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height,
UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
}
/**
* Paints (rounded) cell selection.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3.5
*/
protected void paintCellSelection( Graphics g, int row, int column, int x, int y, int width, int height ) {
boolean rowSelAllowed = table.getRowSelectionAllowed();
boolean colSelAllowed = table.getColumnSelectionAllowed();
boolean rowSelOnly = rowSelAllowed && !colSelAllowed;
boolean colSelOnly = colSelAllowed && !rowSelAllowed;
boolean cellOnlySel = rowSelAllowed && colSelAllowed;
// get selection state of surrounding cells
boolean leftSelected = (column > 0 && (rowSelOnly || table.isCellSelected( row, column - 1 )));
boolean topSelected = (row > 0 && (colSelOnly || table.isCellSelected( row - 1, column )));
boolean rightSelected = (column < table.getColumnCount() - 1 && (rowSelOnly || table.isCellSelected( row, column + 1 )));
boolean bottomSelected = (row < table.getRowCount() - 1 && (colSelOnly || table.isCellSelected( row + 1, column )));
if( !table.getComponentOrientation().isLeftToRight() ) {
boolean temp = leftSelected;
leftSelected = rightSelected;
rightSelected = temp;
}
// selection insets
// (insets are applied to whole row if row-only selection is used,
// or to whole column if column-only selection is used,
// or to cell if cell selection is used)
Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null;
if( insets != null ) {
if( rowSelOnly && leftSelected )
insets.left = 0;
if( rowSelOnly && rightSelected )
insets.right = 0;
if( colSelOnly && topSelected )
insets.top = 0;
if( colSelOnly && bottomSelected )
insets.bottom = 0;
}
// selection arc
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( selectionArc > 0 ) {
// note that intercellSpacing is not considered as a gap because
// grid lines are usually painted to intercell space
boolean hasRowGap = (rowSelOnly || cellOnlySel) && insets != null && (insets.top != 0 || insets.bottom != 0);
boolean hasColGap = (colSelOnly || cellOnlySel) && insets != null && (insets.left != 0 || insets.right != 0);
if( leftSelected && !hasColGap )
arcTopLeft = arcBottomLeft = 0;
if( rightSelected && !hasColGap )
arcTopRight = arcBottomRight = 0;
if( topSelected && !hasRowGap )
arcTopLeft = arcTopRight = 0;
if( bottomSelected && !hasRowGap )
arcBottomLeft = arcBottomRight = 0;
}
FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height,
UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
}
/**
* Paints a cell selection at the given coordinates.
* The selection color must be set on the graphics context.
* <p>
* This method is intended for use in custom cell renderers to support
* {@link #selectionArc} and {@link #selectionInsets}.
*
* @since 3.5
*/
public static void paintCellSelection( JTable table, Graphics g, int row, int column, int x, int y, int width, int height ) {
if( !(table.getUI() instanceof FlatTableUI) )
return;
FlatTableUI ui = (FlatTableUI) table.getUI();
ui.paintCellSelection( g, row, column, x, y, width, height );
}
private void installRepaintRoundedSelectionListeners() {
if( rowSelectionListener == null ) {
rowSelectionListener = this::repaintRoundedRowSelection;
table.getSelectionModel().addListSelectionListener( rowSelectionListener );
}
if( columnSelectionListener == null ) {
columnSelectionListener = new TableColumnModelListener() {
@Override
public void columnSelectionChanged( ListSelectionEvent e ) {
repaintRoundedColumnSelection( e );
}
@Override public void columnRemoved( TableColumnModelEvent e ) {}
@Override public void columnMoved( TableColumnModelEvent e ) {}
@Override public void columnMarginChanged( ChangeEvent e ) {}
@Override public void columnAdded( TableColumnModelEvent e ) {}
};
table.getColumnModel().addColumnModelListener( columnSelectionListener );
}
}
private void repaintRoundedRowSelection( ListSelectionEvent e ) {
if( selectionArc <= 0 || !table.getRowSelectionAllowed() )
return;
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
if( rowCount <= 0 || columnCount <= 0 )
return;
// repaint including rows before and after changed selection
int firstRow = Math.max( 0, Math.min( e.getFirstIndex() - 1, rowCount - 1 ) );
int lastRow = Math.max( 0, Math.min( e.getLastIndex() + 1, rowCount - 1 ) );
Rectangle firstRect = table.getCellRect( firstRow, 0, false );
Rectangle lastRect = table.getCellRect( lastRow, columnCount - 1, false );
table.repaint( firstRect.union( lastRect ) );
}
private void repaintRoundedColumnSelection( ListSelectionEvent e ) {
if( selectionArc <= 0 || !table.getColumnSelectionAllowed() )
return;
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
if( rowCount <= 0 || columnCount <= 0 )
return;
// limit to selected rows for cell selection
int firstRow = 0;
int lastRow = rowCount - 1;
if( table.getRowSelectionAllowed() ) {
firstRow = table.getSelectionModel().getMinSelectionIndex();
lastRow = table.getSelectionModel().getMaxSelectionIndex();
}
// repaint including columns before and after changed selection
int firstColumn = Math.max( 0, Math.min( e.getFirstIndex() - 1, columnCount - 1 ) );
int lastColumn = Math.max( 0, Math.min( e.getLastIndex() + 1, columnCount - 1 ) );
Rectangle firstRect = table.getCellRect( firstRow, firstColumn, false );
Rectangle lastRect = table.getCellRect( lastRow, lastColumn, false );
table.repaint( firstRect.union( lastRect ) );
}
//---- class RoundedSelectionGraphics -------------------------------------
/**
* Because selection painting is done in the cell renderer, it would be
* necessary to require a FlatLaf specific renderer to implement rounded selection.
* Using a LaF specific renderer was avoided because often a custom renderer is
* already used in applications. Then either the rounded selection is not used,
* or the application has to be changed to extend a FlatLaf renderer.
* <p>
* To solve this, a graphics proxy is used that paints rounded selection
* if row/column/cell is selected and the renderer wants to fill the background.
*/
private class RoundedSelectionGraphics
extends Graphics2DProxy
{
private final Color alternateRowColor;
// used to avoid endless loop in case that paintCellSelection() invokes
// g.fillRect() with full bounds (selectionInsets is 0,0,0,0)
private boolean inPaintSelection;
RoundedSelectionGraphics( Graphics delegate, Color alternateRowColor ) {
super( (Graphics2D) delegate );
this.alternateRowColor = alternateRowColor;
}
@Override
public Graphics create() {
return new RoundedSelectionGraphics( super.create(), alternateRowColor );
}
@Override
public Graphics create( int x, int y, int width, int height ) {
return new RoundedSelectionGraphics( super.create( x, y, width, height ), alternateRowColor );
}
@Override
public void fillRect( int x, int y, int width, int height ) {
if( fillCellSelection( x, y, width, height ) )
return;
super.fillRect( x, y, width, height );
}
@Override
public void fill( Shape shape ) {
if( shape instanceof Rectangle2D ) {
Rectangle2D r = (Rectangle2D) shape;
double x = r.getX();
double y = r.getY();
double width = r.getWidth();
double height = r.getHeight();
if( x == (int) x && y == (int) y && width == (int) width && height == (int) height ) {
if( fillCellSelection( (int) x, (int) y, (int) width, (int) height ) )
return;
}
}
super.fill( shape );
}
private boolean fillCellSelection( int x, int y, int width, int height ) {
if( inPaintSelection )
return false;
Color color;
Component rendererComponent;
if( x == 0 && y == 0 &&
((color = getColor()) == table.getSelectionBackground() ||
(alternateRowColor != null && color == alternateRowColor)) &&
(rendererComponent = findActiveRendererComponent()) != null &&
width == rendererComponent.getWidth() &&
height == rendererComponent.getHeight() )
{
Point location = rendererComponent.getLocation();
int row = table.rowAtPoint( location );
int column = table.columnAtPoint( location );
if( row >= 0 && column >= 0 ) {
inPaintSelection = true;
if( color == table.getSelectionBackground() )
paintCellSelection( this, row, column, x, y, width, height );
else
paintAlternateRowBackground( this, row, column, x, y, width, height );
inPaintSelection = false;
return true;
}
}
return false;
}
/**
* A CellRendererPane may contain multiple components, if multiple renderers
* are used. Inactive renderer components have size {@code 0x0}.
*/
private Component findActiveRendererComponent() {
int count = rendererPane.getComponentCount();
for( int i = 0; i < count; i++ ) {
Component c = rendererPane.getComponent( i );
if( c.getWidth() > 0 && c.getHeight() > 0 )
return c;
}
return null;
}
}
//---- class OutsideAlternateRowsListener ---------------------------------
/**
* Used if table auto-resize-mode is off to repaint outside alternate rows
* when table width changed (column resized) or component orientation changed.
*/
private class FlatOutsideAlternateRowsListener
extends ComponentAdapter
{
@Override
public void componentHidden( ComponentEvent e ) {
Container viewport = SwingUtilities.getUnwrappedParent( table );
if( viewport instanceof JViewport )
HiDPIUtils.repaint( viewport );
}
@Override
public void componentMoved( ComponentEvent e ) {
repaintAreaBelowTable();
}
@Override
public void componentResized( ComponentEvent e ) {
repaintAreaBelowTable();
}
private void repaintAreaBelowTable() {
Container viewport = SwingUtilities.getUnwrappedParent( table );
if( viewport instanceof JViewport ) {
int viewportHeight = viewport.getHeight();
int tableHeight = table.getHeight();
if( tableHeight < viewportHeight )
HiDPIUtils.repaint( viewport, 0, tableHeight, viewport.getWidth(), viewportHeight - tableHeight );
}
}
}
//---- class FlatBooleanRenderer ------------------------------------------
private static class FlatBooleanRenderer
extends DefaultTableCellRenderer
implements UIResource
{
private boolean selected;
FlatBooleanRenderer() {
setHorizontalAlignment( SwingConstants.CENTER );
Icon icon = new FlatCheckBoxIcon() {
@Override
protected boolean isSelected( Component c ) {
return selected;
}
};
setIcon( icon );
setDisabledIcon( icon );
}
@Override
protected void setValue( Object value ) {
selected = (value != null && (Boolean) value);
}
}
//---- class StartEditingAction -------------------------------------------
private static class StartEditingAction
extends FlatUIAction
{
static void install( ActionMap map, String key ) {
Action oldAction = map.get( key );
if( oldAction == null || oldAction instanceof StartEditingAction )
return; // not found or already installed
map.put( key, new StartEditingAction( oldAction ) );
}
private StartEditingAction( Action delegate ) {
super( delegate );
}
@Override
public void actionPerformed( ActionEvent e ) {
JTable table = (JTable) e.getSource();
Component oldEditorComp = table.getEditorComponent();
delegate.actionPerformed( e );
// select all text in editor if editing starts with F2 key
Component editorComp = table.getEditorComponent();
if( oldEditorComp == null && editorComp instanceof JTextField )
((JTextField)editorComp).selectAll();
}
}
}

View File

@@ -141,6 +141,12 @@ public class FlatTextAreaUI
focusListener = null;
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
FlatEditorPaneUI.installKeyboardActions( getComponent() );
}
@Override
protected Caret createCaret() {
return new FlatCaret( null, false );

View File

@@ -45,7 +45,6 @@ import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
@@ -174,6 +173,8 @@ public class FlatTextFieldUI
defaultMargin = UIManager.getInsets( prefix + ".margin" );
LookAndFeel.installProperty( getComponent(), "opaque", false );
MigLayoutVisualPadding.install( getComponent() );
}
@Override
@@ -191,6 +192,8 @@ public class FlatTextFieldUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@Override
@@ -200,8 +203,6 @@ public class FlatTextFieldUI
// necessary to update focus border and background
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent(), null );
getComponent().addFocusListener( focusListener );
MigLayoutVisualPadding.install( getComponent() );
}
@Override
@@ -215,8 +216,6 @@ public class FlatTextFieldUI
getComponent().getDocument().removeDocumentListener( documentListener );
documentListener = null;
}
MigLayoutVisualPadding.uninstall( getComponent() );
}
@Override
@@ -239,7 +238,7 @@ public class FlatTextFieldUI
case COMPONENT_ROUND_RECT:
case OUTLINE:
case TEXT_FIELD_PADDING:
HiDPIUtils.repaint( c );
c.repaint();
break;
case MINIMUM_WIDTH:
@@ -250,38 +249,38 @@ public class FlatTextFieldUI
case STYLE_CLASS:
installStyle();
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
break;
case TEXT_FIELD_LEADING_ICON:
leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
HiDPIUtils.repaint( c );
c.repaint();
break;
case TEXT_FIELD_TRAILING_ICON:
trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
HiDPIUtils.repaint( c );
c.repaint();
break;
case TEXT_FIELD_LEADING_COMPONENT:
uninstallLeadingComponent();
installLeadingComponent();
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
break;
case TEXT_FIELD_TRAILING_COMPONENT:
uninstallTrailingComponent();
installTrailingComponent();
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
break;
case TEXT_FIELD_SHOW_CLEAR_BUTTON:
uninstallClearButton();
installClearButton();
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
break;
case "enabled":
@@ -481,21 +480,9 @@ debug*/
// compute placeholder location
Rectangle r = getVisibleEditorRect();
FontMetrics fm = c.getFontMetrics( c.getFont() );
int x = r.x;
int y = r.y + fm.getAscent() + ((r.height - fm.getHeight()) / 2);
// apply horizontal alignment to x location
String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, placeholder, r.width );
int stringWidth = fm.stringWidth( clippedPlaceholder );
int halign = (c instanceof JTextField) ? ((JTextField)c).getHorizontalAlignment() : SwingConstants.LEADING;
if( halign == SwingConstants.LEADING )
halign = isLeftToRight() ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = isLeftToRight() ? SwingConstants.RIGHT : SwingConstants.LEFT;
if( halign == SwingConstants.RIGHT )
x += r.width - stringWidth;
else if( halign == SwingConstants.CENTER )
x = Math.max( 0, x + (r.width / 2) - (stringWidth / 2) );
int x = r.x + (isLeftToRight() ? 0 : r.width - fm.stringWidth( clippedPlaceholder ));
int y = r.y + fm.getAscent() + ((r.height - fm.getHeight()) / 2);
// paint placeholder
g.setColor( placeholderForeground );
@@ -628,7 +615,7 @@ debug*/
/**
* Returns the rectangle used to paint leading and trailing icons.
* It invokes {@code super.getVisibleEditorRect()} and reduces left and/or
* right insets if the text field has leading or trailing icons or components.
* right margin 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 +647,24 @@ debug*/
}
}
// 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 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( leftVisible || (ltr ? hasLeadingIcon() : hasTrailingIcon()) ) {
// reduce left inset
Insets insets = getComponent().getInsets();
int newLeftInset = Math.min( insets.left, insets.top );
if( newLeftInset < insets.left ) {
int diff = insets.left - newLeftInset;
// 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 );
r.x -= diff;
r.width += diff;
}
}
if( rightVisible || (ltr ? hasTrailingIcon() : hasLeadingIcon()) ) {
// reduce right inset
Insets insets = getComponent().getInsets();
int newRightInset = Math.min( insets.right, insets.top );
if( newRightInset < insets.left )
r.width += insets.right - newRightInset;
// 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 );
}
// make sure that width and height are not negative
@@ -815,7 +802,7 @@ debug*/
if( visible != clearButton.isVisible() ) {
clearButton.setVisible( visible );
c.revalidate();
HiDPIUtils.repaint( c );
c.repaint();
}
}

View File

@@ -142,6 +142,12 @@ public class FlatTextPaneUI
focusListener = null;
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
FlatEditorPaneUI.installKeyboardActions( getComponent() );
}
@Override
protected Caret createCaret() {
return new FlatCaret( null, false );
@@ -156,6 +162,11 @@ public class FlatTextPaneUI
super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle );
// BasicEditorPaneUI.propertyChange() re-applied actions from editor kit,
// which removed our delegate actions
if( "editorKit".equals( propertyName ) )
FlatEditorPaneUI.installKeyboardActions( getComponent() );
}
/** @since 2 */

View File

@@ -33,9 +33,9 @@ import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
@@ -46,9 +46,9 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -57,6 +57,7 @@ import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
@@ -65,7 +66,6 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
@@ -89,18 +89,16 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.iconSize Dimension
* @uiDefault TitlePane.iconMargins Insets
* @uiDefault TitlePane.titleMargins Insets
* @uiDefault TitlePane.menuBarEmbedded boolean
* @uiDefault TitlePane.titleMinimumWidth int
* @uiDefault TitlePane.buttonMinimumWidth int
* @uiDefault TitlePane.buttonMaximizedHeight int
* @uiDefault TitlePane.buttonsGap int
* @uiDefault TitlePane.buttonsMargins Insets
* @uiDefault TitlePane.buttonsFillVertically boolean
* @uiDefault TitlePane.centerTitle boolean
* @uiDefault TitlePane.centerTitleIfMenuBarEmbedded boolean
* @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarEmbedded boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int
* @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
@@ -111,8 +109,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane
extends JComponent
{
static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
private static final boolean isWindows_10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
private static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
/** @since 2.5 */ protected final Font titleFont;
protected final Color activeBackground;
@@ -126,19 +123,15 @@ public class FlatTitlePane
/** @since 2.5 */ protected final boolean showIconInDialogs;
/** @since 2 */ protected final int noIconLeftGap;
protected final Dimension iconSize;
/** @since 3.6 */ protected final Insets iconMargins;
/** @since 3.6 */ protected final Insets titleMargins;
/** @since 2.4 */ protected final int titleMinimumWidth;
/** @since 2.4 */ protected final int buttonMinimumWidth;
protected final int buttonMaximizedHeight;
/** @since 3.6 */ protected final int buttonsGap;
/** @since 3.6 */ protected final Insets buttonsMargins;
/** @since 3.6 */ protected final boolean buttonsFillVertically;
protected final boolean centerTitle;
protected final boolean centerTitleIfMenuBarEmbedded;
/** @since 2.4 */ protected final boolean showIconBesideTitle;
protected final int menuBarTitleGap;
/** @since 2.4 */ protected final int menuBarTitleMinimumGap;
/** @since 2.4 */ protected final int menuBarResizeHeight;
protected final JRootPane rootPane;
protected final String windowStyle;
@@ -153,44 +146,16 @@ public class FlatTitlePane
protected JButton restoreButton;
protected JButton closeButton;
private JComponent iconifyMaximizeGapComp;
private JComponent maximizeCloseGapComp;
protected Window window;
private final Handler handler;
/**
* This panel handles mouse events if FlatLaf window decorations are used
* without native window border. E.g. on Linux.
* <p>
* This panel usually has same bounds as the title pane,
* except if fullWindowContent mode is enabled.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer
* under the title pane and under the frame content.
* The separation is necessary for fullWindowContent mode, where the title pane
* is layered over the frame content (for title pane buttons), but the mousePanel
* needs to be layered under the frame content so that components on content pane
* can receive mouse events when located in title area.
*/
final JPanel mouseLayer;
/**
* This panel paint a border at the top of the window in fullWindowContent mode,
* if FlatLaf window decorations are enabled.
* Only used on Windows 10.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer over all other layers.
*/
final JPanel windowTopBorderLayer;
public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane;
windowStyle = getWindowStyle( rootPane );
Window w = SwingUtilities.getWindowAncestor( rootPane );
String defaultWindowStyle = (w != null && w.getType() == Window.Type.UTILITY) ? WINDOW_STYLE_SMALL : null;
windowStyle = clientProperty( rootPane, WINDOW_STYLE, defaultWindowStyle, String.class );
titleFont = FlatUIUtils.getSubUIFont( "TitlePane.font", windowStyle );
activeBackground = FlatUIUtils.getSubUIColor( "TitlePane.background", windowStyle );
@@ -205,19 +170,15 @@ public class FlatTitlePane
showIconInDialogs = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconInDialogs", windowStyle, true );
noIconLeftGap = FlatUIUtils.getSubUIInt( "TitlePane.noIconLeftGap", windowStyle, 8 );
iconSize = FlatUIUtils.getSubUIDimension( "TitlePane.iconSize", windowStyle );
iconMargins = FlatUIUtils.getSubUIInsets( "TitlePane.iconMargins", windowStyle );
titleMargins = FlatUIUtils.getSubUIInsets( "TitlePane.titleMargins", windowStyle );
titleMinimumWidth = FlatUIUtils.getSubUIInt( "TitlePane.titleMinimumWidth", windowStyle, 60 );
buttonMinimumWidth = FlatUIUtils.getSubUIInt( "TitlePane.buttonMinimumWidth", windowStyle, 30 );
buttonMaximizedHeight = FlatUIUtils.getSubUIInt( "TitlePane.buttonMaximizedHeight", windowStyle, 0 );
buttonsGap = FlatUIUtils.getSubUIInt( "TitlePane.buttonsGap", windowStyle, 0 );
buttonsMargins = FlatUIUtils.getSubUIInsets( "TitlePane.buttonsMargins", windowStyle );
buttonsFillVertically = FlatUIUtils.getSubUIBoolean( "TitlePane.buttonsFillVertically", windowStyle, true );
centerTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.centerTitle", windowStyle, false );
centerTitleIfMenuBarEmbedded = FlatUIUtils.getSubUIBoolean( "TitlePane.centerTitleIfMenuBarEmbedded", windowStyle, true );
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 );
menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 );
menuBarResizeHeight = FlatUIUtils.getSubUIInt( "TitlePane.menuBarResizeHeight", windowStyle, 4 );
handler = createHandler();
@@ -226,28 +187,15 @@ public class FlatTitlePane
addSubComponents();
activeChanged( true );
mouseLayer = new JPanel();
mouseLayer.setOpaque( false );
mouseLayer.addMouseListener( handler );
mouseLayer.addMouseMotionListener( handler );
addMouseListener( handler );
addMouseMotionListener( handler );
if( isWindows_10 && FlatNativeWindowBorder.isSupported() ) {
windowTopBorderLayer = new JPanel();
windowTopBorderLayer.setVisible( false );
windowTopBorderLayer.setOpaque( false );
windowTopBorderLayer.setBorder( FlatUIUtils.nonUIResource( WindowTopBorder.getInstance() ) );
} else
windowTopBorderLayer = null;
// necessary for closing window with double-click on icon
iconLabel.addMouseListener( handler );
applyComponentOrientation( rootPane.getComponentOrientation() );
}
static String getWindowStyle( JRootPane rootPane ) {
Window w = SwingUtilities.getWindowAncestor( rootPane );
String defaultWindowStyle = (w != null && w.getType() == Window.Type.UTILITY) ? WINDOW_STYLE_SMALL : null;
return clientProperty( rootPane, WINDOW_STYLE, defaultWindowStyle, String.class );
}
protected FlatTitlePaneBorder createTitlePaneBorder() {
return new FlatTitlePaneBorder();
}
@@ -265,8 +213,8 @@ public class FlatTitlePane
setUI( new FlatTitleLabelUI() );
}
};
iconLabel.setBorder( new FlatEmptyBorder( iconMargins ) );
titleLabel.setBorder( new FlatEmptyBorder( titleMargins ) );
iconLabel.setBorder( new FlatEmptyBorder( FlatUIUtils.getSubUIInsets( "TitlePane.iconMargins", windowStyle ) ) );
titleLabel.setBorder( new FlatEmptyBorder( FlatUIUtils.getSubUIInsets( "TitlePane.titleMargins", windowStyle ) ) );
leftPanel.setLayout( new BoxLayout( leftPanel, BoxLayout.LINE_AXIS ) );
leftPanel.setOpaque( false );
@@ -286,11 +234,6 @@ public class FlatTitlePane
setLayout( new BorderLayout() {
@Override
public void layoutContainer( Container target ) {
if( isFullWindowContent() ) {
super.layoutContainer( target );
return;
}
// compute available bounds
Insets insets = target.getInsets();
int x = insets.left;
@@ -304,7 +247,7 @@ public class FlatTitlePane
int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth );
// increase minimum width if icon is shown besides the title
// increase minimum width if icon is show besides the title
Icon icon = titleLabel.getIcon();
if( icon != null ) {
Insets iconInsets = iconLabel.getInsets();
@@ -352,9 +295,6 @@ public class FlatTitlePane
horizontalGlue.getWidth(), titleLabel.getHeight() );
}
}
// clear hit-test cache
lastCaptionHitTestTime = 0;
}
} );
@@ -369,33 +309,23 @@ public class FlatTitlePane
restoreButton = createButton( "TitlePane.restoreIcon", "Restore", e -> restore() );
closeButton = createButton( "TitlePane.closeIcon", "Close", e -> close() );
iconifyMaximizeGapComp = createButtonsGapComp();
maximizeCloseGapComp = createButtonsGapComp();
// initially hide buttons that are only supported in frames
iconifyButton.setVisible( false );
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
iconifyMaximizeGapComp.setVisible( false );
maximizeCloseGapComp.setVisible( false );
buttonPanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
int titleBarHeight = clientPropertyInt( rootPane, TITLE_BAR_HEIGHT, -1 );
if( titleBarHeight >= 0 )
return new Dimension( size.width, UIScale.scale( titleBarHeight ) );
if( buttonMaximizedHeight > 0 && isWindowMaximized() && !hasVisibleEmbeddedMenuBar( rootPane.getJMenuBar() ) ) {
// make title pane height smaller when frame is maximized
size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight + buttonsMargins.top + buttonsMargins.bottom ) ) );
size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight ) ) );
}
return size;
}
};
buttonPanel.setOpaque( false );
buttonPanel.setBorder( FlatUIUtils.nonUIResource( new FlatEmptyBorder( buttonsMargins ) ) );
buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) );
if( rootPane.getWindowDecorationStyle() == JRootPane.FRAME ) {
// JRootPane.FRAME works only for frames (and not for dialogs)
@@ -404,19 +334,10 @@ public class FlatTitlePane
// later in frameStateChanged(), which is invoked from addNotify()
buttonPanel.add( iconifyButton );
buttonPanel.add( iconifyMaximizeGapComp );
buttonPanel.add( maximizeButton );
buttonPanel.add( restoreButton );
buttonPanel.add( maximizeCloseGapComp );
}
buttonPanel.add( closeButton );
ComponentListener l = new ComponentAdapter() {
@Override public void componentResized( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
@Override public void componentMoved( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
};
buttonPanel.addComponentListener( l );
addComponentListener( l );
}
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
@@ -424,17 +345,7 @@ public class FlatTitlePane
@Override
public Dimension getMinimumSize() {
// allow the button to shrink if space is rare
return new Dimension(
Math.min( UIScale.scale( buttonMinimumWidth ), super.getPreferredSize().width ),
super.getMinimumSize().height );
}
@Override
public Dimension getMaximumSize() {
// allow the button to fill whole button area height
// see also BasicMenuUI.getMaximumSize()
return buttonsFillVertically
? new Dimension( super.getMaximumSize().width, Short.MAX_VALUE )
: super.getMaximumSize();
return new Dimension( UIScale.scale( buttonMinimumWidth ), super.getMinimumSize().height );
}
};
button.setFocusable( false );
@@ -445,14 +356,6 @@ public class FlatTitlePane
return button;
}
private JComponent createButtonsGapComp() {
JComponent gapComp = new JPanel();
gapComp.setOpaque( false );
gapComp.setMinimumSize( new Dimension( 0, 0 ) );
gapComp.setPreferredSize( new Dimension( UIScale.scale( buttonsGap ), 0 ) );
return gapComp;
}
protected void activeChanged( boolean active ) {
Color background = clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null );
Color foreground = clientPropertyColor( rootPane, TITLE_BAR_FOREGROUND, null );
@@ -474,9 +377,6 @@ public class FlatTitlePane
closeButton.setForeground( foreground );
// this is necessary because hover/pressed colors are derived from background color
// (since FlatWindowAbstractIcon now invokes FlatTitlePane.getBackground()
// to get base color, this is no longer necessary, but keep it for compatibility;
// e.g. for custom window icons)
iconifyButton.setBackground( background );
maximizeButton.setBackground( background );
restoreButton.setBackground( background );
@@ -517,9 +417,7 @@ public class FlatTitlePane
/** @since 3 */
protected void updateVisibility() {
boolean isFullWindowContent = isFullWindowContent();
leftPanel.setVisible( !isFullWindowContent );
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) && !isFullWindowContent );
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) );
closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) );
if( window instanceof Frame ) {
@@ -536,13 +434,6 @@ public class FlatTitlePane
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
}
boolean iconifyVisible = iconifyButton.isVisible();
boolean maximizeVisible = maximizeButton.isVisible();
boolean restoreVisible = restoreButton.isVisible();
boolean closeVisible = closeButton.isVisible();
iconifyMaximizeGapComp.setVisible( iconifyVisible && (maximizeVisible || restoreVisible || closeVisible) );
maximizeCloseGapComp.setVisible( closeVisible && (maximizeVisible || restoreVisible) );
}
protected void updateIcon() {
@@ -552,7 +443,7 @@ public class FlatTitlePane
// get window images
List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) && !isFullWindowContent() ) {
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) {
images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
@@ -577,13 +468,6 @@ public class FlatTitlePane
updateNativeTitleBarHeightAndHitTestSpotsLater();
}
void updateFullWindowContentButtonsBoundsProperty() {
Rectangle bounds = isFullWindowContent()
? new Rectangle( SwingUtilities.convertPoint( buttonPanel, 0, 0, rootPane ), buttonPanel.getSize() )
: null;
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
}
@Override
public void addNotify() {
super.addNotify();
@@ -638,11 +522,6 @@ public class FlatTitlePane
window.removeComponentListener( handler );
}
/** @since 3.4 */
protected boolean isFullWindowContent() {
return FlatRootPaneUI.isFullWindowContent( rootPane );
}
/**
* Returns whether this title pane currently has a visible and embedded menubar.
*/
@@ -654,9 +533,6 @@ public class FlatTitlePane
* Returns whether the menubar should be embedded into the title pane.
*/
protected boolean isMenuBarEmbedded() {
if( isFullWindowContent() )
return false;
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.MENUBAR_EMBEDDED,
@@ -732,10 +608,6 @@ public class FlatTitlePane
doLayout();
}
void menuBarInvalidate() {
menuBarPlaceholder.invalidate();
}
@Override
public void paint( Graphics g ) {
super.paint( g );
@@ -744,45 +616,21 @@ public class FlatTitlePane
return;
if( debugTitleBarHeight > 0 ) {
// title bar height is measured from window top edge
int y = SwingUtilities.convertPoint( window, 0, debugTitleBarHeight, this ).y;
g.setColor( Color.green );
g.drawLine( 0, y, getWidth(), y );
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
}
g.setColor( Color.red );
debugPaintComponentWithMouseListener( g, Color.red, rootPane.getLayeredPane(), 0, 0 );
debugPaintRect( g, Color.blue, debugAppIconBounds );
debugPaintRect( g, Color.blue, debugMinimizeButtonBounds );
debugPaintRect( g, Color.magenta, debugMaximizeButtonBounds );
debugPaintRect( g, Color.cyan, debugCloseButtonBounds );
if( debugHitTestSpots != null ) {
for( Rectangle r : debugHitTestSpots )
paintRect( g, Color.red, r );
}
paintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.cyan, debugCloseButtonBounds );
}
private void debugPaintComponentWithMouseListener( Graphics g, Color color, Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || c == mouseLayer ||
c == iconifyButton || c == maximizeButton || c == restoreButton || c == closeButton )
return;
if( c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0 ||
c.getMouseWheelListeners().length > 0 )
{
g.drawRect( x, y, c.getWidth(), c.getHeight() );
return;
}
if( c instanceof Container ) {
Rectangle titlePaneBoundsOnWindow = SwingUtilities.convertRectangle( this, new Rectangle( getSize() ), window );
for( Component child : ((Container)c).getComponents() ) {
Rectangle compBoundsOnWindow = SwingUtilities.convertRectangle( c, new Rectangle( c.getSize() ), window );
if( compBoundsOnWindow.intersects( titlePaneBoundsOnWindow ) )
debugPaintComponentWithMouseListener( g, color, child, x + child.getX(), y + child.getY() );
}
}
}
private void debugPaintRect( Graphics g, Color color, Rectangle r ) {
private void paintRect( Graphics g, Color color, Rectangle r ) {
if( r == null )
return;
@@ -793,20 +641,22 @@ public class FlatTitlePane
@Override
protected void paintComponent( Graphics g ) {
if( isFullWindowContent() )
return;
g.setColor( getBackground() );
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
? FlatUIUtils.getParentBackground( this )
: getBackground() );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
@Override
public Color getBackground() {
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
return (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
? FlatUIUtils.getParentBackground( this )
: super.getBackground();
protected void repaintWindowBorder() {
int width = rootPane.getWidth();
int height = rootPane.getHeight();
Insets insets = rootPane.getInsets();
rootPane.repaint( 0, 0, width, insets.top ); // top
rootPane.repaint( 0, 0, insets.left, height ); // left
rootPane.repaint( 0, height - insets.bottom, width, insets.bottom ); // bottom
rootPane.repaint( width - insets.right, 0, insets.right, height ); // right
}
/**
@@ -878,8 +728,7 @@ public class FlatTitlePane
Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
if( !hasNativeCustomDecoration() &&
(oldMaximizedBounds == null ||
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) &&
window.getGraphicsConfiguration() != null )
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) )
{
GraphicsConfiguration gc = window.getGraphicsConfiguration();
@@ -914,7 +763,7 @@ public class FlatTitlePane
// screen insets are in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8243925)
// and except for Java 8 on secondary screens where primary screen is scaled
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
Insets screenInsets = window.getToolkit().getScreenInsets( gc );
// maximized bounds are required in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and
@@ -986,6 +835,10 @@ public class FlatTitlePane
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
}
private boolean hasJBRCustomDecoration() {
return window != null && JBRCustomDecorations.hasCustomDecoration( window );
}
/**
* Returns whether windows uses native window border and has custom decorations enabled.
*/
@@ -993,10 +846,6 @@ public class FlatTitlePane
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
}
boolean isWindowTopBorderNeeded() {
return isWindows_10 && hasNativeCustomDecoration();
}
// used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater()
private int laterCounter;
@@ -1017,14 +866,11 @@ public class FlatTitlePane
return;
int titleBarHeight = getHeight();
// title bar height must be measured from window top edge
// (when window is maximized, window y location is e.g. -11 and window top inset is 11)
for( Component c = this; c != window && c != null; c = c.getParent() )
titleBarHeight += c.getY();
// slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 )
titleBarHeight--;
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null;
if( !showIconBesideTitle && iconLabel.isVisible() ) {
@@ -1050,7 +896,10 @@ public class FlatTitlePane
iconBounds.width += iconInsets.right;
}
appIconBounds = iconBounds;
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconBounds );
else
appIconBounds = iconBounds;
} else if( showIconBesideTitle && titleLabel.getIcon() != null && titleLabel.getUI() instanceof FlatTitleLabelUI ) {
FlatTitleLabelUI ui = (FlatTitleLabelUI) titleLabel.getUI();
@@ -1078,21 +927,78 @@ public class FlatTitlePane
iconR.width += 2;
iconR.height += 2;
appIconBounds = iconR;
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconR );
else
appIconBounds = iconR;
}
}
Rectangle r = getNativeHitTestSpot( buttonPanel );
if( r != null )
hitTestSpots.add( r );
JMenuBar menuBar = rootPane.getJMenuBar();
if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
r = getNativeHitTestSpot( menuBar );
if( r != null ) {
// if frame is resizable and not maximized, make menu bar hit test spot smaller at top
// to have a small area above the menu bar to resize the window
if( window instanceof Frame && ((Frame)window).isResizable() && !isWindowMaximized() ) {
// limit to 8, because Windows does not use a larger height
int resizeHeight = UIScale.scale( Math.min( menuBarResizeHeight, 8 ) );
r.y += resizeHeight;
r.height -= resizeHeight;
}
int count = menuBar.getComponentCount();
for( int i = count - 1; i >= 0; i-- ) {
Component c = menuBar.getComponent( i );
if( c instanceof Box.Filler ||
(c instanceof JComponent && clientPropertyBoolean( (JComponent) c, COMPONENT_TITLE_BAR_CAPTION, false ) ) )
{
// If menu bar is embedded and contains a horizontal glue or caption component,
// then split the hit test spot so that
// the glue/caption component area can be used to move the window.
Point glueLocation = SwingUtilities.convertPoint( c, 0, 0, window );
int x2 = glueLocation.x + c.getWidth();
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
r.width = glueLocation.x - r.x;
} else {
r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
r.width = (r.x + r.width) - x2;
r.x = x2;
}
if( r2.width > 0 )
hitTestSpots.add( r2 );
}
}
hitTestSpots.add( r );
}
}
// allow internal frames in layered pane to be moved/resized when placed over title bar
for( Component c : rootPane.getLayeredPane().getComponents() ) {
r = (c instanceof JInternalFrame) ? getNativeHitTestSpot( (JInternalFrame) c ) : null;
if( r != null )
hitTestSpots.add( r );
}
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton );
// clear hit-test cache
lastCaptionHitTestTime = 0;
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
@@ -1107,123 +1013,18 @@ public class FlatTitlePane
: null;
}
/**
* Returns whether there is a component at the given location, that processes
* mouse events. E.g. buttons, menus, etc.
* <p>
* Note:
* <ul>
* <li>This method is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul>
*/
private boolean captionHitTest( Point pt ) {
// Windows invokes this method every ~200ms, even if the mouse has not moved
long time = System.currentTimeMillis();
if( pt.x == lastCaptionHitTestX && pt.y == lastCaptionHitTestY && time < lastCaptionHitTestTime + 300 ) {
lastCaptionHitTestTime = time;
return lastCaptionHitTestResult;
}
protected Rectangle getNativeHitTestSpot( JComponent c ) {
Dimension size = c.getSize();
if( size.width <= 0 || size.height <= 0 )
return null;
// convert pt from window coordinates to layeredPane coordinates
Component layeredPane = rootPane.getLayeredPane();
int x = pt.x;
int y = pt.y;
for( Component c = layeredPane; c != window && c != null; c = c.getParent() ) {
x -= c.getX();
y -= c.getY();
}
lastCaptionHitTestX = pt.x;
lastCaptionHitTestY = pt.y;
lastCaptionHitTestTime = time;
lastCaptionHitTestResult = isTitleBarCaptionAt( layeredPane, x, y );
return lastCaptionHitTestResult;
Point location = SwingUtilities.convertPoint( c, 0, 0, window );
Rectangle r = new Rectangle( location, size );
return r;
}
private boolean isTitleBarCaptionAt( Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || !contains( c, x, y ) || c == mouseLayer )
return true; // continue checking with next component
// check enabled component that has mouse listeners
if( c.isEnabled() &&
(c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0) )
{
if( !(c instanceof JComponent) )
return false; // assume that this is not a caption because the component has mouse listeners
// check client property boolean value
Object caption = ((JComponent)c).getClientProperty( COMPONENT_TITLE_BAR_CAPTION );
if( caption instanceof Boolean )
return (boolean) caption;
// if component is not fully layouted, do not invoke function
// because it is too dangerous that the function tries to layout the component,
// which could cause a dead lock
if( !c.isValid() ) {
// revalidate if necessary so that it is valid when invoked again later
EventQueue.invokeLater( () -> {
Window w = SwingUtilities.windowForComponent( c );
if( w != null )
w.revalidate();
else
c.revalidate();
} );
return false; // assume that this is not a caption because the component has mouse listeners
}
if( caption instanceof Function ) {
// check client property function value
@SuppressWarnings( "unchecked" )
Function<Point, Boolean> hitTest = (Function<Point, Boolean>) caption;
Boolean result = hitTest.apply( new Point( x, y ) );
if( result != null )
return result;
} else {
// check component UI
ComponentUI ui = JavaCompatibility2.getUI( (JComponent) c );
if( !(ui instanceof TitleBarCaptionHitTest) )
return false; // assume that this is not a caption because the component has mouse listeners
Boolean result = ((TitleBarCaptionHitTest)ui).isTitleBarCaptionAt( x, y );
if( result != null )
return result;
}
// else continue checking children
}
// check children
if( c instanceof Container ) {
for( Component child : ((Container)c).getComponents() ) {
if( !isTitleBarCaptionAt( child, x - child.getX(), y - child.getY() ) )
return false;
}
}
return true;
}
/**
* Same as {@link Component#contains(int, int)}, but not using that method
* because it may be overridden by custom components and invoke code that
* tries to request AWT tree lock on 'AWT-Windows' thread.
* This could freeze the application if AWT tree is already locked on 'AWT-EventQueue' thread.
*/
private boolean contains( Component c, int x, int y ) {
return x >= 0 && y >= 0 && x < c.getWidth() && y < c.getHeight();
}
private int lastCaptionHitTestX;
private int lastCaptionHitTestY;
private long lastCaptionHitTestTime;
private boolean lastCaptionHitTestResult;
private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds;
@@ -1246,7 +1047,7 @@ public class FlatTitlePane
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 );
if( isWindowTopBorderNeeded() && !isWindowMaximized() )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() );
return insets;
@@ -1265,7 +1066,7 @@ public class FlatTitlePane
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
}
if( isWindowTopBorderNeeded() && !isWindowMaximized() && !isFullWindowContent() )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
}
@@ -1321,7 +1122,7 @@ public class FlatTitlePane
}
}
// compute icon width and gap (if icon is shown besides the title)
// compute icon width and gap (if icon is show besides the title)
int iconTextGap = 0;
int iconWidthAndGap = 0;
if( icon != null ) {
@@ -1330,7 +1131,7 @@ public class FlatTitlePane
iconWidthAndGap = icon.getIconWidth() + iconTextGap;
}
// layout title and icon (if shown besides the title)
// layout title and icon (if show besides the title)
String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
@@ -1429,7 +1230,10 @@ public class FlatTitlePane
activeChanged( true );
updateNativeTitleBarHeightAndHitTestSpots();
repaintBorder();
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
}
@Override
@@ -1437,22 +1241,10 @@ public class FlatTitlePane
activeChanged( false );
updateNativeTitleBarHeightAndHitTestSpots();
repaintBorder();
}
private void repaintBorder() {
// Windows 10 top border
if( windowTopBorderLayer != null && windowTopBorderLayer.isShowing())
WindowTopBorder.getInstance().repaintBorder( windowTopBorderLayer );
else if( isWindowTopBorderNeeded() && !isWindowMaximized() && !isFullWindowContent() )
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
// Window border used for non-native window decorations
if( rootPane.getBorder() instanceof FlatRootPaneUI.FlatWindowBorder ) {
// not repainting four areas on the four sides because RepaintManager
// unions dirty regions, which also results in repaint of whole rootpane
rootPane.repaint();
}
repaintWindowBorder();
}
@Override
@@ -1472,11 +1264,24 @@ debug*/
private Point dragOffset;
private boolean linuxNativeMove;
private long lastSingleClickWhen;
@Override
public void mouseClicked( MouseEvent e ) {
// on Linux, when using native library, the mouse clicked event
// is usually not sent and maximize/restore is done in mouse pressed event
// this check is here for the case that a mouse clicked event comes thru for some reason
if( linuxNativeMove && SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// see comment in mousePressed()
if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) {
lastSingleClickWhen = 0;
maximizeOrRestore();
}
return;
}
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( SwingUtilities.getDeepestComponentAt( FlatTitlePane.this, e.getX(), e.getY() ) == iconLabel ) {
if( e.getSource() == iconLabel ) {
// double-click on icon closes window
close();
} else if( !hasNativeCustomDecoration() ) {
@@ -1503,8 +1308,44 @@ debug*/
if( !SwingUtilities.isLeftMouseButton( e ) )
return;
dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window );
dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
linuxNativeMove = false;
// on Linux, move or maximize/restore window
if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// The fired Java mouse events, when doing a double-click and the first click
// sends a _NET_WM_MOVERESIZE message, are different for various Linux distributions:
// CentOS 7 (GNOME 3.28.2, X11): PRESSED(clickCount=1) PRESSED(clickCount=2) RELEASED(clickCount=2)
// Ubuntu 20.04 (GNOME 3.36.1, X11): PRESSED(clickCount=1) PRESSED(clickCount=2) RELEASED(clickCount=2)
// Ubuntu 22.04 (GNOME 42.2, Wayland): PRESSED(clickCount=1) RELEASED(clickCount=1) CLICKED(clickCount=1)
// Kubuntu 22.04 (KDE 5.24.4, X11): PRESSED(clickCount=1) PRESSED(clickCount=1) RELEASED(clickCount=1)
// double-click is not always recognized in Java when using _NET_WM_MOVERESIZE message
int clickCount = e.getClickCount();
if( clickCount == 1 && lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() )
clickCount = 2;
switch( clickCount ) {
case 1:
// move window via _NET_WM_MOVERESIZE message
e.consume();
linuxNativeMove = FlatNativeLinuxLibrary.moveOrResizeWindow( window, e, FlatNativeLinuxLibrary.MOVE );
lastSingleClickWhen = e.getWhen();
break;
case 2:
// maximize/restore on double-click
// also done here because no mouse clicked event is sent when using _NET_WM_MOVERESIZE message
lastSingleClickWhen = 0;
maximizeOrRestore();
break;
}
}
}
private int getMultiClickInterval() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "awt.multiClickInterval" );
return (value instanceof Integer) ? (Integer) value : 500;
}
@Override public void mouseReleased( MouseEvent e ) {}
@@ -1527,13 +1368,6 @@ debug*/
if( hasNativeCustomDecoration() )
return; // do nothing if having native window border
// on Linux, move window using window manager
if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
linuxNativeMove = FlatNativeLinuxLibrary.moveOrResizeWindow( window, e, FlatNativeLinuxLibrary.MOVE );
if( linuxNativeMove )
return;
}
// restore window if it is maximized
if( window instanceof Frame ) {
Frame frame = (Frame) window;
@@ -1587,36 +1421,4 @@ debug*/
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
//---- interface TitleBarCaptionHitTest -----------------------------------
/**
* For custom components use {@link FlatClientProperties#COMPONENT_TITLE_BAR_CAPTION}
* instead of this interface.
*
* @since 3.4
*/
public interface TitleBarCaptionHitTest {
/**
* Invoked for a component that is enabled and has mouse listeners,
* to check whether it processes mouse input at the given x/y location.
* Useful for components that do not use mouse input on whole component bounds.
* E.g. a tabbed pane with a few tabs has some empty space beside the tabs
* that can be used to move the window.
* <p>
* Note:
* <ul>
* <li>This method is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul>
*
* @return {@code true} if the component is not interested in mouse input at the given location
* {@code false} if the component wants process mouse input at the given location
* {@code null} if the component children should be checked
*/
Boolean isTitleBarCaptionAt( int x, int y );
}
}

View File

@@ -26,7 +26,6 @@ import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -160,14 +159,14 @@ public class FlatToggleButtonUI
b.revalidate();
}
HiDPIUtils.repaint( b );
b.repaint();
break;
case TAB_BUTTON_UNDERLINE_PLACEMENT:
case TAB_BUTTON_UNDERLINE_HEIGHT:
case TAB_BUTTON_UNDERLINE_COLOR:
case TAB_BUTTON_SELECTED_BACKGROUND:
HiDPIUtils.repaint( b );
b.repaint();
break;
}
}

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -36,7 +35,6 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -132,7 +130,7 @@ public class FlatToolBarSeparatorUI
} else
installStyle( s );
s.revalidate();
HiDPIUtils.repaint( s );
s.repaint();
break;
}
}
@@ -175,12 +173,6 @@ public class FlatToolBarSeparatorUI
if( size != null )
return scale( size );
// get separator width
int separatorWidth = this.separatorWidth;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorWidth != null )
separatorWidth = toolBarUI.separatorWidth;
// make sure that gap on left and right side of line have same size
int sepWidth = (scale( (separatorWidth - LINE_WIDTH) / 2 ) * 2) + scale( LINE_WIDTH );
@@ -204,12 +196,6 @@ public class FlatToolBarSeparatorUI
float lineWidth = scale( 1f );
float offset = scale( 2f );
// get separator color
Color separatorColor = this.separatorColor;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorColor != null )
separatorColor = toolBarUI.separatorColor;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( separatorColor );
@@ -224,11 +210,4 @@ public class FlatToolBarSeparatorUI
private boolean isVertical( JComponent c ) {
return ((JToolBar.Separator)c).getOrientation() == SwingConstants.VERTICAL;
}
private FlatToolBarUI getToolBarUI( JComponent c ) {
Container parent = c.getParent();
return (parent instanceof JToolBar && ((JToolBar)parent).getUI() instanceof FlatToolBarUI)
? (FlatToolBarUI) ((JToolBar)parent).getUI()
: null;
}
}

View File

@@ -47,8 +47,6 @@ 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;
@@ -84,7 +82,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatToolBarUI
extends BasicToolBarUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
implements StyleableUI
{
/** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
@@ -95,10 +93,6 @@ public class FlatToolBarUI
@Styleable protected Insets borderMargins;
@Styleable protected Color gripColor;
// for FlatToolBarSeparatorUI
/** @since 3.3 */ @Styleable protected Integer separatorWidth;
/** @since 3.3 */ @Styleable protected Color separatorColor;
private FocusTraversalPolicy focusTraversalPolicy;
private Boolean oldFloatable;
private Map<String, Object> oldStyleValues;
@@ -145,13 +139,11 @@ public class FlatToolBarUI
hoverButtonGroupBackground = UIManager.getColor( "ToolBar.hoverButtonGroupBackground" );
// floatable
oldFloatable = null;
if( !UIManager.getBoolean( "ToolBar.floatable" ) ) {
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} );
}
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} else
oldFloatable = null;
}
@Override
@@ -161,9 +153,7 @@ public class FlatToolBarUI
hoverButtonGroupBackground = null;
if( oldFloatable != null ) {
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
toolBar.setFloatable( oldFloatable );
} );
toolBar.setFloatable( oldFloatable );
oldFloatable = null;
}
}
@@ -449,7 +439,7 @@ public class FlatToolBarUI
// repaint button group
if( gr != null )
HiDPIUtils.repaint(toolBar, gr );
toolBar.repaint( gr );
}
private ButtonGroup getButtonGroup( AbstractButton b ) {
@@ -459,15 +449,6 @@ public class FlatToolBarUI
: null;
}
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicToolBarUI adds some mouse listeners for dragging when toolbar is floatable
return null; // check children
}
//---- class FlatToolBarFocusTraversalPolicy ------------------------------
/**
@@ -536,11 +517,8 @@ public class FlatToolBarUI
private Component getRecentComponent( Container aContainer, boolean first ) {
// if moving focus into the toolbar, focus recently focused toolbar button
if( focusedCompIndex >= 0 && focusedCompIndex < toolBar.getComponentCount() ) {
Component c = toolBar.getComponent( focusedCompIndex );
if( accept( c ) )
return c;
}
if( focusedCompIndex >= 0 && focusedCompIndex < toolBar.getComponentCount() )
return toolBar.getComponent( focusedCompIndex );
return first
? super.getFirstComponent( aContainer )

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