Compare commits

...

98 Commits

Author SHA1 Message Date
Karl Tauber
84e95764fa macOS: hide popups when window is resized (issue #1082) 2026-01-30 19:01:26 +01:00
Karl Tauber
1a80a65411 Merge PR #1087: Fix exception when SVG icon name has no file extension 2026-01-30 13:17:56 +01:00
Karl Tauber
bf2227e1b8 Dialog: some client properties (e.g. JRootPane.titleBarShowTitle) did not work before the dialog was made visible (issue #1081) 2026-01-30 12:58:17 +01:00
poce1don
7fc26fe77e Fix exception when SVG icon name has no file extension 2026-01-24 20:44:50 -03:00
Karl Tauber
465254c0da FlatDesktop: avoid unnecessary logging if desktop is not supported (e.g. on NixOS with Plasma/KDE desktop) 2026-01-14 13:21:52 +01:00
Karl Tauber
aaca7cace1 README.md: added notes regarding uber/fat JARs and obfuscation/minimizing/shrinking tools (replaces PR #845) 2026-01-14 13:09:40 +01:00
Karl Tauber
c995b4cbdf Merge PR #1074: Recalculate geometry after styling in FlatSliderUI 2026-01-13 20:03:54 +01:00
Karl Tauber
b251ff76e8 Merge PR #1080: Fix NPE in FlatUIDefaultsInspector when color is null 2026-01-13 19:31:56 +01:00
Karl Tauber
ea2a985481 ToolBar: do not change floatable if it was changed from application code (issue #1075) 2026-01-13 12:35:49 +01:00
poce1don
3ca7ebbfee Fix NPE in FlatUIDefaultsInspector when color is null 2026-01-13 00:44:13 -03:00
daWoife
7e6dc269c5 Recalculate geometry after styling in FlatSliderUI 2025-12-31 11:28:41 +01:00
Karl Tauber
7680c3a817 ComboBox: added UI property ComboBox.buttonFocusedEditableBackground (issue #1068) 2025-12-30 17:36:06 +01:00
Karl Tauber
0e3cb95791 Popup: fixed scrolling popup painting issue on Windows 10 when a glass pane is visible and frame is maximized (issue #1071) 2025-12-30 13:36:09 +01:00
Karl Tauber
3f0b5d5a0e release 3.7 2025-12-04 12:13:56 +01:00
Karl Tauber
6e3633cca3 System File Chooser: save and restore size of (fallback) JFileChooser dialog (PR #988) 2025-12-04 11:36:02 +01:00
Karl Tauber
4f1e5cdb05 IntelliJ Themes: do not release JSON memory to allow re-using theme instance (issue #1020) 2025-12-03 19:13:07 +01:00
Karl Tauber
070cf9c40d Icons: scale checkbox and radiobutton icons when using text styles large, medium, small and mini (issue #1061)
https://www.formdev.com/flatlaf/typography/#text_styles

Theme Editor: added to preview
2025-12-03 14:19:47 +01:00
Karl Tauber
5b0f13110a Icons: support scaling Laf icons (checkbox, radiobutton, etc) (issue #1061) 2025-12-03 11:41:39 +01:00
Karl Tauber
d7a5c353fe System File Chooser: updated Linux native libraries (PR #988)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/19859966311
2025-12-02 18:19:45 +01:00
Karl Tauber
9e8b8697d1 System File Chooser: Linux: show file dialog in dark if current FlatLaf theme is dark (PR #988) 2025-12-02 14:11:10 +01:00
Karl Tauber
58e073a05b System File Chooser: on Linux when JavaFX is used in application, then always use Swing file chooser because system file dialog does work (PR #988)
with Java 8, some GLib related messages are logged to console and system file dialog is not shown
e.g.: `GLib-GObject-WARNING **: cannot register existing type 'GdkDisplayManager'`

with Java 17, the system file dialog is shown, but then JavaFX no longer works
with Java 21, the application quits/crashes immediately when trying to show system file dialog

also fixed Error Prone warning in `getFiltersForDialog()`
2025-12-01 20:10:06 +01:00
Karl Tauber
1c6e8774cf System File Chooser: (PR #988)
- fixed missing filter in combobox if only `setFileFilter(myFilter)` is used, but not `addChoosableFileFilter(myFilter)`
- fallback Swing file chooser did ignore `isAcceptAllFileFilterUsed()`
- check for supported filter types in `setFileFilter()`
2025-11-30 19:16:51 +01:00
Karl Tauber
1e724029ae Merge PR #987: Enhance FlatUIUtils.getBorderArc to support FlatLineBorder Arc value 2025-11-30 00:44:22 +01:00
Karl Tauber
9fdaa827b5 Merge PR #1030: TabbedPaneUI: When the mouse wheel event is not in the viewport, propagate the event to the nearest parent 2025-11-29 23:25:50 +01:00
Karl Tauber
b9441050b2 TabbedPane:
- convert mouse wheel event before dispatching it to parent
- use `Component.dispatchEvent()`, instead of invoking mouse wheel listeners directly, which dispatches the event to an ancestor
2025-11-29 23:12:21 +01:00
Karl Tauber
8239cdd540 fixed errors reported by Error Prone in commit 855c41bf4a 2025-11-29 18:47:06 +01:00
Karl Tauber
08419d6135 TabbedPane:
- added icon-only tab mode, which shows tab icons but hides tab titles
- in "Show Hidden Tabs" popup menu, do not show text "x. Tab" if tab has icon but no title (issue #1062)
2025-11-29 18:11:31 +01:00
Karl Tauber
3a72232ae3 UI defaults dumps updated for commit 4945378dd3 2025-11-29 14:35:38 +01:00
Karl Tauber
e61499e7c6 use AtomicReference for method parameters that return values 2025-11-29 14:31:05 +01:00
Karl Tauber
d3e6c7af14 Styling: TestFlatStyleableValue: use random values for boolean, integer, float, color, insets and dimension to better test whether set and get style work correctly 2025-11-28 00:43:37 +01:00
Karl Tauber
ff11a11d28 Styling: TestFlatStyling: removed component, border and icon tests because they are also tested in TestFlatStyleableValue 2025-11-27 23:42:26 +01:00
Karl Tauber
855c41bf4a Styling: TestFlatStyleableValue:
- check whether all keys (returned by `getStyleableInfos()`) are tested
- added tests for missing keys
- cache `ui.applyStyle(...)` methods
2025-11-27 23:00:29 +01:00
Karl Tauber
2b587d4dba Styling: added missing unit tests 2025-11-27 15:53:25 +01:00
Karl Tauber
5fabfc7051 Styling: dump FlatLaf styleable infos to flatlaf-testing/dumps/styleable-infos.txt 2025-11-27 15:21:03 +01:00
Karl Tauber
db4173dd06 Styling: introduced interface StyleableObject
- has default implementations that use annotations
- replaces `StyleableBorder`
- used for styleable icons
2025-11-26 11:09:43 +01:00
Karl Tauber
03b7a1c29e Styling: added missing unit tests; added missing FlatCapsLockIcon.getStyleableInfos() 2025-11-25 18:51:40 +01:00
Karl Tauber
60968f77eb Styling: replaced all occurrences of
`return new UnknownStyleException( key )`
with
  `throw new UnknownStyleException( key )`
2025-11-25 18:48:15 +01:00
Karl Tauber
8bafa37b4a Merge PR #1060: Styling of wrong icon in FlatRadioButtonUI 2025-11-25 18:24:06 +01:00
Karl Tauber
04602ac227 CheckBox and RadioButton:
- fixed styling of custom icon
- fixed focus width (and preferred size) if using custom icon
- added unit tests
2025-11-25 18:18:22 +01:00
Karl Tauber
02636b260a Merge PR #1051: Zooming API 2025-11-25 12:30:09 +01:00
Karl Tauber
c8e2e78955 Gradle: use configuration cache 2025-11-25 11:51:41 +01:00
Karl Tauber
19c86cf1f7 update to Gradle 9.2.1 2025-11-25 11:36:07 +01:00
daWoife
7ebc1b27c1 Update FlatRadioButtonUI.java
The method applyStyleProperty of class FlatRadioButtonUI currently styles the variable icon, a field of the parent class BasicRadioButtonUI and the default icon which is set with UIManager.getIcon in method installDefaults. 
So the wrong icon is styled if someone subclasses FlatRadioBoxIcon or FlatCheckBoxIcon and sets this as the new icon for a JRadioButton or JCheckBox instance. (An example why someone would do so is shown in issue #413).
With this change styling takes account of the current icon of a JRadioButton or JCheckBox.
2025-11-15 11:37:18 +01:00
Karl Tauber
d4827b6ddf GitHub Actions: cache buildSrc/build to speed up Gradle builds by ~40 seconds 2025-11-01 11:15:46 +01:00
Karl Tauber
02f7cb8972 update to Gradle 9.2.0; always use Java 25 toolchain to generate javadoc
Notes:
- Gradle 9+ requires Java 17 to run
- using Java 21 on GitHub Actions
- not using Java 25 because Kotlin does not yet support it and output some warnings
2025-10-31 23:52:08 +01:00
Karl Tauber
10677d469f flatlaf-natives-windows: fixed link error on GitHub Actions
for some unknown reason (maybe newer Visual C++ version),
MSVC on GitHub Actions no longer inlines methods `wcscpy` and `wcslen`,
which results in linker error:
`error LNK2019: unresolved external symbol wcscpy/wcslen`
2025-10-31 12:01:20 +01:00
Karl Tauber
df8212b49e Gradle:
- default is now Java 8 toolchain (to fix Eclipse project import)
- `src/main/module-info` and `src/main/java9` are compiled with Java 11 toolchain (if global toolchain is Java 8)
- `src/main/module-info` and `src/main/java9` are no longer imported into Eclipse projects
- task `errorprone` now uses at least Java 11 toolchain
2025-10-30 18:59:21 +01:00
Karl Tauber
c583a21bf7 GitHub Actions: moved Error Prone checks to own workflow to reduce build time of CI workflow 2025-10-28 17:47:40 +01:00
Karl Tauber
5263125a04 GitHub Actions: build using various Java versions and publish snapshot in single job to reduce overhead 2025-10-28 17:15:36 +01:00
Karl Tauber
056da35758 Gradle:
- always use Java toolchains
- default is Java 11 (but source/targetCompatibility is still 1.8)
- use system property `toolchain` (e.g. `-Dtoolchain=25`) to compile with other Java versions

preparation for Gradle 9.x, which requires Java 17+ to run
2025-10-28 13:06:40 +01:00
Karl Tauber
3ccaacfb00 Native window decorations: updated com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h for commit 34b19f00e4 2025-10-28 12:27:50 +01:00
Karl Tauber
bdb7438672 Merge PR #988: System File Chooser 2025-10-27 19:16:50 +01:00
Karl Tauber
299250a710 System File Chooser: change @since 3.6 to @since 3.7 2025-10-27 18:06:41 +01:00
Karl Tauber
1e2a75a19c sigtest: fix line separators for file generated by sigtest (default is a mix of cr+lf and lf on Windows) 2025-10-26 19:44:58 +01:00
Karl Tauber
0fb4c811f6 Zooming API 2025-10-26 18:50:24 +01:00
Karl Tauber
0d4946230e JIDE: JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: paint border in button style TOOLBAR_STYLE if in selected state (issue #1045) 2025-10-24 19:50:04 +02:00
Karl Tauber
960f9d86c1 TextField: fixed wrong leading/trailing icon placement if border is set to null (issue #1047) 2025-10-22 14:18:40 +02:00
Karl Tauber
015645e173 UI defaults inspector: exclude window from being blocked by modal dialogs (issue #1048) 2025-10-20 19:32:22 +02:00
lkrieger-oashi
8cfe1ca597 changed: when the mouse wheel event is not in the viewport, propagate the event to the nearest parent
Scenario: A JTabbedPane inside a JScrollPane
Expectation: Moving the mouse wheel inside the TabbedPane's content should cause the outer ScrollPane to scroll
Actual: Event dispatcher targets TabbedPane, but the TabbedPane's mouse listener does nothing because the event is not in the viewport. The only other target is the ancestor (Window), which discards the event.
Fix: If the event does not occur in the viewport, delegate it up in the hierarchy to the nearest parent with a MouseWheelListener
2025-08-20 10:17:01 +02:00
Karl Tauber
1eee35035d Merge main into system-file-chooser 2025-06-23 17:09:19 +02:00
Karl Tauber
5c2d8ba555 System File Chooser: fix crash on macOS 15.x 2025-06-17 12:23:09 +02:00
Karl Tauber
0f27125107 GitHub Actions: natives.yml:
- fixed build issue on ubuntu arm64
- disabled signing on macOS (because it no longer works and I have no idea how to fix it)
2025-06-17 11:32:20 +02:00
Karl Tauber
f2882370de GitHub Actions: natives.yml:
- disabled cross-compile for arm64 architecture on x86_64 Linux
- use `apt-get` instead of `apt`
- use long command line options for `codesign`
2025-06-13 14:33:54 +02:00
Karl Tauber
6715886b24 Merge main into system-file-chooser 2025-06-12 11:29:17 +02:00
Karl Tauber
35e86ba772 System File Chooser: updated all native libraries
built and signed by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/14008769165
2025-03-22 14:24:34 +01:00
Karl Tauber
dade1cba5a System File Chooser:
- introduced state storage
- added "New Folder" to macOS select folder dialog
- Demo: added "Select Folder (System)" menu item
- javadoc fixes
2025-03-22 13:51:16 +01:00
Dar
dcb4c09387 fix: extended support for getBorderArc method
Fixes `FlatUIUtils.getBorderArc()` returning `0` for `FlatLineBorder`, causing background overflow outside rounded corners when a background color is set.
2025-03-21 09:12:24 +01:00
Karl Tauber
3e8b213367 System File Chooser: fixed font in message dialog on Windows 2025-03-20 19:12:10 +01:00
Karl Tauber
202a0d159b GitHub Actions: natives.yml: sign Windows and macOS native libraries 2025-03-18 19:57:38 +01:00
Karl Tauber
5d247f6269 GitHub Actions: natives.yml: include only the core natives that have been built in artefacts 2025-03-17 19:27:47 +01:00
Karl Tauber
d81bcd5254 Merge main into system-file-chooser 2025-03-09 19:20:54 +01:00
Karl Tauber
03e5f8623e update to Gradle 8.12.1 2025-01-27 18:45:50 +01:00
Karl Tauber
54d6959533 System File Chooser:
- always use some window as owner (similar to `JFileChooser`)
- Linux: use "Select" for approve button in directory selection
2025-01-23 19:52:39 +01:00
Karl Tauber
112116556d Merge main into system-file-chooser 2025-01-23 17:04:05 +01:00
Karl Tauber
3283cfe22f System File Chooser: macOS: disable screen menu bar when file dialog is shown
Testing: reduced duplicate code
2025-01-23 00:07:38 +01:00
Karl Tauber
aecb496142 System File Chooser: macOS: show file dialog in dark if current FlatLaf theme is dark 2025-01-21 14:36:01 +01:00
Karl Tauber
1e3e4d7c61 System File Chooser: fixed (cross-)compile native library for ARM64 Linux 2025-01-21 11:28:57 +01:00
Karl Tauber
b808f6e803 System File Chooser: support platform specific features 2025-01-20 19:23:09 +01:00
Karl Tauber
f3ca3a001a System File Chooser: added "approve" callback to SystemFileChooser 2025-01-20 17:20:06 +01:00
Karl Tauber
d524536575 System File Chooser: Linux: cross-compile native library for ARM64 on x86_64 Linux 2025-01-20 16:09:16 +01:00
Karl Tauber
0a4c01cd40 Merge main into system-file-chooser 2025-01-18 17:53:04 +01:00
Karl Tauber
d513ec497b System File Chooser: support system message dialog with custom buttons on Windows (not yet used in SystemFileChooser 2025-01-15 18:51:37 +01:00
Karl Tauber
07fc190b5f Native Libraries: moved code to JNIUtils.cpp and *MessageDialog.cpp 2025-01-14 16:50:13 +01:00
Karl Tauber
078e59a443 System File Chooser: support "approve" callback and system message dialog on macOS (not yet used in SystemFileChooser 2025-01-12 18:32:58 +01:00
Karl Tauber
d49282dfe8 System File Chooser: support "approve" callback and system message dialog on Windows and Linux (not yet used in SystemFileChooser 2025-01-12 01:40:12 +01:00
Karl Tauber
c73fd51704 System File Chooser: support filename extension filters 2025-01-08 18:41:14 +01:00
Karl Tauber
251198c66d Native Libraries:
- made C methods `static` (similar to `private` in Java) to avoid that they are added (exported) to shared library symbol table
- macOS and Linux: added `-fvisibility=hidden` to compiler options to mark C methods hidden by default
2025-01-08 12:59:38 +01:00
Karl Tauber
d7462bd424 System File Chooser: use Cocoa autolayout for "Format" label and combobox on macOS 2025-01-07 19:20:40 +01:00
Karl Tauber
9af7f95197 System File Chooser: added "Format" combobox on macOS (if using more than one filter) 2025-01-07 14:37:58 +01:00
Karl Tauber
2e16ded5d4 System File Chooser: support macOS in class SystemFileChooser 2025-01-06 19:22:15 +01:00
Karl Tauber
91e8d04a9f System File Chooser: introduced class SystemFileChooser as replacement for JFileChooser 2025-01-06 18:01:50 +01:00
Karl Tauber
9453d55abd System File Chooser: fixes for Windows 2025-01-04 12:33:18 +01:00
Karl Tauber
641fada6c4 System File Chooser: implemented modality for GtkFileChooserDialog on Linux 2025-01-04 12:22:14 +01:00
Karl Tauber
a303cd2dec System File Chooser: renamed Windows and macOS test apps 2025-01-03 17:56:02 +01:00
Karl Tauber
2b810addd8 System File Chooser: implemented native bindings for GtkFileChooserDialog on Linux 2025-01-03 16:38:10 +01:00
Karl Tauber
63272a03cf System File Chooser: macOS:
- use `optionsSet` and `optionsClear` (as on Windows)
- delete local reference after getting Java array item
- added "or null" to javadoc
2024-12-31 18:44:06 +01:00
Karl Tauber
49a0a83eca System File Chooser: implemented native bindings for IFileOpenDialog and IFileSaveDialog on Windows 2024-12-31 17:39:44 +01:00
Karl Tauber
516bd80702 System File Chooser: implemented native bindings for NSOpenPanel and NSSavePanel on macOS 2024-12-30 12:46:28 +01:00
148 changed files with 13618 additions and 2750 deletions

View File

@@ -7,6 +7,11 @@ charset = latin1
indent_style = tab
indent_size = 4
[{*.yaml,*.yml}]
end_of_line = lf
indent_style = space
indent_size = 2
[*.java]
indent_style = tab
ij_continuation_indent_size = 4

32
.github/actions/cache-gradle/action.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action#creating-a-composite-action-within-the-same-repository
# cache uses two files:
#
# - gradle-wrapper-<os>-<arch>-<hash>:
# contains the Gradle wrapper/distribution (~/.gradle/wrapper)
# and is updated when the Gradle version changed
# - gradle-caches-<os>-<arch>-<hash>:
# contains the Gradle caches (~/.gradle/caches), buildSrc/build and buildSrc/.gradle
# and is updated when Gradle related files were changed
# buildSrc/.gradle is needed so that buildSrc tasks are UP-TO-DATE
name: 'Cache Gradle'
runs:
using: "composite"
steps:
- name: Cache '.gradle/wrapper'
uses: actions/cache@v4
with:
key: gradle-wrapper-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( 'gradle/wrapper/gradle-wrapper.properties' ) }}
path: |
~/.gradle/wrapper
- name: Cache '.gradle/caches' and 'buildSrc/build'
uses: actions/cache@v4
with:
key: gradle-caches-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( '**/*.gradle*', 'gradle/**', 'gradle.properties', 'buildSrc/src/**' ) }}
path: |
~/.gradle/caches
buildSrc/build
buildSrc/.gradle

View File

@@ -14,13 +14,13 @@ on:
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-natives/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
build:
name: build (11)
runs-on: ubuntu-latest
steps:
@@ -28,20 +28,44 @@ jobs:
- uses: gradle/actions/wrapper-validation@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Check with Error Prone
run: ./gradlew errorprone clean
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Build with Gradle
# test against
# - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...)
# - latest Java version(s)
- name: Build with Java 11 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=11
- name: Build with Java 17 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=17
- name: Build with Java 21 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=21
- name: Build with Java 25 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=25
# build with Java 8 for snapshot
- name: Build with Java 8
run: ./gradlew build
- name: Upload artifacts
- name: Upload artifacts to GitHub Actions
uses: actions/upload-artifact@v4
with:
name: FlatLaf-build-artifacts
@@ -52,59 +76,11 @@ jobs:
!**/*-sources.jar
build-on:
runs-on: ubuntu-latest
needs: build
if: github.repository == 'JFormDesigner/FlatLaf'
strategy:
matrix:
# test against
# - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...)
# - latest Java version(s)
java:
- 8
- 17 # LTS
- 21 # LTS
toolchain: [""]
include:
- java: 21
toolchain: 25 # LTS
steps:
- uses: actions/checkout@v4
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: temurin # Java 8, 11, 17 and 21 are pre-installed on ubuntu-latest
cache: gradle
- name: Build with Gradle
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
snapshot:
runs-on: ubuntu-latest
needs: build-on
if: |
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) &&
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Publish snapshot to Sonatype Central
if: |
github.repository == 'JFormDesigner/FlatLaf' &&
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' ))
run: ./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
@@ -113,7 +89,7 @@ jobs:
release:
runs-on: ubuntu-latest
needs: build-on
needs: build
if: |
github.event_name == 'push' &&
startsWith( github.ref, 'refs/tags/' ) &&
@@ -122,14 +98,16 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Release a new stable version to Maven Central
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Release a new stable version to Maven Central and build demo and theme editor
run: ./gradlew publishToSonatype closeSonatypeStagingRepository :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}

40
.github/workflows/error-prone.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Error Prone
on:
push:
branches:
- '*'
tags:
- '[0-9]*'
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-natives/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
error-prone:
runs-on: ubuntu-latest
if: github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Check with Error Prone
run: ./gradlew errorprone

View File

@@ -34,12 +34,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Build with Gradle
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build

View File

@@ -33,30 +33,99 @@ jobs:
- uses: gradle/actions/wrapper-validation@v4
- name: install libxt-dev
- name: apt update (Linux)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: sudo apt install libxt-dev
run: sudo apt-get update
- name: install g++-aarch64-linux-gnu
if: matrix.os == 'ubuntu-latest'
run: sudo apt install g++-aarch64-linux-gnu
- name: install libxt-dev and libgtk-3-dev (Linux)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: sudo apt-get install libxt-dev libgtk-3-dev
- name: Setup Java 11
# - name: Download libgtk-3.so for arm64 (Linux)
# if: matrix.os == 'ubuntu-latest'
# working-directory: flatlaf-natives/flatlaf-natives-linux/lib/aarch64
# run: |
# pwd
# ls -l /usr/lib/x86_64-linux-gnu/libgtk*
# wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
# ls -l
# ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
# tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
# rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
# ls -l
# - name: install g++-aarch64-linux-gnu (Linux)
# if: matrix.os == 'ubuntu-latest'
# run: sudo apt-get install g++-aarch64-linux-gnu
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin
cache: gradle
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Build with Gradle
# --no-daemon is necessary on Windows otherwise caching Gradle would fail with:
# tar.exe: Couldn't open ~/.gradle/caches/modules-2/modules-2.lock: Permission denied
run: ./gradlew build-natives --no-daemon
- name: Sign Windows DLLs
if: matrix.os == 'windows-latest'
uses: skymatic/code-sign-action@v3
with:
certificate: '${{ secrets.CODE_SIGN_CERT_BASE64 }}'
password: '${{ secrets.CODE_SIGN_CERT_PASSWORD }}'
certificatesha1: '${{ secrets.CODE_SIGN_CERT_SHA1 }}'
folder: 'flatlaf-core/src/main/resources/com/formdev/flatlaf/natives'
- name: Sign macOS natives
if: matrix.os == 'DISABLED--macos-latest'
env:
CERT_BASE64: ${{ secrets.CODE_SIGN_CERT_BASE64 }}
CERT_PASSWORD: ${{ secrets.CODE_SIGN_CERT_PASSWORD }}
CERT_IDENTITY: ${{ secrets.CODE_SIGN_CERT_IDENTITY }}
run: |
# https://docs.github.com/en/actions/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/cert.p12
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
KEYCHAIN_PASSWORD=$CERT_PASSWORD
# decode certificate
printenv CERT_BASE64 | base64 --decode > $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
# set partition list (required for codesign)
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# add keychain to keychain search list
security list-keychains -d user -s $KEYCHAIN_PATH
# sign code
codesign --sign "$CERT_IDENTITY" --force --verbose=4 --timestamp \
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
codesign --display --verbose=4 flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
# cleanup
security delete-keychain $KEYCHAIN_PATH
- name: Set artifacts pattern
shell: bash
run: |
case ${{ matrix.os }} in
windows-latest) echo "artifactPattern=flatlaf-windows-*.dll" >> $GITHUB_ENV ;;
macos-latest) echo "artifactPattern=libflatlaf-macos-*.dylib" >> $GITHUB_ENV ;;
ubuntu-latest) echo "artifactPattern=libflatlaf-linux-x86_64.so" >> $GITHUB_ENV ;;
ubuntu-24.04-arm) echo "artifactPattern=libflatlaf-linux-arm64.so" >> $GITHUB_ENV ;;
esac
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: |
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/${{ env.artifactPattern }}
flatlaf-natives/flatlaf-natives-*/build

View File

@@ -21,12 +21,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Publish PR snapshot to Sonatype Central
run: >

View File

@@ -1,6 +1,62 @@
FlatLaf Change Log
==================
## 3.7.1-SNAPSHOT
- ComboBox: Added UI property `ComboBox.buttonFocusedEditableBackground`. (issue
#1068)
- Dialog: Some client properties (e.g. `JRootPane.titleBarShowTitle`) did not
work before the dialog was made visible. (issue #1081)
- Popup: Fixed scrolling popup painting issue on Windows 10 when a glass pane is
visible and frame is maximized. (issue #1071)
- Slider: Styling `thumbSize` or `focusWidth` did not update slider size/layout.
(PR #1074)
- ToolBar: Grip disappeared when switching between Look and Feels. (issue #1075)
- macOS: Popups (menus and combobox lists) were not always hidden when window is
resized. (issue #1082)
- Extras:
- UI defaults inspector: Fixed NPE if color of `FlatLineBorder` is null. Also
use `FlatLineBorder` line color as cell background color in "Value" column.
(PR #1080)
- `FlatDesktop`: Avoid unnecessary logging if desktop is not supported (e.g.
on NixOS with Plasma/KDE desktop).
## 3.7
#### New features and improvements
- System File Chooser allows using **operating system file dialogs** in Java
Swing applications. (PR #988)
- Zooming API. (PR #1051)
- Icons:
- Support scaling Laf icons (checkbox, radiobutton, etc). (issue #1061)
- Scale checkbox and radiobutton icons when using
[text styles](https://www.formdev.com/flatlaf/typography/#text_styles)
`large`, `medium`, `small` and `mini`.
- TabbedPane: Added icon-only tab mode, which shows tab icons but hides tab
titles. Tab titles are used in "Show Hidden Tabs" popup menu. (set client
property `JTabbedPane.tabWidthMode` to `"iconOnly"`)
- TabbedPane: In scroll tab layout, propagate mouse wheel events to ancestors.
This allows mouse wheel scrolling if JTabbedPane is inside a JScrollPane. (PR
#1030)
#### Fixed bugs
- CheckBox and RadioButton: Fixed styling of custom icon. Also fixed focus width
(and preferred size) if using custom icon. (PR #1060)
- TabbedPane: In "Show Hidden Tabs" popup menu, do not show text "x. Tab" if tab
has icon but no title. (issue #1062)
- TextField: Fixed wrong leading/trailing icon placement if border is set to
`null`. (issue #1047)
- Extras: UI defaults inspector: Exclude inspector window from being blocked by
modal dialogs. (issue #1048)
- JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: Paint
border in button style `TOOLBAR_STYLE` if in selected state. (issue #1045)
- IntelliJ Themes: Fixed problem when using theme instance more than once when
switching to that theme. (issue #990)
## 3.6.2
#### New features and improvements

View File

@@ -76,10 +76,16 @@ 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)
See also
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
for instructions on how to redistribute FlatLaf native libraries with your
application.
- See
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
for instructions on how to redistribute FlatLaf native libraries with your
application.
- If repackaging FlatLaf (and other) JARs into a single fat/uber JAR:
- add `Multi-Release: true` to `META-INF/MANIFEST.MF`
- keep `META-INF/versions/` and `META-INF/services/` directories
- merge content of equally named files in `META-INF/services/`
- If using obfuscation/minimizing/shrinking tools (e.g. **ProGuard** or
**Shadow**), exclude package `com.formdev.flatlaf` and all sub-packages.
### Snapshots

View File

@@ -14,8 +14,12 @@
* limitations under the License.
*/
import io.github.gradlenexus.publishplugin.CloseNexusStagingRepository
import net.ltgt.gradle.errorprone.errorprone
import org.gradle.kotlin.dsl.withType
// initialize version
group = "com.formdev"
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
@@ -24,18 +28,16 @@ val pullRequestNumber = findProperty( "github.event.pull_request.number" )
if( pullRequestNumber != null )
version = "PR-${pullRequestNumber}-SNAPSHOT"
allprojects {
// apply version to all subprojects
subprojects {
version = rootProject.version
repositories {
mavenCentral()
}
}
// check required Java version
if( JavaVersion.current() < JavaVersion.VERSION_1_8 )
throw RuntimeException( "Java 8 or later required (running ${System.getProperty( "java.version" )})" )
// initialize toolchain version (default is Java 8)
val toolchainJavaVersion: String by extra {
System.getProperty( "toolchain", "8" )
}
// log version, Gradle and Java versions
println()
@@ -43,9 +45,7 @@ println( "----------------------------------------------------------------------
println( "FlatLaf Version: ${version}" )
println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" )
println( "Java ${System.getProperty( "java.version" )}" )
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() )
println( "Java toolchain ${toolchainJavaVersion}" )
println( "Java toolchain ${toolchainJavaVersion}" )
println()
@@ -55,6 +55,10 @@ plugins {
}
allprojects {
repositories {
mavenCentral()
}
tasks {
withType<JavaCompile>().configureEach {
sourceCompatibility = "1.8"
@@ -78,6 +82,10 @@ allprojects {
}
}
withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = true
}
withType<Javadoc>().configureEach {
options {
this as StandardJavadocDocletOptions
@@ -90,6 +98,23 @@ allprojects {
links( "https://docs.oracle.com/en/java/javase/11/docs/api/" )
}
isFailOnError = false
// use Java 25 to generate javadoc
val javaToolchains = (project as ExtensionAware).extensions.getByName("javaToolchains") as JavaToolchainService
javadocTool.set( javaToolchains.javadocToolFor {
languageVersion.set( JavaLanguageVersion.of( 25 ) )
} )
}
// mark some publishing related tasks as not compatible with configuration cache
withType<Sign>().configureEach {
notCompatibleWithConfigurationCache( "not compatible" )
}
withType<PublishToMavenRepository>().configureEach {
notCompatibleWithConfigurationCache( "not compatible" )
}
withType<CloseNexusStagingRepository>().configureEach {
notCompatibleWithConfigurationCache( "not compatible" )
}
}
@@ -141,6 +166,15 @@ allprojects {
)
}
}
// Error Prone requires at lease Java 11
val java = (project as ExtensionAware).extensions.getByName("java") as JavaPluginExtension
val javaToolchains = (project as ExtensionAware).extensions.getByName("javaToolchains") as JavaToolchainService
if( java.toolchain.languageVersion.get().asInt() < 11 ) {
javaCompiler.set( javaToolchains.compilerFor {
languageVersion.set( JavaLanguageVersion.of( 11 ) )
} )
}
}
}
}

View File

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

View File

@@ -18,7 +18,14 @@ plugins {
java
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
// for Eclipse IDE project import, exclude if:
// - plugin "eclipse" is applied; e.g. if running in Eclipse IDE with buildship plugin
// - no taskNames specified at command line; e.g. if buildship synchronizes projects
val exclude =
rootProject.plugins.hasPlugin( "eclipse" ) &&
gradle.startParameter.taskNames.isEmpty()
if( !exclude ) {
sourceSets {
create( "java9" ) {
java {
@@ -35,6 +42,13 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
named<JavaCompile>( "compileJava9Java" ) {
sourceCompatibility = "9"
targetCompatibility = "9"
// if global toolchain is Java 8, then use Java 11 to build
if( java.toolchain.languageVersion.get().asInt() < 9 ) {
javaCompiler.set( javaToolchains.compilerFor {
languageVersion.set( JavaLanguageVersion.of( 11 ) )
} )
}
}
jar {

View File

@@ -29,7 +29,14 @@ plugins {
java
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
// for Eclipse IDE project import, exclude if:
// - plugin "eclipse" is applied; e.g. if running in Eclipse IDE with buildship plugin
// - no taskNames specified at command line; e.g. if buildship synchronizes projects
val exclude =
rootProject.plugins.hasPlugin( "eclipse" ) &&
gradle.startParameter.taskNames.isEmpty()
if( !exclude ) {
sourceSets {
create( "module-info" ) {
java {
@@ -38,10 +45,11 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
setSrcDirs( listOf( "src/main/module-info", "src/main/java", "src/main/java9" ) )
// exclude Java 8 source file if an equally named Java 9+ source file exists
val projectDir = projectDir // necessary for configuration cache
exclude {
if( it.isDirectory )
return@exclude false
val java9file = file( "${projectDir}/src/main/java9/${it.path}" )
val java9file = File( "${projectDir}/src/main/java9/${it.path}" )
java9file.exists() && java9file != it.file
}
}
@@ -58,6 +66,13 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
options.compilerArgs.add( "--module-path" )
options.compilerArgs.add( configurations.runtimeClasspath.get().asPath
+ File.pathSeparator + configurations.compileClasspath.get().asPath )
// if global toolchain is Java 8, then use Java 11 to build
if( java.toolchain.languageVersion.get().asInt() < 9 ) {
javaCompiler.set( javaToolchains.compilerFor {
languageVersion.set( JavaLanguageVersion.of( 11 ) )
} )
}
}
jar {

View File

@@ -15,7 +15,7 @@
*/
open class NativeArtifact( val fileName: String, val classifier: String, val type: String ) {}
open class NativeArtifact( val fileName: String, val classifier: String, val extension: String ) {}
open class PublishExtension {
var artifactId: String? = null
@@ -77,35 +77,14 @@ publishing {
afterEvaluate {
extension.nativeArtifacts?.forEach {
artifact( artifacts.add( "archives", file( it.fileName ) ) {
artifact( file( it.fileName ) ) {
classifier = it.classifier
type = it.type
} )
extension = it.extension
}
}
}
}
}
/*
repositories {
maven {
name = "MavenCentral"
val releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/"
url = uri( if( rootProject.hasProperty( "release" ) ) releasesRepoUrl else snapshotsRepoUrl )
credentials {
// get from gradle.properties
val sonatypeUsername: String? by project
val sonatypePassword: String? by project
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
}
}
}
*/
}
signing {
@@ -125,10 +104,22 @@ tasks.withType<Sign>().configureEach {
onlyIf { rootProject.hasProperty( "release" ) }
}
// check whether parallel build is enabled
tasks.withType<AbstractPublishToMaven>().configureEach {
doFirst {
if( System.getProperty( "org.gradle.parallel" ) == "true" )
throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )
tasks {
// check whether parallel build is enabled
withType<AbstractPublishToMaven>().configureEach {
doFirst {
if( System.getProperty( "org.gradle.parallel" ) == "true" )
throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )
}
}
register( "publishToSonatypeAndCloseStagingRepo" ) {
group = "publishing"
description = "Publish to Sonatype Maven Central and close staging repository"
dependsOn(
"publishToSonatype",
":closeSonatypeStagingRepository"
)
}
}

View File

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

View File

@@ -90,7 +90,7 @@ tasks {
useJUnitPlatform()
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
if( java.toolchain.languageVersion.get().asInt() >= 9 )
jvmArgs( listOf( "--add-opens", "java.desktop/javax.swing.plaf.basic=ALL-UNNAMED" ) )
}
@@ -98,21 +98,31 @@ tasks {
group = "verification"
dependsOn( "jar" )
// necessary for configuration cache
val classpath = sigtest.asPath
val signatureFile = "${project.name}-sigtest.txt"
val jarPath = jar.get().outputs.files.asPath
val version = version
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"classpath" to classpath )
"sigtest"(
"action" to "generate",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"fileName" to signatureFile,
"classpath" to jarPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.themes,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
"failonerror" to "true" )
"fixcrlf"(
"file" to signatureFile,
"eol" to "lf" )
}
}
}
@@ -121,17 +131,23 @@ tasks {
group = "verification"
dependsOn( "jar" )
// necessary for configuration cache
val classpath = sigtest.asPath
val signatureFile = "${project.name}-sigtest.txt"
val jarPath = jar.get().outputs.files.asPath
val version = version
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"classpath" to classpath )
"sigtest"(
"action" to "check",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"fileName" to signatureFile,
"classpath" to jarPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version

View File

@@ -1,5 +1,5 @@
#Signature file v4.1
#Version 3.6.2
#Version 3.7
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -87,6 +87,7 @@ fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "unde
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_EQUAL = "equal"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY = "iconOnly"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_PREFERRED = "preferred"
fld public final static java.lang.String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent"
fld public final static java.lang.String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"
@@ -307,6 +308,7 @@ fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "fla
fld public final static java.lang.String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
fld public final static java.lang.String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder"
fld public final static java.lang.String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle"
fld public final static java.lang.String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser"
fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection"
fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"
@@ -770,6 +772,112 @@ cons public init()
meth public static <%0 extends java.awt.Component> {%%0} getComponentByName(java.awt.Container,java.lang.String)
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.SystemFileChooser
cons public init()
cons public init(java.io.File)
cons public init(java.lang.String)
fld public final static int APPROVE_OPTION = 0
fld public final static int CANCEL_OPTION = 1
fld public final static int DIRECTORIES_ONLY = 1
fld public final static int FILES_ONLY = 0
fld public final static int OPEN_DIALOG = 0
fld public final static int SAVE_DIALOG = 1
fld public final static java.lang.String LINUX_OPTIONS_CLEAR = "linux.optionsClear"
fld public final static java.lang.String LINUX_OPTIONS_SET = "linux.optionsSet"
fld public final static java.lang.String MAC_FILTER_FIELD_LABEL = "mac.filterFieldLabel"
fld public final static java.lang.String MAC_MESSAGE = "mac.message"
fld public final static java.lang.String MAC_NAME_FIELD_LABEL = "mac.nameFieldLabel"
fld public final static java.lang.String MAC_OPTIONS_CLEAR = "mac.optionsClear"
fld public final static java.lang.String MAC_OPTIONS_SET = "mac.optionsSet"
fld public final static java.lang.String MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES = "mac.treatsFilePackagesAsDirectories"
fld public final static java.lang.String WINDOWS_DEFAULT_EXTENSION = "windows.defaultExtension"
fld public final static java.lang.String WINDOWS_DEFAULT_FOLDER = "windows.defaultFolder"
fld public final static java.lang.String WINDOWS_FILE_NAME_LABEL = "windows.fileNameLabel"
fld public final static java.lang.String WINDOWS_OPTIONS_CLEAR = "windows.optionsClear"
fld public final static java.lang.String WINDOWS_OPTIONS_SET = "windows.optionsSet"
innr public abstract interface static ApproveCallback
innr public abstract interface static StateStore
innr public abstract static ApproveContext
innr public abstract static FileFilter
innr public final static FileNameExtensionFilter
meth public <%0 extends java.lang.Object> {%%0} getPlatformProperty(java.lang.String)
meth public boolean isAcceptAllFileFilterUsed()
meth public boolean isDirectorySelectionEnabled()
meth public boolean isFileHidingEnabled()
meth public boolean isFileSelectionEnabled()
meth public boolean isMultiSelectionEnabled()
meth public boolean removeChoosableFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
meth public com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback getApproveCallback()
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter getAcceptAllFileFilter()
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter getFileFilter()
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter[] getChoosableFileFilters()
meth public int getApproveButtonMnemonic()
meth public int getDialogType()
meth public int getFileSelectionMode()
meth public int showDialog(java.awt.Component,java.lang.String)
meth public int showOpenDialog(java.awt.Component)
meth public int showSaveDialog(java.awt.Component)
meth public java.io.File getCurrentDirectory()
meth public java.io.File getSelectedFile()
meth public java.io.File[] getSelectedFiles()
meth public java.lang.String getApproveButtonText()
meth public java.lang.String getDialogTitle()
meth public java.lang.String getStateStoreID()
meth public static com.formdev.flatlaf.util.SystemFileChooser$StateStore getStateStore()
meth public static void setStateStore(com.formdev.flatlaf.util.SystemFileChooser$StateStore)
meth public void addChoosableFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
meth public void putPlatformProperty(java.lang.String,java.lang.Object)
meth public void resetChoosableFileFilters()
meth public void setAcceptAllFileFilterUsed(boolean)
meth public void setApproveButtonMnemonic(char)
meth public void setApproveButtonMnemonic(int)
meth public void setApproveButtonText(java.lang.String)
meth public void setApproveCallback(com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback)
meth public void setCurrentDirectory(java.io.File)
meth public void setDialogTitle(java.lang.String)
meth public void setDialogType(int)
meth public void setFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
meth public void setFileHidingEnabled(boolean)
meth public void setFileSelectionMode(int)
meth public void setMultiSelectionEnabled(boolean)
meth public void setSelectedFile(java.io.File)
meth public void setSelectedFiles(java.io.File[])
meth public void setStateStoreID(java.lang.String)
supr java.lang.Object
hfds acceptAllFileFilter,approveButtonMnemonic,approveButtonText,approveCallback,approveResult,currentDirectory,dialogTitle,dialogType,fileFilter,fileSelectionMode,filters,inMemoryStateStore,keepAcceptAllAtEnd,multiSelection,platformProperties,selectedFile,selectedFiles,stateStore,stateStoreID,useAcceptAllFileFilter,useFileHiding
hcls AcceptAllFileFilter,FileChooserProvider,LinuxFileChooserProvider,MacFileChooserProvider,SwingFileChooserProvider,SystemFileChooserProvider,WindowsFileChooserProvider
CLSS public abstract interface static com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback
outer com.formdev.flatlaf.util.SystemFileChooser
meth public abstract int approve(java.io.File[],com.formdev.flatlaf.util.SystemFileChooser$ApproveContext)
CLSS public abstract static com.formdev.flatlaf.util.SystemFileChooser$ApproveContext
outer com.formdev.flatlaf.util.SystemFileChooser
cons public init()
meth public abstract !varargs int showMessageDialog(int,java.lang.String,java.lang.String,int,java.lang.String[])
supr java.lang.Object
CLSS public abstract static com.formdev.flatlaf.util.SystemFileChooser$FileFilter
outer com.formdev.flatlaf.util.SystemFileChooser
cons public init()
meth public abstract java.lang.String getDescription()
supr java.lang.Object
CLSS public final static com.formdev.flatlaf.util.SystemFileChooser$FileNameExtensionFilter
outer com.formdev.flatlaf.util.SystemFileChooser
cons public !varargs init(java.lang.String,java.lang.String[])
meth public java.lang.String getDescription()
meth public java.lang.String toString()
meth public java.lang.String[] getExtensions()
supr com.formdev.flatlaf.util.SystemFileChooser$FileFilter
hfds description,extensions
CLSS public abstract interface static com.formdev.flatlaf.util.SystemFileChooser$StateStore
outer com.formdev.flatlaf.util.SystemFileChooser
fld public final static java.lang.String KEY_CURRENT_DIRECTORY = "currentDirectory"
meth public abstract java.lang.String get(java.lang.String,java.lang.String)
meth public abstract void put(java.lang.String,java.lang.String)
CLSS public com.formdev.flatlaf.util.SystemInfo
cons public init()
fld public final static boolean isAARCH64
@@ -806,13 +914,21 @@ supr java.lang.Object
CLSS public com.formdev.flatlaf.util.UIScale
cons public init()
fld public final static java.lang.String PROP_USER_SCALE_FACTOR = "userScaleFactor"
fld public final static java.lang.String PROP_ZOOM_FACTOR = "zoomFactor"
meth public static boolean isSystemScalingEnabled()
meth public static boolean setZoomFactor(float)
meth public static boolean zoomIn()
meth public static boolean zoomOut()
meth public static boolean zoomReset()
meth public static double getSystemScaleFactor(java.awt.Graphics2D)
meth public static double getSystemScaleFactor(java.awt.GraphicsConfiguration)
meth public static float computeFontScaleFactor(java.awt.Font)
meth public static float getUserScaleFactor()
meth public static float getZoomFactor()
meth public static float scale(float)
meth public static float unscale(float)
meth public static float[] getSupportedZoomFactors()
meth public static int scale(int)
meth public static int scale2(int)
meth public static int unscale(int)
@@ -822,8 +938,9 @@ meth public static javax.swing.plaf.FontUIResource applyCustomScaleFactor(javax.
meth public static void addPropertyChangeListener(java.beans.PropertyChangeListener)
meth public static void removePropertyChangeListener(java.beans.PropertyChangeListener)
meth public static void scaleGraphics(java.awt.Graphics2D)
meth public static void setSupportedZoomFactors(float[])
supr java.lang.Object
hfds DEBUG,changeSupport,initialized,jreHiDPI,scaleFactor
hfds DEBUG,changeSupport,ignoreFontChange,inUnitTests,initialized,jreHiDPI,listenerInitialized,scaleFactor,supportedZoomFactors,unzoomedScaleFactor,zoomFactor
CLSS public java.awt.Color
cons public init(float,float,float)

View File

@@ -1086,8 +1086,9 @@ public interface FlatClientProperties
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #TABBED_PANE_TAB_WIDTH_MODE_PREFERRED} (default),
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL},
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT} or
* {@link #TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY}
*/
String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode";
@@ -1113,6 +1114,14 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact";
/**
* All tabs are smaller because they show only the tab icon, but no tab title.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
* @since 3.7
*/
String TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY = "iconOnly";
/**
* Specifies the tab icon placement (relative to tab title).
* <p>

View File

@@ -310,8 +310,8 @@ public abstract class FlatLaf
// install submenu usability helper
subMenuUsabilityHelperInstalled = SubMenuUsabilityHelper.install();
// install Linux popup menu canceler
if( SystemInfo.isLinux )
// install Linux/macOS popup menu canceler
if( SystemInfo.isLinux || SystemInfo.isMacOS )
linuxPopupMenuCanceler = new LinuxPopupMenuCanceler();
// listen to desktop property changes to update UI if system font or scaling changes
@@ -368,6 +368,22 @@ public abstract class FlatLaf
String.format( "a, address { color: #%06x; }", linkColor.getRGB() & 0xffffff ) );
}
};
// Initialize UIScale user scale factor immediately after FlatLaf was activated,
// which is necessary to ensure that UIScale.setZoomFactor(float)
// scales FlatLaf defaultDont correctly even if UIScale.scale() was not yet used.
// In other words: Without this, UIScale.setZoomFactor(float) would
// not work correctly if invoked between FlatLaf.setup() and crating UI.
PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
UIManager.removePropertyChangeListener( this );
UIScale.getUserScaleFactor();
}
}
};
UIManager.addPropertyChangeListener( listener );
}
@Override
@@ -707,11 +723,22 @@ public abstract class FlatLaf
uiFont = ((ActiveFont)defaultFont).derive( baseFont, fontSize -> {
return Math.round( fontSize * UIScale.computeFontScaleFactor( baseFont ) );
} );
}
} else if( defaultFont instanceof LazyValue )
uiFont = ActiveFont.toUIResource( (Font) ((LazyValue)defaultFont).createValue( defaults ) );
// increase font size if system property "flatlaf.uiScale" is set
uiFont = UIScale.applyCustomScaleFactor( uiFont );
// apply zoom factor to font size
float zoomFactor = UIScale.getZoomFactor();
if( zoomFactor != 1 ) {
// see also UIScale.setZoomFactor()
int unzoomedFontSize = uiFont.getSize();
defaults.put( "defaultFont.unzoomedSize", unzoomedFontSize );
int newFontSize = Math.max( Math.round( unzoomedFontSize * zoomFactor ), 1 );
uiFont = new FontUIResource( uiFont.deriveFont( (float) newFontSize ) );
}
// set default font
defaults.put( "defaultFont", uiFont );
}
@@ -1768,7 +1795,7 @@ public abstract class FlatLaf
return toUIResource( baseFont );
}
private FontUIResource toUIResource( Font font ) {
private static FontUIResource toUIResource( Font font ) {
// make sure that font is a UIResource for LaF switching
return (font instanceof FontUIResource)
? (FontUIResource) font

View File

@@ -16,8 +16,10 @@
package com.formdev.flatlaf;
import javax.swing.JFileChooser;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.util.SystemFileChooser;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -246,6 +248,17 @@ public interface FlatSystemProperties
*/
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
/**
* Specifies whether {@link SystemFileChooser} uses operating system file dialogs.
* If set to {@code false}, the {@link JFileChooser} is used instead.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*
* @since 3.7
*/
String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser";
/**
* Checks whether a system property is set and returns {@code true} if its value
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.

View File

@@ -58,9 +58,9 @@ public class IntelliJTheme
public final boolean dark;
public final String author;
private Map<String, String> jsonColors;
private Map<String, Object> jsonUI;
private Map<String, Object> jsonIcons;
private final Map<String, String> jsonColors;
private final Map<String, Object> jsonUI;
private final Map<String, Object> jsonIcons;
private Map<String, String> namedColors = Collections.emptyMap();
@@ -276,11 +276,6 @@ public class IntelliJTheme
put( properties, key, value );
}
// let Java release memory
jsonColors = null;
jsonUI = null;
jsonIcons = null;
}
private String get( Properties properties, Map<String, String> themeSpecificProps, String key ) {

View File

@@ -30,7 +30,7 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Cancels (hides) popup menus on Linux.
* Cancels (hides) popup menus on Linux and macOS.
* <p>
* On Linux, popups are not hidden under following conditions, which results in
* misplaced popups:
@@ -41,7 +41,13 @@ import javax.swing.event.ChangeListener;
* <li>window deactivated (e.g. activated other application)
* </ul>
*
* On Windows and macOS, popups are automatically hidden.
* On macOS, popups are usually automatically hidden, but not always.
* When resizing a window, then it depends where clicking to start resizing (and on the Java version).
* E.g. with Java 25, clicking at bottom-right corner inside of the window does not hide the popups.
* But clicking on same corner outside of the window, hides the popup.
*
* <p>
* On Windows, popups are automatically hidden.
* <p>
* The implementation is similar to what's done in
* {@code javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber},

View File

@@ -45,6 +45,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.swing.Icon;
@@ -431,7 +432,7 @@ class UIDefaultsLoader
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, INTEGERORFLOAT, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, FONT,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
private static final ValueType[] tempResultValueType = new ValueType[1];
private static final AtomicReference<ValueType> tempResultValueType = new AtomicReference<>();
private static Map<Class<?>, ValueType> javaValueTypes;
private static Map<String, ValueType> knownValueTypes;
@@ -441,7 +442,7 @@ class UIDefaultsLoader
return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() );
}
static Object parseValue( String key, String value, Class<?> javaValueType, ValueType[] resultValueType,
static Object parseValue( String key, String value, Class<?> javaValueType, AtomicReference<ValueType> resultValueType,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
throws IllegalArgumentException
{
@@ -450,7 +451,7 @@ class UIDefaultsLoader
// do not parse styles here
if( key.startsWith( "[style]" ) ) {
resultValueType[0] = ValueType.STRING;
resultValueType.set( ValueType.STRING );
return value;
}
@@ -458,7 +459,7 @@ class UIDefaultsLoader
// null
if( value.equals( "null" ) || value.isEmpty() ) {
resultValueType[0] = ValueType.NULL;
resultValueType.set( ValueType.NULL );
return null;
}
@@ -514,14 +515,14 @@ class UIDefaultsLoader
} else {
// false, true
switch( value ) {
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
case "false": resultValueType.set( ValueType.BOOLEAN ); return false;
case "true": resultValueType.set( ValueType.BOOLEAN ); return true;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
resultValueType.set( ValueType.LAZY );
String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 );
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
@@ -602,7 +603,7 @@ class UIDefaultsLoader
}
}
resultValueType[0] = valueType;
resultValueType.set( valueType );
// parse value
switch( valueType ) {
@@ -629,14 +630,14 @@ class UIDefaultsLoader
default:
// string
if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
resultValueType[0] = ValueType.STRING;
resultValueType.set( ValueType.STRING );
return value.substring( 1, value.length() - 1 );
}
// colors
if( value.startsWith( "#" ) || value.endsWith( ")" ) ) {
Object color = parseColorOrFunction( value, resolver );
resultValueType[0] = (color != null) ? ValueType.COLOR : ValueType.NULL;
resultValueType.set( (color != null) ? ValueType.COLOR : ValueType.NULL );
return color;
}
@@ -648,7 +649,7 @@ class UIDefaultsLoader
// integer
try {
Integer integer = parseInteger( value );
resultValueType[0] = ValueType.INTEGER;
resultValueType.set( ValueType.INTEGER );
return integer;
} catch( NumberFormatException ex ) {
// ignore
@@ -657,7 +658,7 @@ class UIDefaultsLoader
// float
try {
Float f = parseFloat( value );
resultValueType[0] = ValueType.FLOAT;
resultValueType.set( ValueType.FLOAT );
return f;
} catch( NumberFormatException ex ) {
// ignore
@@ -665,7 +666,7 @@ class UIDefaultsLoader
}
// string
resultValueType[0] = ValueType.STRING;
resultValueType.set( ValueType.STRING );
return value;
}
}

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.util.UIScale.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
@@ -29,7 +28,7 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Base class for icons that scales width and height, creates and initializes
* a scaled graphics context for icon painting.
*
* <p>
* Subclasses do not need to scale icon painting.
*
* @author Karl Tauber
@@ -37,10 +36,15 @@ import com.formdev.flatlaf.util.UIScale;
public abstract class FlatAbstractIcon
implements Icon, UIResource
{
/** Unscaled icon width. */
protected final int width;
/** Unscaled icon height. */
protected final int height;
protected Color color;
/** Additional icon scale factor. */
private float scale = 1;
public FlatAbstractIcon( int width, int height, Color color ) {
this.width = width;
this.height = height;
@@ -61,6 +65,9 @@ public abstract class FlatAbstractIcon
g2.translate( x, y );
UIScale.scaleGraphics( g2 );
float scale = getScale();
if( scale != 1 )
g2.scale( scale, scale );
if( color != null )
g2.setColor( color );
@@ -71,19 +78,71 @@ public abstract class FlatAbstractIcon
}
}
/** @since 3.5.2 */
/**
* Paints icon background. Default implementation does nothing.
* Can be overridden to paint specific icon background.
* <p>
* The bounds of the area to be filled are:
* x, y, {@link #getIconWidth()}, {@link #getIconHeight()}.
* <p>
* In contrast to {@link #paintIcon(Component, Graphics2D)},
* the graphics context {@code g} is not translated and not scaled.
*
* @since 3.5.2
*/
protected void paintBackground( Component c, Graphics2D g, int x, int y ) {
}
/**
* Paints icon.
* <p>
* The graphics context is translated and scaled.
* This means that icon x,y coordinates are {@code 0,0}
* and it is not necessary to scale coordinates within this method.
* <p>
* The bounds to be used for icon painting are:
* 0, 0, {@link #width}, {@link #height}.
*/
protected abstract void paintIcon( Component c, Graphics2D g );
/**
* Returns the scaled icon width.
*/
@Override
public int getIconWidth() {
return scale( width );
return scale( UIScale.scale( width ) );
}
/**
* Returns the scaled icon height.
*/
@Override
public int getIconHeight() {
return scale( height );
return scale( UIScale.scale( height ) );
}
/** @since 3.7 */
public float getScale() {
return scale;
}
/** @since 3.7 */
public void setScale( float scale ) {
this.scale = scale;
}
/**
* Multiplies the given value by the icon scale factor {@link #getScale()} and rounds the result.
* <p>
* If you want scale a {@code float} or {@code double} value,
* simply use: {@code myFloatValue * }{@link #getScale()}.
* <p>
* Do not use this method when painting icon in {@link #paintIcon(Component, Graphics2D)}.
*
* @since 3.7
*/
protected int scale( int size ) {
float scale = getScale();
return (scale == 1) ? size : Math.round( size * scale );
}
}

View File

@@ -17,7 +17,6 @@
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
@@ -26,7 +25,8 @@ import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -36,8 +36,11 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="capsLockIconScale", fieldName="scale" )
@StyleableField( cls=FlatAbstractIcon.class, key="capsLockIconColor", fieldName="color" )
public class FlatCapsLockIcon
extends FlatAbstractIcon
implements StyleableObject
{
private Path2D path;
@@ -45,23 +48,6 @@ public class FlatCapsLockIcon
super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
case "capsLockIconColor": oldValue = color; color = (Color) value; return oldValue;
default: throw new UnknownStyleException( key );
}
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
switch( key ) {
case "capsLockIconColor": return color;
default: return null;
}
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*

View File

@@ -24,13 +24,13 @@ import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -101,8 +101,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatCheckBoxIcon
extends FlatAbstractIcon
implements StyleableObject
{
protected final String style = UIManager.getString( getPropertyPrefix() + "icon.style" );
@Styleable protected float focusWidth = getUIFloat( "CheckBox.icon.focusWidth", UIManager.getInt( "Component.focusWidth" ), style );
@@ -197,21 +199,6 @@ public class FlatCheckBoxIcon
super( ICON_SIZE, ICON_SIZE, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
boolean indeterminate = isIndeterminate( c );
@@ -306,7 +293,7 @@ public class FlatCheckBoxIcon
/** @since 2 */
public float getFocusWidth() {
return focusWidth;
return focusWidth * getScale();
}
protected Color getFocusColor( Component c ) {

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,8 +38,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatCheckBoxMenuItemIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Color checkmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.checkmarkColor" );
@Styleable protected Color disabledCheckmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.disabledCheckmarkColor" );
@@ -49,21 +51,6 @@ public class FlatCheckBoxMenuItemIcon
super( 15, 15, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
boolean selected = (c instanceof AbstractButton) && ((AbstractButton)c).isSelected();

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,8 +39,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @author Karl Tauber
* @since 1.5
*/
@StyleableField( cls=FlatAbstractIcon.class, key="clearIconScale", fieldName="scale" )
public class FlatClearIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" );
@Styleable protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" );
@@ -58,21 +60,6 @@ public class FlatClearIcon
this.ignoreButtonState = ignoreButtonState;
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( !ignoreButtonState && c instanceof AbstractButton ) {

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.util.UIScale.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
@@ -24,11 +23,12 @@ import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.UIScale;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -52,8 +52,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatHelpButtonIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
@@ -76,21 +78,6 @@ public class FlatHelpButtonIcon
super( 0, 0, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
/*
@@ -167,12 +154,12 @@ public class FlatHelpButtonIcon
@Override
public int getIconWidth() {
return scale( iconSize() );
return scale( UIScale.scale( iconSize() ) );
}
@Override
public int getIconHeight() {
return scale( iconSize() );
return scale( UIScale.scale( iconSize() ) );
}
private int iconSize() {

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,8 +39,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatMenuArrowIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected String arrowType = UIManager.getString( "Component.arrowType" );
@Styleable protected Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
@@ -51,21 +53,6 @@ public class FlatMenuArrowIcon
super( 6, 10, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( c != null && !c.getComponentOrientation().isLeftToRight() )

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,8 +38,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @author Karl Tauber
* @since 1.5
*/
@StyleableField( cls=FlatAbstractIcon.class, key="searchIconScale", fieldName="scale" )
public class FlatSearchIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" );
@Styleable protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" );
@@ -58,21 +60,6 @@ public class FlatSearchIcon
this.ignoreButtonState = ignoreButtonState;
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*

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,8 +46,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="closeScale", fieldName="scale" )
public class FlatTabbedPaneCloseIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Dimension closeSize = UIManager.getDimension( "TabbedPane.closeSize" );
@Styleable protected int closeArc = UIManager.getInt( "TabbedPane.closeArc" );
@@ -65,21 +67,6 @@ public class FlatTabbedPaneCloseIcon
super( 16, 16, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background

View File

@@ -23,7 +23,6 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.util.Map;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
@@ -32,7 +31,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.DerivedColor;
/**
@@ -67,7 +66,7 @@ import com.formdev.flatlaf.util.DerivedColor;
*/
public class FlatBorder
extends BasicBorders.MarginBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
@@ -92,24 +91,6 @@ public class FlatBorder
/** @since 2 */ @Styleable protected Color outlineColor;
/** @since 2 */ @Styleable protected Color outlineFocusedColor;
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();

View File

@@ -58,8 +58,8 @@ import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -369,8 +369,8 @@ public class FlatButtonUI
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
if( key.startsWith( "help." ) ) {
if( !(helpButtonIcon instanceof FlatHelpButtonIcon) )
return new UnknownStyleException( key );
if( !(helpButtonIcon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( helpButtonIconShared ) {
helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon );
@@ -378,7 +378,7 @@ public class FlatButtonUI
}
key = key.substring( "help.".length() );
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
return ((StyleableObject)helpButtonIcon).applyStyleProperty( key, value );
}
// update internal values; otherwise isCustomBackground() and isCustomForeground() would return wrong results
@@ -399,8 +399,8 @@ public class FlatButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this, c.getBorder() );
if( helpButtonIcon instanceof FlatHelpButtonIcon )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((FlatHelpButtonIcon)helpButtonIcon).getStyleableInfos() );
if( helpButtonIcon instanceof StyleableObject )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((StyleableObject)helpButtonIcon).getStyleableInfos() );
return infos;
}
@@ -408,8 +408,8 @@ public class FlatButtonUI
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.startsWith( "help." ) ) {
return (helpButtonIcon instanceof FlatHelpButtonIcon)
? ((FlatHelpButtonIcon)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
return (helpButtonIcon instanceof StyleableObject)
? ((StyleableObject)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
: null;
}

View File

@@ -113,6 +113,7 @@ import com.formdev.flatlaf.util.SystemInfo;
* @uiDefault ComboBox.buttonBackground Color optional
* @uiDefault ComboBox.buttonEditableBackground Color optional
* @uiDefault ComboBox.buttonFocusedBackground Color optional; defaults to ComboBox.focusedBackground
* @uiDefault ComboBox.buttonFocusedEditableBackground Color optional; defaults to ComboBox.buttonEditableBackground
* @uiDefault ComboBox.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
* @uiDefault ComboBox.buttonSeparatorColor Color optional
* @uiDefault ComboBox.buttonDisabledSeparatorColor Color optional
@@ -147,6 +148,7 @@ public class FlatComboBoxUI
@Styleable protected Color buttonBackground;
@Styleable protected Color buttonEditableBackground;
@Styleable protected Color buttonFocusedBackground;
/** @since 3.7.1 */ @Styleable protected Color buttonFocusedEditableBackground;
/** @since 2 */ @Styleable protected float buttonSeparatorWidth;
/** @since 2 */ @Styleable protected Color buttonSeparatorColor;
/** @since 2 */ @Styleable protected Color buttonDisabledSeparatorColor;
@@ -258,6 +260,7 @@ public class FlatComboBoxUI
buttonBackground = UIManager.getColor( "ComboBox.buttonBackground" );
buttonFocusedBackground = UIManager.getColor( "ComboBox.buttonFocusedBackground" );
buttonFocusedEditableBackground = UIManager.getColor( "ComboBox.buttonFocusedEditableBackground" );
buttonEditableBackground = UIManager.getColor( "ComboBox.buttonEditableBackground" );
buttonSeparatorWidth = FlatUIUtils.getUIFloat( "ComboBox.buttonSeparatorWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ) );
buttonSeparatorColor = UIManager.getColor( "ComboBox.buttonSeparatorColor" );
@@ -293,6 +296,7 @@ public class FlatComboBoxUI
buttonBackground = null;
buttonEditableBackground = null;
buttonFocusedBackground = null;
buttonFocusedEditableBackground = null;
buttonSeparatorColor = null;
buttonDisabledSeparatorColor = null;
buttonArrowColor = null;
@@ -587,7 +591,7 @@ public class FlatComboBoxUI
// paint arrow button background
if( enabled && !isCellRenderer && arrowButton.isVisible() ) {
Color buttonColor = paintButton
? buttonEditableBackground
? (buttonFocusedEditableBackground != null && isPermanentFocusOwner( comboBox ) ? buttonFocusedEditableBackground : buttonEditableBackground)
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
: buttonBackground;

View File

@@ -24,9 +24,8 @@ import java.awt.Image;
import java.awt.Insets;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;
import java.util.Map;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -43,7 +42,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatDropShadowBorder
extends FlatEmptyBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected Color shadowColor;
@Styleable protected Insets shadowInsets;
@@ -93,7 +92,7 @@ public class FlatDropShadowBorder
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = FlatStylingSupport.applyToAnnotatedObject( this, key, value );
Object oldValue = StyleableObject.super.applyStyleProperty( key, value );
if( key.equals( "shadowInsets" ) ) {
applyStyleProperty( nonNegativeInsets( shadowInsets ) );
shadowSize = maxInset( shadowInsets );
@@ -101,18 +100,6 @@ public class FlatDropShadowBorder
return oldValue;
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( shadowSize <= 0 )

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.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
@@ -209,7 +209,7 @@ public class FlatInternalFrameUI
public static class FlatInternalFrameBorder
extends FlatEmptyBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
@Styleable protected Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );

View File

@@ -21,11 +21,10 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.util.Map;
import javax.swing.JMenuBar;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
/**
* Border for {@link javax.swing.JMenuBar}.
@@ -36,27 +35,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
*/
public class FlatMenuBarBorder
extends FlatMarginBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected Color borderColor = UIManager.getColor( "MenuBar.borderColor" );
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( !showBottomSeparator( c ) )

View File

@@ -24,6 +24,7 @@ import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -34,9 +35,9 @@ import com.formdev.flatlaf.util.SystemInfo;
* @author Karl Tauber
* @since 2.5
*/
class FlatNativeLinuxLibrary
public class FlatNativeLinuxLibrary
{
private static int API_VERSION_LINUX = 3001;
private static int API_VERSION_LINUX = 3003;
/**
* Checks whether native library is loaded/available.
@@ -44,10 +45,13 @@ class FlatNativeLinuxLibrary
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
static boolean isLoaded() {
public static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
}
//---- X Window System ----------------------------------------------------
// direction for _NET_WM_MOVERESIZE message
// see https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html
static final int
@@ -124,4 +128,110 @@ class FlatNativeLinuxLibrary
return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) ||
(window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated());
}
//---- GTK ----------------------------------------------------------------
private static Boolean isGtk3Available;
/**
* Checks whether GTK 3 is available.
* Use this before invoking any native method that uses GTK.
* Otherwise the app may terminate immediately if GTK is not installed.
* <p>
* This works because Java uses {@code dlopen(RTLD_LAZY)} to load JNI libraries,
* which only resolves symbols as the code that references them is executed.
*
* @since 3.7
*/
public static boolean isGtk3Available() {
if( isGtk3Available == null )
isGtk3Available = isLibAvailable( "libgtk-3.so.0" ) || isLibAvailable( "libgtk-3.so" );
return isGtk3Available;
}
private native static boolean isLibAvailable( String libname );
/**
* https://docs.gtk.org/gtk3/iface.FileChooser.html#properties
*
* @since 3.7
*/
public static final int
FC_select_folder = 1 << 0,
FC_select_multiple = 1 << 1,
FC_show_hidden = 1 << 2,
FC_local_only = 1 << 3, // default
FC_do_overwrite_confirmation = 1 << 4, // GTK 3 only; removed and always-on in GTK 4
FC_create_folders = 1 << 5; // default for Save
/**
* Shows the Linux/GTK system file dialog
* <a href="https://docs.gtk.org/gtk3/class.FileChooserDialog.html">GtkFileChooserDialog</a>.
* <p>
* Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}.
* Otherwise uses {@code GTK_FILE_CHOOSER_ACTION_OPEN} if parameter {@code open} is {@code true},
* or {@code GTK_FILE_CHOOSER_ACTION_SAVE} if {@code false}.
* <p>
* <b>Note:</b> This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
*
* @param owner the owner of the file dialog; or {@code null}
* @param dark preferred appearance of the file dialog: {@code 1} = prefer dark, {@code 0} = prefer light, {@code -1} = default
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}.
* Use '_' for mnemonics (e.g. "_Choose")
* Use '__' for '_' character (e.g. "Choose__and__Quit").
* @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null}
* @param currentFolder current directory shown in the dialog; or {@code null}
* @param optionsSet options to set; see {@code FOS_*} constants
* @param optionsClear options to clear; see {@code FOS_*} constants
* @param callback approve callback; or {@code null}
* @param fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save.
* Two or more strings and {@code null} are required for each filter.
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
* Subsequent strings are the filter patterns (e.g. "*.txt" or "*").
* {@code null} is required to mark end of filter.
* @return file path(s) that the user selected; an empty array if canceled;
* or {@code null} on failures (no dialog shown)
*
* @since 3.7
*/
public native static String[] showFileChooser( Window owner, int dark, boolean open,
String title, String okButtonLabel, String currentName, String currentFolder,
int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes );
/** @since 3.7 */
public interface FileChooserCallback {
boolean approve( String[] files, long hwndFileDialog );
}
/**
* Shows a GTK message box
* <a href="https://docs.gtk.org/gtk3/class.MessageDialog.html">GtkMessageDialog</a>.
* <p>
* For use in {@link FileChooserCallback} only.
*
* @param hwndParent the parent of the message box
* @param messageType type of message being displayed:
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE},
* {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or
* {@link JOptionPane#PLAIN_MESSAGE}
* @param primaryText primary text; if the dialog has a secondary text,
* this will appear as title in a larger bold font
* @param secondaryText secondary text; shown below of primary text; or {@code null}
* @param defaultButton index of the default button, which can be pressed using ENTER key
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown.
* Use '_' for mnemonics (e.g. "_Choose")
* Use '__' for '_' character (e.g. "Choose__and__Quit").
* @return index of pressed button; or -1 for ESC key
*
* @since 3.7
*/
public native static int showMessageDialog( long hwndParent, int messageType,
String primaryText, String secondaryText, int defaultButton, String... buttons );
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -44,7 +45,7 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeMacLibrary
{
private static int API_VERSION_MACOS = 2001;
private static int API_VERSION_MACOS = 2002;
/**
* Checks whether native library is loaded/available.
@@ -68,4 +69,88 @@ public class FlatNativeMacLibrary
/** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window );
/** @since 3.4 */ public native static boolean isWindowFullScreen( Window window );
/** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window );
/** @since 3.7 */
public static final int
// NSOpenPanel (extends NSSavePanel)
FC_canChooseFiles = 1 << 0, // default
FC_canChooseDirectories = 1 << 1,
FC_resolvesAliases = 1 << 2, // default
FC_allowsMultipleSelection = 1 << 3,
FC_accessoryViewDisclosed = 1 << 4,
// NSSavePanel
FC_showsTagField = 1 << 8, // default for Save
FC_canCreateDirectories = 1 << 9, // default for Save
FC_canSelectHiddenExtension = 1 << 10,
FC_showsHiddenFiles = 1 << 11,
FC_extensionHidden = 1 << 12,
FC_allowsOtherFileTypes = 1 << 13,
FC_treatsFilePackagesAsDirectories = 1 << 14,
// custom
FC_showSingleFilterField = 1 << 24;
/**
* Shows the macOS system file dialogs
* <a href="https://developer.apple.com/documentation/appkit/nsopenpanel?language=objc">NSOpenPanel</a> or
* <a href="https://developer.apple.com/documentation/appkit/nssavepanel?language=objc">NSSavePanel</a>.
* <p>
* <b>Note:</b> This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
*
* @param owner the owner of the file dialog; or {@code null}
* @param dark appearance of the file dialog: {@code 1} = dark, {@code 0} = light, {@code -1} = default
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed at top of save dialog (not used in open dialog); or {@code null}
* @param prompt text displayed in default button; or {@code null}
* @param message text displayed at top of open/save dialogs; or {@code null}
* @param filterFieldLabel text displayed in front of the filter combobox; or {@code null}
* @param nameFieldLabel text displayed in front of the filename text field in save dialog (not used in open dialog); or {@code null}
* @param nameFieldStringValue user-editable filename currently shown in the name field in save dialog (not used in open dialog); or {@code null}
* @param directoryURL current directory shown in the dialog; or {@code null}
* @param optionsSet options to set; see {@code FC_*} constants
* @param optionsClear options to clear; see {@code FC_*} constants
* @param fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save.
* Two or more strings and {@code null} are required for each filter.
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
* Subsequent strings are the filter patterns (e.g. "txt" or "*").
* {@code null} is required to mark end of filter.
* @return file path(s) that the user selected; an empty array if canceled;
* or {@code null} on failures (no dialog shown)
*
* @since 3.7
*/
public native static String[] showFileChooser( Window owner, int dark, boolean open,
String title, String prompt, String message, String filterFieldLabel,
String nameFieldLabel, String nameFieldStringValue, String directoryURL,
int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes );
/** @since 3.7 */
public interface FileChooserCallback {
boolean approve( String[] files, long hwndFileDialog );
}
/**
* Shows a macOS alert
* <a href="https://developer.apple.com/documentation/appkit/nsalert?language=objc">NSAlert</a>.
* <p>
* For use in {@link FileChooserCallback} only.
*
* @param hwndParent the parent of the message box
* @param alertStyle type of alert being displayed:
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE} or
* {@link JOptionPane#WARNING_MESSAGE}
* @param messageText main message of the alert
* @param informativeText additional information about the alert; shown below of main message; or {@code null}
* @param defaultButton index of the default button, which can be pressed using ENTER key
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown
* @return index of pressed button
*
* @since 3.7
*/
public native static int showMessageDialog( long hwndParent, int alertStyle,
String messageText, String informativeText, int defaultButton, String... buttons );
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Window;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -30,7 +31,7 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeWindowsLibrary
{
private static int API_VERSION_WINDOWS = 1001;
private static int API_VERSION_WINDOWS = 1002;
private static long osBuildNumber = Long.MIN_VALUE;
@@ -158,4 +159,125 @@ public class FlatNativeWindowsLibrary
// DwmSetWindowAttribute() expects COLORREF as attribute value, which is defined as DWORD
return dwmSetWindowAttributeDWORD( hwnd, attribute, rgb );
}
/**
* FILEOPENDIALOGOPTIONS
* see https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions
*
* @since 3.7
*/
public static final int
FOS_OVERWRITEPROMPT = 0x2, // default for Save
FOS_STRICTFILETYPES = 0x4,
FOS_NOCHANGEDIR = 0x8, // default
FOS_PICKFOLDERS = 0x20,
FOS_FORCEFILESYSTEM = 0x40,
FOS_ALLNONSTORAGEITEMS = 0x80,
FOS_NOVALIDATE = 0x100,
FOS_ALLOWMULTISELECT = 0x200,
FOS_PATHMUSTEXIST = 0x800, // default
FOS_FILEMUSTEXIST = 0x1000, // default for Open
FOS_CREATEPROMPT = 0x2000,
FOS_SHAREAWARE = 0x4000,
FOS_NOREADONLYRETURN = 0x8000, // default for Save
FOS_NOTESTFILECREATE = 0x10000,
FOS_HIDEMRUPLACES = 0x20000,
FOS_HIDEPINNEDPLACES = 0x40000,
FOS_NODEREFERENCELINKS = 0x100000,
FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
FOS_DONTADDTORECENT = 0x2000000,
FOS_FORCESHOWHIDDEN = 0x10000000,
FOS_DEFAULTNOMINIMODE = 0x20000000,
FOS_FORCEPREVIEWPANEON = 0x40000000,
FOS_SUPPORTSTREAMABLEITEMS = 0x80000000;
/**
* Shows the Windows system
* <a href="https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog">file dialogs</a>
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifileopendialog">IFileOpenDialog</a> or
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesavedialog">IFileSaveDialog</a>.
* <p>
* <b>Note:</b> This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
*
* @param owner the owner of the file dialog; or {@code null}
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}.
* Use '&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 );
}

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
@@ -41,8 +40,8 @@ import javax.swing.text.JTextComponent;
import javax.swing.text.PasswordView;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -215,12 +214,12 @@ public class FlatPasswordFieldUI
/** @since 2 */
@Override
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) {
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject ) {
if( capsLockIconShared ) {
capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon );
capsLockIconShared = false;
}
return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value );
return ((StyleableObject)capsLockIcon).applyStyleProperty( key, value );
}
return super.applyStyleProperty( key, value );
@@ -230,14 +229,15 @@ public class FlatPasswordFieldUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
infos.put( "capsLockIconColor", Color.class );
if( capsLockIcon instanceof StyleableObject )
infos.putAll( ((StyleableObject)capsLockIcon).getStyleableInfos() );
return infos;
}
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon )
return ((FlatCapsLockIcon)capsLockIcon).getStyleableValue( key );
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject )
return ((StyleableObject)capsLockIcon).getStyleableValue( key );
return super.getStyleableValue( c, key );
}

View File

@@ -108,10 +108,8 @@ public class FlatPopupFactory
}
}
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, isForceHeavyWeight( owner, contents, x, y ) ), owner, contents );
// macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
@@ -131,12 +129,8 @@ public class FlatPopupFactory
return popup;
}
// check whether popup overlaps a heavy weight component
if( !forceHeavyWeight && overlapsHeavyWeightComponent( owner, contents, x, y ) )
forceHeavyWeight = true;
// create drop shadow popup
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight );
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, isForceHeavyWeight( owner, contents, x, y ) );
GraphicsConfiguration gc = (owner != null) ? owner.getGraphicsConfiguration() : null;
return (gc != null && gc.isTranslucencyCapable())
? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
@@ -226,6 +220,11 @@ public class FlatPopupFactory
}
}
private static boolean isForceHeavyWeight( Component owner, Component contents, int x, int y ) {
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
return forceHeavyWeight || hasVisibleGlassPane( owner ) || overlapsHeavyWeightComponent( owner, contents, x, y );
}
private static boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
@@ -469,6 +468,18 @@ public class FlatPopupFactory
//---- fixes --------------------------------------------------------------
private static boolean hasVisibleGlassPane( Component owner ) {
if( owner == null )
return false;
Window window = SwingUtilities.windowForComponent( owner );
if( !(window instanceof RootPaneContainer) )
return false;
Component glassPane = ((RootPaneContainer)window).getGlassPane();
return (glassPane != null && glassPane.isVisible());
}
private static boolean overlapsHeavyWeightComponent( Component owner, Component contents, int x, int y ) {
if( owner == null )
return false;

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.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.UIScale;
@@ -37,7 +37,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatPopupMenuBorder
extends FlatLineBorder
implements StyleableBorder
implements StyleableObject
{
private Color borderColor;

View File

@@ -45,6 +45,7 @@ import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -203,16 +204,17 @@ public class FlatRadioButtonUI
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) ) {
if( !(icon instanceof FlatCheckBoxIcon) )
return new UnknownStyleException( key );
Icon icon = getRealIcon( b );
if( !(icon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( iconShared ) {
icon = FlatStylingSupport.cloneIcon( icon );
if( icon == this.icon && iconShared ) {
this.icon = icon = FlatStylingSupport.cloneIcon( icon );
iconShared = false;
}
key = key.substring( "icon.".length() );
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
return ((StyleableObject)icon).applyStyleProperty( key, value );
}
if( "iconTextGap".equals( key ) && value instanceof Integer )
@@ -225,10 +227,9 @@ public class FlatRadioButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
if( icon instanceof FlatCheckBoxIcon ) {
for( Map.Entry<String, Class<?>> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() )
infos.put( "icon.".concat( e.getKey() ), e.getValue() );
}
Icon icon = getRealIcon( c );
if( icon instanceof StyleableObject )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((StyleableObject)icon).getStyleableInfos() );
return infos;
}
@@ -237,8 +238,9 @@ public class FlatRadioButtonUI
public Object getStyleableValue( JComponent c, String key ) {
// style icon
if( key.startsWith( "icon." ) ) {
return (icon instanceof FlatCheckBoxIcon)
? ((FlatCheckBoxIcon)icon).getStyleableValue( key.substring( "icon.".length() ) )
Icon icon = getRealIcon( c );
return (icon instanceof StyleableObject)
? ((StyleableObject)icon).getStyleableValue( key.substring( "icon.".length() ) )
: null;
}
@@ -332,16 +334,18 @@ public class FlatRadioButtonUI
}
private int getIconFocusWidth( JComponent c ) {
AbstractButton b = (AbstractButton) c;
Icon icon = b.getIcon();
if( icon == null )
icon = getDefaultIcon();
Icon icon = getRealIcon( c );
return (icon instanceof FlatCheckBoxIcon)
? Math.round( UIScale.scale( ((FlatCheckBoxIcon)icon).getFocusWidth() ) )
: 0;
}
private Icon getRealIcon( JComponent c ) {
AbstractButton b = (AbstractButton) c;
Icon icon = b.getIcon();
return (icon != null) ? icon : getDefaultIcon();
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return FlatButtonUI.getBaselineImpl( c, width, height );

View File

@@ -227,7 +227,13 @@ public class FlatSliderUI
/** @since 2 */
protected void applyStyle( Object style ) {
Dimension oldThumbSize = thumbSize;
int oldFocusWidth = focusWidth;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( !thumbSize.equals( oldThumbSize ) || focusWidth != oldFocusWidth )
calculateGeometry();
}
/** @since 2 */

View File

@@ -51,6 +51,9 @@ import com.formdev.flatlaf.util.StringUtils;
*/
public class FlatStylingSupport
{
//---- annotations --------------------------------------------------------
/**
* Indicates that a field is intended to be used by FlatLaf styling support.
* <p>
@@ -98,17 +101,56 @@ public class FlatStylingSupport
}
//---- interfaces ---------------------------------------------------------
/** @since 2 */
public interface StyleableUI {
Map<String, Class<?>> getStyleableInfos( JComponent c ) throws IllegalArgumentException;
/** @since 2.5 */ Object getStyleableValue( JComponent c, String key ) throws IllegalArgumentException;
}
/** @since 2 */
public interface StyleableBorder {
Object applyStyleProperty( String key, Object value );
Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException;
/** @since 2.5 */ Object getStyleableValue( String key ) throws IllegalArgumentException;
/**
* An object that implements this interface is intended to support FlatLaf styling.
*
* @since 3.7
*/
public interface StyleableObject {
/**
* Applies the given value to this object.
* <p>
* The default implementation invokes {@link FlatStylingSupport#applyToAnnotatedObject(Object, String, Object)}.
*
* @param key the name of the property
* @param value the new value
* @return the old value of the property
*/
default Object applyStyleProperty( String key, Object value )
throws UnknownStyleException, IllegalArgumentException
{
return applyToAnnotatedObject( this, key, value );
}
/**
* Returns a map of all styleable properties.
* The key is the name of the property and the value the type of the property.
* <p>
* The default implementation invokes {@link FlatStylingSupport#getAnnotatedStyleableInfos(Object)}.
*/
default Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException {
return getAnnotatedStyleableInfos( this );
}
/**
* Returns the current value for the given property key.
* <p>
* The default implementation invokes {@link FlatStylingSupport#getAnnotatedStyleableValue(Object, String)}.
*
* @param key the name of the property
* @return the current value of the property
*/
default Object getStyleableValue( String key ) throws IllegalArgumentException {
return getAnnotatedStyleableValue( this, key );
}
}
/** @since 2.5 */
@@ -117,6 +159,8 @@ public class FlatStylingSupport
}
//---- methods ------------------------------------------------------------
/**
* Returns the style specified in client property {@link FlatClientProperties#STYLE}.
*/
@@ -675,7 +719,7 @@ public class FlatStylingSupport
} catch( UnknownStyleException ex ) {
// apply to border
Border border = c.getBorder();
if( border instanceof StyleableBorder ) {
if( border instanceof StyleableObject ) {
if( borderShared.get() ) {
border = cloneBorder( border );
c.setBorder( border );
@@ -683,7 +727,7 @@ public class FlatStylingSupport
}
try {
return ((StyleableBorder)border).applyStyleProperty( key, value );
return ((StyleableObject)border).applyStyleProperty( key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
@@ -833,8 +877,8 @@ public class FlatStylingSupport
}
public static void collectStyleableInfos( Border border, Map<String, Class<?>> infos ) {
if( border instanceof StyleableBorder )
infos.putAll( ((StyleableBorder)border).getStyleableInfos() );
if( border instanceof StyleableObject )
infos.putAll( ((StyleableObject)border).getStyleableInfos() );
}
public static void putAllPrefixKey( Map<String, Class<?>> infos, String keyPrefix, Map<String, Class<?>> infos2 ) {
@@ -882,8 +926,8 @@ public class FlatStylingSupport
}
public static Object getAnnotatedStyleableValue( Object obj, Border border, String key ) {
if( border instanceof StyleableBorder ) {
Object value = ((StyleableBorder)border).getStyleableValue( key );
if( border instanceof StyleableObject ) {
Object value = ((StyleableObject)border).getStyleableValue( key );
if( value != null )
return value;
}

View File

@@ -92,8 +92,8 @@ import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.Animator;
@@ -205,6 +205,7 @@ public class FlatTabbedPaneUI
protected static final int WIDTH_MODE_PREFERRED = 0;
protected static final int WIDTH_MODE_EQUAL = 1;
protected static final int WIDTH_MODE_COMPACT = 2;
/** @since 3.7 */ protected static final int WIDTH_MODE_ICON_ONLY = 3;
// tab rotation
/** @since 3.3 */ protected static final int NONE = -1;
@@ -670,15 +671,15 @@ public class FlatTabbedPaneUI
protected Object applyStyleProperty( String key, Object value ) {
// close icon
if( key.startsWith( "close" ) ) {
if( !(closeIcon instanceof FlatTabbedPaneCloseIcon) )
return new UnknownStyleException( key );
if( !(closeIcon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( closeIconShared ) {
closeIcon = FlatStylingSupport.cloneIcon( closeIcon );
closeIconShared = false;
}
return ((FlatTabbedPaneCloseIcon)closeIcon).applyStyleProperty( key, value );
return ((StyleableObject)closeIcon).applyStyleProperty( key, value );
}
if( value instanceof String ) {
@@ -720,8 +721,8 @@ public class FlatTabbedPaneUI
infos.put( "tabAreaInsets", Insets.class );
infos.put( "textIconGap", int.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
if( closeIcon instanceof FlatTabbedPaneCloseIcon )
infos.putAll( ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableInfos() );
if( closeIcon instanceof StyleableObject )
infos.putAll( ((StyleableObject)closeIcon).getStyleableInfos() );
return infos;
}
@@ -730,8 +731,8 @@ public class FlatTabbedPaneUI
public Object getStyleableValue( JComponent c, String key ) {
// close icon
if( key.startsWith( "close" ) ) {
return (closeIcon instanceof FlatTabbedPaneCloseIcon)
? ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableValue( key )
return (closeIcon instanceof StyleableObject)
? ((StyleableObject)closeIcon).getStyleableValue( key )
: null;
}
@@ -780,6 +781,7 @@ public class FlatTabbedPaneUI
case WIDTH_MODE_PREFERRED: return TABBED_PANE_TAB_WIDTH_MODE_PREFERRED;
case WIDTH_MODE_EQUAL: return TABBED_PANE_TAB_WIDTH_MODE_EQUAL;
case WIDTH_MODE_COMPACT: return TABBED_PANE_TAB_WIDTH_MODE_COMPACT;
case WIDTH_MODE_ICON_ONLY: return TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY;
}
case "tabRotation":
@@ -926,9 +928,10 @@ public class FlatTabbedPaneUI
int tabWidth;
Icon icon;
if( tabWidthMode == WIDTH_MODE_COMPACT &&
tabIndex != tabPane.getSelectedIndex() &&
isHorizontalOrRotated( tabPlacement ) &&
if( ((tabWidthMode == WIDTH_MODE_COMPACT &&
tabIndex != tabPane.getSelectedIndex() &&
isHorizontalOrRotated( tabPlacement )) ||
tabWidthMode == WIDTH_MODE_ICON_ONLY) &&
tabPane.getTabComponentAt( tabIndex ) == null &&
(icon = getIconForTab( tabIndex )) != null )
{
@@ -1247,7 +1250,8 @@ public class FlatTabbedPaneUI
Font font = tabPane.getFont();
FontMetrics metrics = tabPane.getFontMetrics( font );
boolean isCompact = (icon != null && !isSelected && getTabWidthMode() == WIDTH_MODE_COMPACT && isHorizontalOrRotated( tabPlacement ));
if( isCompact )
boolean isIconOnly = (icon != null && getTabWidthMode() == WIDTH_MODE_ICON_ONLY);
if( isCompact || isIconOnly )
title = null;
String clippedTitle = layoutAndClipLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected );
@@ -1283,7 +1287,7 @@ debug*/
}
// paint title and icon
if( !isCompact )
if( !isCompact || isIconOnly )
paintText( g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected );
paintIcon( g, tabPlacement, tabIndex, icon, iconRect, isSelected );
}
@@ -2160,6 +2164,7 @@ debug*/
case TABBED_PANE_TAB_WIDTH_MODE_PREFERRED: return WIDTH_MODE_PREFERRED;
case TABBED_PANE_TAB_WIDTH_MODE_EQUAL: return WIDTH_MODE_EQUAL;
case TABBED_PANE_TAB_WIDTH_MODE_COMPACT: return WIDTH_MODE_COMPACT;
case TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY: return WIDTH_MODE_ICON_ONLY;
}
}
@@ -2508,7 +2513,7 @@ debug*/
// 2. text of label or text component in custom tab component (including children)
// 3. accessible name of tab
// 4. accessible name of custom tab component (including children)
// 5. string "n. Tab"
// 5. string "n. Tab", if tab does not have an icon
String title = tabPane.getTitleAt( tabIndex );
if( StringUtils.isEmpty( title ) ) {
Component tabComp = tabPane.getTabComponentAt( tabIndex );
@@ -2518,7 +2523,7 @@ debug*/
title = tabPane.getAccessibleContext().getAccessibleChild( tabIndex ).getAccessibleContext().getAccessibleName();
if( StringUtils.isEmpty( title ) && tabComp instanceof Accessible )
title = findTabTitleInAccessible( (Accessible) tabComp );
if( StringUtils.isEmpty( title ) )
if( StringUtils.isEmpty( title ) && tabPane.getIconAt( tabIndex ) == null )
title = (tabIndex + 1) + ". Tab";
}
@@ -2539,6 +2544,12 @@ debug*/
if( !tabPane.isEnabled() || !tabPane.isEnabledAt( tabIndex ) )
menuItem.setEnabled( false );
// make menu item smaller if it contains icon only
if( StringUtils.isEmpty( title ) ) {
menuItem.putClientProperty( FlatClientProperties.STYLE,
"minimumWidth: 0; textNoAcceleratorGap: 0; acceleratorArrowGap: 0" );
}
menuItem.addActionListener( e -> selectTab( tabIndex ) );
return menuItem;
}
@@ -2707,8 +2718,13 @@ debug*/
// because this listener receives mouse events for the whole tabbed pane,
// we have to check whether the mouse is located over the viewport
if( !isInViewport( e.getX(), e.getY() ) )
if( !isInViewport( e.getX(), e.getY() ) ) {
// if it is not in the viewport, retarget the event to a parent container
// which might support scrolling (e.g. a surrounding ScrollPane)
Container parent = tabPane.getParent();
parent.dispatchEvent( SwingUtilities.convertMouseEvent( tabPane, e, parent ) );
return;
}
lastMouseX = e.getX();
lastMouseY = e.getY();

View File

@@ -34,7 +34,6 @@ import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Action;
@@ -66,6 +65,7 @@ import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatUIUtils.FlatPropertyWatcher;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
@@ -189,29 +189,26 @@ public class FlatTableUI
if( rowHeight > 0 )
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
if( !showHorizontalLines && (watcher == null || !watcher.showHorizontalLinesChanged) ) {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
if( !showHorizontalLines ) {
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
} );
}
if( !showVerticalLines && (watcher == null || !watcher.showVerticalLinesChanged) ) {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
if( !showVerticalLines ) {
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
} );
}
if( intercellSpacing != null && (watcher == null || !watcher.intercellSpacingChanged) ) {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
if( intercellSpacing != null ) {
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
} );
}
if( watcher != null )
watcher.enabled = true;
else
table.addPropertyChangeListener( new FlatTablePropertyWatcher() );
// install boolean renderer
oldBooleanRenderer = table.getDefaultRenderer( Boolean.class );
if( oldBooleanRenderer instanceof UIResource )
@@ -231,25 +228,24 @@ public class FlatTableUI
oldStyleValues = null;
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
// restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() &&
(watcher == null || !watcher.showHorizontalLinesChanged) )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() &&
(watcher == null || !watcher.showVerticalLinesChanged) )
table.setShowVerticalLines( true );
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() ) {
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
table.setShowHorizontalLines( true );
} );
}
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() ) {
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
table.setShowVerticalLines( true );
} );
}
// restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) &&
(watcher == null || !watcher.intercellSpacingChanged) )
table.setIntercellSpacing( oldIntercellSpacing );
if( watcher != null )
watcher.enabled = true;
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) ) {
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
table.setIntercellSpacing( oldIntercellSpacing );
} );
}
// uninstall boolean renderer
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
@@ -938,48 +934,6 @@ public class FlatTableUI
}
}
//---- class FlatTablePropertyWatcher -------------------------------------
/**
* Listener that watches for change of some table properties from application code.
* This information is used in {@link FlatTableUI#installDefaults()} and
* {@link FlatTableUI#uninstallDefaults()} to decide whether FlatLaf modifies those properties.
* If they are modified in application code, FlatLaf no longer changes them.
*
* The listener is added once for each table, but never removed.
* So switching Laf/theme reuses existing listener.
*/
private static class FlatTablePropertyWatcher
implements PropertyChangeListener
{
boolean enabled = true;
boolean showHorizontalLinesChanged;
boolean showVerticalLinesChanged;
boolean intercellSpacingChanged;
static FlatTablePropertyWatcher get( JTable table ) {
for( PropertyChangeListener l : table.getPropertyChangeListeners() ) {
if( l instanceof FlatTablePropertyWatcher )
return (FlatTablePropertyWatcher) l;
}
return null;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( !enabled )
return;
switch( e.getPropertyName() ) {
case "showHorizontalLines": showHorizontalLinesChanged = true; break;
case "showVerticalLines": showVerticalLinesChanged = true; break;
case "rowMargin": intercellSpacingChanged = true; break;
}
}
}
//---- class FlatBooleanRenderer ------------------------------------------
private static class FlatBooleanRenderer

View File

@@ -628,7 +628,7 @@ debug*/
/**
* Returns the rectangle used to paint leading and trailing icons.
* It invokes {@code super.getVisibleEditorRect()} and reduces left and/or
* right margin if the text field has leading or trailing icons or components.
* right insets if the text field has leading or trailing icons or components.
* Also, the preferred widths of leading and trailing components are removed.
*
* @since 2
@@ -660,24 +660,24 @@ debug*/
}
}
// if a leading/trailing icons (or components) are shown, then the left/right margins are reduced
// to the top margin, which places the icon nicely centered on left/right side
// if a leading/trailing icons (or components) are shown, then the left/right insets are reduced
// to the top inset, which places the icon nicely centered on left/right side
if( leftVisible || (ltr ? hasLeadingIcon() : hasTrailingIcon()) ) {
// reduce left margin
Insets margin = getComponent().getMargin();
int newLeftMargin = Math.min( margin.left, margin.top );
if( newLeftMargin < margin.left ) {
int diff = scale( margin.left - newLeftMargin );
// reduce left inset
Insets insets = getComponent().getInsets();
int newLeftInset = Math.min( insets.left, insets.top );
if( newLeftInset < insets.left ) {
int diff = insets.left - newLeftInset;
r.x -= diff;
r.width += diff;
}
}
if( rightVisible || (ltr ? hasTrailingIcon() : hasLeadingIcon()) ) {
// reduce right margin
Insets margin = getComponent().getMargin();
int newRightMargin = Math.min( margin.right, margin.top );
if( newRightMargin < margin.left )
r.width += scale( margin.right - newRightMargin );
// reduce right inset
Insets insets = getComponent().getInsets();
int newRightInset = Math.min( insets.right, insets.top );
if( newRightInset < insets.left )
r.width += insets.right - newRightInset;
}
// make sure that width and height are not negative

View File

@@ -484,7 +484,7 @@ public class FlatTitlePane
}
protected void frameStateChanged() {
if( window == null || rootPane.getWindowDecorationStyle() != JRootPane.FRAME )
if( window == null || rootPane.getWindowDecorationStyle() == JRootPane.NONE )
return;
updateVisibility();

View File

@@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicToolBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatUIUtils.FlatPropertyWatcher;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -144,11 +145,13 @@ public class FlatToolBarUI
hoverButtonGroupBackground = UIManager.getColor( "ToolBar.hoverButtonGroupBackground" );
// floatable
oldFloatable = null;
if( !UIManager.getBoolean( "ToolBar.floatable" ) ) {
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} else
oldFloatable = null;
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} );
}
}
@Override
@@ -158,7 +161,9 @@ public class FlatToolBarUI
hoverButtonGroupBackground = null;
if( oldFloatable != null ) {
toolBar.setFloatable( oldFloatable );
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
toolBar.setFloatable( oldFloatable );
} );
oldFloatable = null;
}
}

View File

@@ -44,6 +44,8 @@ import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.IdentityHashMap;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -474,6 +476,9 @@ public class FlatUIUtils
* Returns the scaled arc diameter of the border for the given component.
*/
public static float getBorderArc( JComponent c ) {
if( c.getBorder() instanceof FlatLineBorder )
return UIScale.scale( ((FlatLineBorder)c.getBorder()).getArc() );
FlatBorder border = getOutsideFlatBorder( c );
return (border != null)
? UIScale.scale( (float) border.getArc( c ) )
@@ -1410,4 +1415,47 @@ debug*/
return delegate.isBorderOpaque();
}
}
//---- class FlatPropertyWatcher ------------------------------------------
/**
* Listener that watches for change of a property from application code.
* This information can be used to decide whether FlatLaf modifies the property.
* If it is modified in application code, FlatLaf no longer changes it.
* <p>
* The listener is added once for a property, but never removed.
* So switching Laf/theme reuses existing listener.
*/
static class FlatPropertyWatcher
implements PropertyChangeListener
{
private boolean changed;
static void runIfNotChanged( JComponent c, String propName, Runnable runnable ) {
FlatPropertyWatcher watcher = getOrInstall( c, propName );
if( watcher.changed )
return;
runnable.run();
watcher.changed = false;
}
private static FlatPropertyWatcher getOrInstall( JComponent c, String propName ) {
for( PropertyChangeListener l : c.getPropertyChangeListeners( propName ) ) {
if( l instanceof FlatPropertyWatcher )
return (FlatPropertyWatcher) l;
}
FlatPropertyWatcher watcher = new FlatPropertyWatcher();
c.addPropertyChangeListener( propName, watcher );
return watcher;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
changed = true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,9 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
@@ -61,16 +63,28 @@ import com.formdev.flatlaf.FlatSystemProperties;
* or if the default font is changed.
* The user scale factor is computed based on the used font.
* The JRE does not scale anything.
* So we have to invoke {@link #scale(float)} where necessary.
* So we have to invoke {@link #scale(int)} where necessary.
* There is only one user scale factor for all displays.
* The user scale factor may change if the active LaF, "defaultFont" or "Label.font" has changed.
* If system scaling mode is available the user scale factor is usually 1,
* but may be larger on Linux or if the default font is changed.
*
* <h2>Zooming</h2>
*
* Zooming allows appliations to easily zoom their UI, if FlatLaf is active Laf.
* This is done by changing user scale factor and default font.
* There are methods to increase, decrease and reset zoom factor.
* <p>
* Note: Only standard Swing components are zoomed.
* Custom components need to use {@link #scale(int)} to zoom their UI.
*
* @author Karl Tauber
*/
public class UIScale
{
/** @since 3.7 */ public static final String PROP_USER_SCALE_FACTOR = "userScaleFactor";
/** @since 3.7 */ public static final String PROP_ZOOM_FACTOR = "zoomFactor";
private static final boolean DEBUG = false;
private static PropertyChangeSupport changeSupport;
@@ -87,7 +101,7 @@ public class UIScale
changeSupport.removePropertyChangeListener( listener );
}
//---- system scaling (Java 9) --------------------------------------------
//---- system scaling (Java 9+) -------------------------------------------
private static Boolean jreHiDPI;
@@ -135,10 +149,13 @@ public class UIScale
return (isSystemScalingEnabled() && gc != null) ? gc.getDefaultTransform().getScaleX() : 1;
}
//---- user scaling (Java 8) ----------------------------------------------
//---- user scaling (Java 8 / zooming) ------------------------------------
private static float unzoomedScaleFactor = 1;
private static float scaleFactor = 1;
private static boolean initialized;
private static boolean listenerInitialized; // use extra flag for unit tests
private static boolean ignoreFontChange;
private static void initialize() {
if( initialized )
@@ -148,33 +165,43 @@ public class UIScale
if( !isUserScalingEnabled() )
return;
initializeListener();
updateScaleFactor( true );
}
private static void initializeListener() {
if( listenerInitialized )
return;
listenerInitialized = true;
// listener to update scale factor if LaF changed, "defaultFont" or "Label.font" changed
PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case "lookAndFeel":
// it is not necessary (and possible) to remove listener of old LaF defaults
// it is not possible (and necessary) to remove listener of old LaF defaults
// because it is not possible to access the UIDefault object of the old LaF
if( e.getNewValue() instanceof LookAndFeel )
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( this );
updateScaleFactor();
updateScaleFactor( true );
break;
case "defaultFont":
case "Label.font":
updateScaleFactor();
if( !ignoreFontChange )
updateScaleFactor( false );
break;
}
}
};
UIManager.addPropertyChangeListener( listener );
UIManager.getDefaults().addPropertyChangeListener( listener );
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( listener );
updateScaleFactor();
UIManager.addPropertyChangeListener( listener );
}
private static void updateScaleFactor() {
private static void updateScaleFactor( boolean lafChanged ) {
if( !isUserScalingEnabled() )
return;
@@ -185,17 +212,20 @@ public class UIScale
return;
}
// use font size to calculate scale factor (instead of DPI)
// because even if we are on a HiDPI display it is not sure
// that a larger font size is set by the current LaF
// (e.g. can avoid large icons with small text)
// get font that is used to calculate scale factor
Font font = null;
if( UIManager.getLookAndFeel() instanceof FlatLaf )
font = UIManager.getFont( "defaultFont" );
if( font == null )
font = UIManager.getFont( "Label.font" );
setUserScaleFactor( computeFontScaleFactor( font ), true );
float fontScaleFactor = computeFontScaleFactor( font );
if( lafChanged && UIManager.getLookAndFeel() instanceof FlatLaf ) {
// FlatLaf has applied zoom factor in FlatLaf.initDefaultFont() to defaultFont,
// so we need to take it into account to get correct user scale factor
fontScaleFactor /= zoomFactor;
}
setUserScaleFactor( fontScaleFactor, true );
}
/**
@@ -204,7 +234,7 @@ public class UIScale
* @since 2
*/
public static float computeFontScaleFactor( Font font ) {
if( SystemInfo.isWindows ) {
if( SystemInfo.isWindows && !inUnitTests ) {
// Special handling for Windows to be compatible with OS scaling,
// which distinguish between "screen scaling" and "text scaling".
// - Windows "screen scaling" scales everything (text, icon, gaps, etc.)
@@ -335,7 +365,7 @@ public class UIScale
}
/**
* Returns the user scale factor.
* Returns the user scale factor (including zoom factor).
*/
public static float getUserScaleFactor() {
initialize();
@@ -345,27 +375,49 @@ public class UIScale
/**
* Sets the user scale factor.
*/
private static void setUserScaleFactor( float scaleFactor, boolean normalize ) {
if( normalize ) {
if( scaleFactor < 1f ) {
scaleFactor = FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false )
? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10
: 1f;
} else if( scaleFactor > 1f ) // round scale factor to 1/4
scaleFactor = Math.round( scaleFactor * 4f ) / 4f;
}
private static void setUserScaleFactor( float unzoomedScaleFactor, boolean normalize ) {
if( normalize )
unzoomedScaleFactor = normalizeScaleFactor( unzoomedScaleFactor );
// minimum scale factor
scaleFactor = Math.max( scaleFactor, 0.1f );
unzoomedScaleFactor = Math.max( unzoomedScaleFactor, 0.1f );
if( unzoomedScaleFactor == UIScale.unzoomedScaleFactor )
return;
if( DEBUG )
System.out.println( "Unzoomed scale factor " + UIScale.unzoomedScaleFactor + " --> " + unzoomedScaleFactor );
UIScale.unzoomedScaleFactor = unzoomedScaleFactor;
setScaleFactor( unzoomedScaleFactor * zoomFactor );
}
private static void setScaleFactor( float scaleFactor ) {
// round scale factor to 1/100
scaleFactor = Math.round( scaleFactor * 100f ) / 100f;
if( scaleFactor == UIScale.scaleFactor )
return;
float oldScaleFactor = UIScale.scaleFactor;
UIScale.scaleFactor = scaleFactor;
if( DEBUG )
System.out.println( "HiDPI scale factor " + scaleFactor );
System.out.println( "Scale factor " + oldScaleFactor + " --> " + scaleFactor + " (unzoomed " + UIScale.unzoomedScaleFactor + ")" );
if( changeSupport != null )
changeSupport.firePropertyChange( "userScaleFactor", oldScaleFactor, scaleFactor );
changeSupport.firePropertyChange( PROP_USER_SCALE_FACTOR, oldScaleFactor, scaleFactor );
}
private static float normalizeScaleFactor( float scaleFactor ) {
if( scaleFactor < 1f ) {
return FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false )
? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10
: 1f;
} else if( scaleFactor > 1f ) // round scale factor to 1/4
return Math.round( scaleFactor * 4f ) / 4f;
else
return scaleFactor;
}
/**
@@ -451,4 +503,185 @@ public class UIScale
? new InsetsUIResource( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) )
: new Insets ( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) ));
}
//---- zoom ---------------------------------------------------------------
private static float zoomFactor = 1;
private static float[] supportedZoomFactors = { 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f };
/**
* Returns the current zoom factor. Default is {@code 1}.
*
* @since 3.7
*/
public static float getZoomFactor() {
return zoomFactor;
}
/**
* Sets the zoom factor.
* Also updates user scale factor and default font (if FlatLaf is active Laf).
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.setZoomFactor( newZoomFactor ) )
* FlatLaf.updateUI();
* }</pre>
*
* @param zoomFactor new zoom factor
* @return {@code true} if zoom factor has changed
* @since 3.7
*/
public static boolean setZoomFactor( float zoomFactor ) {
// minimum zoom factor
zoomFactor = Math.max( zoomFactor, 0.1f );
if( UIScale.zoomFactor == zoomFactor )
return false;
float oldZoomFactor = UIScale.zoomFactor;
UIScale.zoomFactor = zoomFactor;
if( DEBUG )
System.out.println( "Zoom factor " + oldZoomFactor + " --> " + zoomFactor );
setScaleFactor( UIScale.unzoomedScaleFactor * zoomFactor );
if( initialized && UIManager.getLookAndFeel() instanceof FlatLaf ) {
// see also FlatLaf.initDefaultFont()
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
Font font = defaults.getFont( "defaultFont" );
int unzoomedSize = defaults.getInt( "defaultFont.unzoomedSize" );
if( unzoomedSize == 0 ) {
unzoomedSize = font.getSize();
defaults.put( "defaultFont.unzoomedSize", unzoomedSize );
}
// update "defaultFont"
ignoreFontChange = true;
try {
// get application default font before updating Laf default font
Font appFont = UIManager.getFont( "defaultFont" );
// update Laf default font
int newFontSize = Math.max( Math.round( unzoomedSize * zoomFactor ), 1 );
defaults.put( "defaultFont", new FontUIResource( font.deriveFont( (float) newFontSize ) ) );
if( DEBUG )
System.out.println( "Zoom Laf font " + font.getSize() + " --> " + newFontSize + " (unzoomed " + unzoomedSize + ")" );
// check whether application has changed default font
if( appFont != font ) {
// application has own default font --> also zoom it
int newAppFontSize = Math.max( Math.round( (appFont.getSize() / oldZoomFactor) * zoomFactor ), 1 );
UIManager.put( "defaultFont", appFont.deriveFont( (float) newAppFontSize ) );
if( DEBUG )
System.out.println( "Zoom app font " + appFont.getSize() + " --> " + newAppFontSize );
}
} finally {
ignoreFontChange = false;
}
}
if( changeSupport != null )
changeSupport.firePropertyChange( PROP_ZOOM_FACTOR, oldZoomFactor, zoomFactor );
return true;
}
/**
* Increases zoom factor using next greater factor in supported factors array.
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.zoomIn() )
* FlatLaf.updateUI();
* }</pre>
*
* @return {@code true} if zoom factor has changed
* @see #getSupportedZoomFactors()
* @since 3.7
*/
public static boolean zoomIn() {
int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor );
int next = (i >= 0) ? i + 1 : -i - 1;
if( next >= supportedZoomFactors.length )
return false;
return setZoomFactor( supportedZoomFactors[next] );
}
/**
* Decreases zoom factor using next smaller factor in supported factors array.
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.zoomOut() )
* FlatLaf.updateUI();
* }</pre>
*
* @return {@code true} if zoom factor has changed
* @see #getSupportedZoomFactors()
* @since 3.7
*/
public static boolean zoomOut() {
int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor );
int prev = (i >= 0) ? i - 1 : -i - 2;
if( prev < 0 )
return false;
return setZoomFactor( supportedZoomFactors[prev] );
}
/**
* Resets zoom factor to {@code 1}.
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.zoomReset() )
* FlatLaf.updateUI();
* }</pre>
*
* @return {@code true} if zoom factor has changed
* @since 3.7
*/
public static boolean zoomReset() {
return setZoomFactor( 1 );
}
/**
* Returns the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}.
* <p>
* Default is {@code [ 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f ]}.
*
* @since 3.7
*/
public static float[] getSupportedZoomFactors() {
return supportedZoomFactors.clone();
}
/**
* Sets the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}.
*
* @since 3.7
*/
public static void setSupportedZoomFactors( float[] supportedZoomFactors ) {
UIScale.supportedZoomFactors = supportedZoomFactors.clone();
Arrays.sort( UIScale.supportedZoomFactors );
if( Arrays.binarySearch( UIScale.supportedZoomFactors, 1f ) < 0 )
throw new IllegalArgumentException( "supportedZoomFactors array must contain value 1f" );
}
//---- unit testing -------------------------------------------------------
static boolean inUnitTests;
static void tests_uninitialize() {
initialized = false;
unzoomedScaleFactor = 1;
scaleFactor = 1;
zoomFactor = 1;
}
}

View File

@@ -696,7 +696,7 @@ TabbedPane.hiddenTabsNavigation = moreTabsButton
TabbedPane.tabAreaAlignment = leading
# allowed values: leading, trailing or center
TabbedPane.tabAlignment = center
# allowed values: preferred, equal or compact
# allowed values: preferred, equal, compact or iconsOnly
TabbedPane.tabWidthMode = preferred
# allowed values: none, auto, left or right
TabbedPane.tabRotation = none
@@ -977,6 +977,24 @@ Tree.icon.openColor = @icon
#---- Styles ------------------------------------------------------------------
#---- scale icons ----
@largeScale = 1.125
@mediumScale = 0.875
@smallScale = 0.8125
@miniScale = 0.75
[style]CheckBox.large = icon.scale: @largeScale; iconTextGap: 5
[style]CheckBox.medium = icon.scale: @mediumScale
[style]CheckBox.small = icon.scale: @smallScale; iconTextGap: 3
[style]CheckBox.mini = icon.scale: @miniScale; iconTextGap: 3
[style]RadioButton.large = icon.scale: @largeScale; iconTextGap: 5
[style]RadioButton.medium = icon.scale: @mediumScale
[style]RadioButton.small = icon.scale: @smallScale; iconTextGap: 3
[style]RadioButton.mini = icon.scale: @miniScale; iconTextGap: 3
#---- inTextField ----
# for leading/trailing components in text fields

View File

@@ -30,6 +30,10 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.icons.*;
import com.formdev.flatlaf.ui.FlatInternalFrameUI.FlatInternalFrameBorder;
import com.formdev.flatlaf.ui.TestFlatStyling.CustomCheckBoxIcon;
import com.formdev.flatlaf.ui.TestFlatStyling.CustomIcon;
import com.formdev.flatlaf.ui.TestFlatStyling.CustomRadioButtonIcon;
/**
* @author Karl Tauber
@@ -70,6 +74,8 @@ public class TestFlatStyleableInfo
//---- FlatHelpButtonIcon ----
expectedMap( expected,
"help.scale", float.class,
"help.focusWidth", int.class,
"help.focusColor", Color.class,
"help.innerFocusWidth", float.class,
@@ -144,7 +150,20 @@ public class TestFlatStyleableInfo
@Test
void checkBox() {
JCheckBox c = new JCheckBox();
checkBox( new JCheckBox() );
}
@Test
void checkBox2() {
checkBox( new JCheckBox( new CustomIcon() ) );
}
@Test
void checkBox3() {
checkBox( new JCheckBox( new CustomCheckBoxIcon() ) );
}
private void checkBox( JCheckBox c ) {
FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI();
assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon );
@@ -153,6 +172,11 @@ public class TestFlatStyleableInfo
Map<String, Class<?>> expected = new LinkedHashMap<>();
radioButton( expected );
// remove "icon." keys if check box has custom icon
Icon icon = c.getIcon();
if( icon != null && !(icon instanceof FlatCheckBoxIcon) )
expected.keySet().removeIf( key -> key.startsWith( "icon." ) );
assertMapEquals( expected, ui.getStyleableInfos( c ) );
}
@@ -175,8 +199,9 @@ public class TestFlatStyleableInfo
"disabledForeground", Color.class,
"buttonBackground", Color.class,
"buttonFocusedBackground", Color.class,
"buttonEditableBackground", Color.class,
"buttonFocusedBackground", Color.class,
"buttonFocusedEditableBackground", Color.class,
"buttonSeparatorWidth", float.class,
"buttonSeparatorColor", Color.class,
"buttonDisabledSeparatorColor", Color.class,
@@ -391,6 +416,8 @@ public class TestFlatStyleableInfo
private void menuItem_checkIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"icon.scale", float.class,
"icon.checkmarkColor", Color.class,
"icon.disabledCheckmarkColor", Color.class,
"selectionForeground", Color.class
@@ -399,6 +426,8 @@ public class TestFlatStyleableInfo
private void menuItem_arrowIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"icon.scale", float.class,
"icon.arrowType", String.class,
"icon.arrowColor", Color.class,
"icon.disabledArrowColor", Color.class,
@@ -434,7 +463,8 @@ public class TestFlatStyleableInfo
expectedMap( expected,
// capsLockIcon
"capsLockIconColor", Color.class
"capsLockIconColor", Color.class,
"capsLockIconScale", float.class
);
// border
@@ -492,7 +522,20 @@ public class TestFlatStyleableInfo
@Test
void radioButton() {
JRadioButton c = new JRadioButton();
radioButton( new JRadioButton() );
}
@Test
void radioButton2() {
radioButton( new JRadioButton( new CustomIcon() ) );
}
@Test
void radioButton3() {
radioButton( new JRadioButton( new CustomRadioButtonIcon() ) );
}
private void radioButton( JRadioButton c ) {
FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI();
assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon );
@@ -504,6 +547,11 @@ public class TestFlatStyleableInfo
"icon.centerDiameter", float.class
);
// remove "icon." keys if radio button has custom icon
Icon icon = c.getIcon();
if( icon != null && !(icon instanceof FlatRadioButtonIcon) )
expected.keySet().removeIf( key -> key.startsWith( "icon." ) );
assertMapEquals( expected, ui.getStyleableInfos( c ) );
}
@@ -513,6 +561,8 @@ public class TestFlatStyleableInfo
//---- icon ----
"icon.scale", float.class,
"icon.focusWidth", float.class,
"icon.focusColor", Color.class,
"icon.borderWidth", float.class,
@@ -791,6 +841,7 @@ public class TestFlatStyleableInfo
"tabIconPlacement", int.class,
// FlatTabbedPaneCloseIcon
"closeScale", float.class,
"closeSize", Dimension.class,
"closeArc", int.class,
"closeCrossPlainSize", float.class,
@@ -1112,6 +1163,16 @@ public class TestFlatStyleableInfo
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatScrollPaneBorder() {
FlatScrollPaneBorder border = new FlatScrollPaneBorder();
Map<String, Class<?>> expected = new LinkedHashMap<>();
flatScrollPaneBorder( expected );
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatTextBorder() {
FlatTextBorder border = new FlatTextBorder();
@@ -1132,6 +1193,64 @@ public class TestFlatStyleableInfo
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatDropShadowBorder() {
FlatDropShadowBorder border = new FlatDropShadowBorder();
Map<String, Class<?>> expected = expectedMap(
"shadowColor", Color.class,
"shadowInsets", Insets.class,
"shadowOpacity", float.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatMenuBarBorder() {
FlatMenuBarBorder border = new FlatMenuBarBorder();
Map<String, Class<?>> expected = expectedMap(
"borderColor", Color.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatPopupMenuBorder() {
FlatPopupMenuBorder border = new FlatPopupMenuBorder();
Map<String, Class<?>> expected = expectedMap(
"borderInsets", Insets.class,
"borderColor", Color.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatInternalFrameBorder() {
FlatInternalFrameBorder border = new FlatInternalFrameBorder();
Map<String, Class<?>> expected = expectedMap(
"activeBorderColor", Color.class,
"inactiveBorderColor", Color.class,
"borderLineWidth", int.class,
"dropShadowPainted", boolean.class,
"borderMargins", Insets.class,
"activeDropShadowColor", Color.class,
"activeDropShadowInsets", Insets.class,
"activeDropShadowOpacity", float.class,
"inactiveDropShadowColor", Color.class,
"inactiveDropShadowInsets", Insets.class,
"inactiveDropShadowOpacity", float.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
//---- icons --------------------------------------------------------------
@Test
@@ -1161,6 +1280,8 @@ public class TestFlatStyleableInfo
private void flatCheckBoxIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"scale", float.class,
"focusWidth", float.class,
"focusColor", Color.class,
"borderWidth", float.class,
@@ -1245,6 +1366,8 @@ public class TestFlatStyleableInfo
private void flatCheckBoxMenuItemIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"scale", float.class,
"checkmarkColor", Color.class,
"disabledCheckmarkColor", Color.class,
"selectionForeground", Color.class
@@ -1263,6 +1386,8 @@ public class TestFlatStyleableInfo
private void flatMenuArrowIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"scale", float.class,
"arrowType", String.class,
"arrowColor", Color.class,
"disabledArrowColor", Color.class,
@@ -1275,6 +1400,8 @@ public class TestFlatStyleableInfo
FlatHelpButtonIcon icon = new FlatHelpButtonIcon();
Map<String, Class<?>> expected = expectedMap(
"scale", float.class,
"focusWidth", int.class,
"focusColor", Color.class,
"innerFocusWidth", float.class,
@@ -1295,4 +1422,87 @@ public class TestFlatStyleableInfo
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatClearIcon() {
FlatClearIcon icon = new FlatClearIcon();
Map<String, Class<?>> expected = expectedMap(
"clearIconScale", float.class,
"clearIconColor", Color.class,
"clearIconHoverColor", Color.class,
"clearIconPressedColor", Color.class
);
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatSearchIcon() {
FlatSearchIcon icon = new FlatSearchIcon();
Map<String, Class<?>> expected = new LinkedHashMap<>();
flatSearchIcon( expected );
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatSearchWithHistoryIcon() {
FlatSearchWithHistoryIcon icon = new FlatSearchWithHistoryIcon();
Map<String, Class<?>> expected = new LinkedHashMap<>();
flatSearchIcon( expected );
assertMapEquals( expected, icon.getStyleableInfos() );
}
private void flatSearchIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"searchIconScale", float.class,
"searchIconColor", Color.class,
"searchIconHoverColor", Color.class,
"searchIconPressedColor", Color.class
);
}
@Test
void flatCapsLockIcon() {
FlatCapsLockIcon icon = new FlatCapsLockIcon();
Map<String, Class<?>> expected = expectedMap(
"capsLockIconScale", float.class,
"capsLockIconColor", Color.class
);
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatTabbedPaneCloseIcon() {
FlatTabbedPaneCloseIcon icon = new FlatTabbedPaneCloseIcon();
Map<String, Class<?>> expected = expectedMap(
//TODO closeScale ?
// "scale", float.class,
"closeScale", float.class,
"closeSize", Dimension.class,
"closeArc", int.class,
"closeCrossPlainSize", float.class,
"closeCrossFilledSize", float.class,
"closeCrossLineWidth", float.class,
"closeBackground", Color.class,
"closeForeground", Color.class,
"closeHoverBackground", Color.class,
"closeHoverForeground", Color.class,
"closePressedBackground", Color.class,
"closePressedForeground", Color.class
);
assertMapEquals( expected, icon.getStyleableInfos() );
}
}

View File

@@ -17,9 +17,15 @@
package com.formdev.flatlaf.ui;
import java.awt.Font;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.swing.UIManager;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLightLaf;
@@ -57,12 +63,57 @@ public class TestUtils
public static void assertMapEquals( Map<?, ?> expected, Map<?, ?> actual ) {
if( !Objects.equals( expected, actual ) ) {
String expectedStr = String.valueOf( expected ).replace( ", ", ",\n" );
String actualStr = String.valueOf( actual ).replace( ", ", ",\n" );
String expectedStr = String.valueOf( new TreeMap<>( expected ) ).replace( ", ", ",\n" );
String actualStr = String.valueOf( new TreeMap<>( actual ) ).replace( ", ", ",\n" );
String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr );
// pass expected/actual strings to exception for nice diff in IDE
throw new AssertionFailedError( msg, expectedStr, actualStr );
}
}
public static void assertSetEquals( Set<?> expected, Set<?> actual, String message ) {
if( !Objects.equals( expected, actual ) ) {
String expectedStr = String.valueOf( new TreeSet<>( expected ) ).replace( ", ", ",\n" );
String actualStr = String.valueOf( new TreeSet<>( actual ) ).replace( ", ", ",\n" );
String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr );
if( message != null )
msg = message + " ==> " + msg;
// pass expected/actual strings to exception for nice diff in IDE
throw new AssertionFailedError( msg, expectedStr, actualStr );
}
}
public static void checkImplementedTests( Set<String> excludes, Class<?> baseClass, Class<?>... classes ) {
Set<String> expected = getTestMethods( baseClass );
for( Class<?> cls : classes ) {
Set<String> actual = getTestMethods( cls );
for( String methodName : expected ) {
if( !actual.contains( methodName ) && !excludes.contains( methodName ) ) {
throw new AssertionFailedError( "missing " + cls.getSimpleName() + '.' + methodName
+ "() for " + baseClass.getSimpleName() + '.' + methodName + "()" );
}
}
for( String methodName : actual ) {
if( !expected.contains( methodName ) && !excludes.contains( methodName ) ) {
throw new AssertionFailedError( "missing " + baseClass.getSimpleName() + '.' + methodName
+ "() for " + cls.getSimpleName() + '.' + methodName + "()" );
}
}
}
}
private static Set<String> getTestMethods( Class<?> cls ) {
HashSet<String> tests = new HashSet<>();
Method[] methods = cls.getDeclaredMethods();
for( Method m : methods ) {
if( m.isAnnotationPresent( Test.class ) )
tests.add( m.getName() );
}
return tests;
}
}

View File

@@ -0,0 +1,343 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import java.awt.Font;
import java.util.Collections;
import java.util.Map;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.metal.MetalLookAndFeel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.FlatSystemProperties;
/**
* @author Karl Tauber
*/
public class TestUIScale
{
private static Map<String, String> FONT_EXTRA_DEFAULTS_1x = Collections.singletonMap(
"defaultFont", "{instance}java.awt.Font,Dialog,0,12" );
private static Map<String, String> FONT_EXTRA_DEFAULTS_1_5x = Collections.singletonMap(
"defaultFont", "{instance}java.awt.Font,Dialog,0,18" );
@BeforeAll
static void setup() {
UIScale.inUnitTests = true;
// disable platform specific fonts
System.setProperty( "flatlaf.uiScale.fontSizeDivider", "12" );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x );
}
@AfterAll
static void cleanup() throws UnsupportedLookAndFeelException {
System.clearProperty( "flatlaf.uiScale.fontSizeDivider" );
FlatLaf.setGlobalExtraDefaults( null );
UIScale.inUnitTests = false;
}
@AfterEach
void afterEach() throws UnsupportedLookAndFeelException {
UIManager.setLookAndFeel( new MetalLookAndFeel() );
UIManager.put( "defaultFont", null );
UIManager.put( "Label.font", null );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x );
UIScale.tests_uninitialize();
}
@Test
void testCustomScaleFactor() {
System.setProperty( FlatSystemProperties.UI_SCALE, "1.25x" );
assertScaleFactor( 1.25f );
System.setProperty( FlatSystemProperties.UI_SCALE, "2x" );
UIScale.tests_uninitialize();
assertScaleFactor( 2f );
System.clearProperty( FlatSystemProperties.UI_SCALE );
}
@Test
void testLabelFontScaling() {
assertInstanceOf( MetalLookAndFeel.class, UIManager.getLookAndFeel() );
testLabelFont( 8, 1f );
testLabelFont( 9, 1f );
testLabelFont( 10, 1f );
testLabelFont( 11, 1f );
testLabelFont( 12, 1f );
testLabelFont( 13, 1f );
testLabelFont( 14, 1.25f );
testLabelFont( 15, 1.25f );
testLabelFont( 16, 1.25f );
testLabelFont( 17, 1.5f );
testLabelFont( 18, 1.5f );
testLabelFont( 19, 1.5f );
testLabelFont( 20, 1.75f );
testLabelFont( 21, 1.75f );
testLabelFont( 22, 1.75f );
testLabelFont( 23, 2f );
testLabelFont( 24, 2f );
testLabelFont( 25, 2f );
testLabelFont( 26, 2.25f );
}
private void testLabelFont( int fontSize, float expectedScaleFactor ) {
UIManager.put( "Label.font", new Font( Font.DIALOG, Font.PLAIN, fontSize ) );
assertScaleFactor( expectedScaleFactor );
}
@Test
void testDefaultFontScaling() {
FlatLightLaf.setup();
testDefaultFont( 8, 1f );
testDefaultFont( 9, 1f );
testDefaultFont( 10, 1f );
testDefaultFont( 11, 1f );
testDefaultFont( 12, 1f );
testDefaultFont( 13, 1f );
testDefaultFont( 14, 1.25f );
testDefaultFont( 15, 1.25f );
testDefaultFont( 16, 1.25f );
testDefaultFont( 17, 1.5f );
testDefaultFont( 18, 1.5f );
testDefaultFont( 19, 1.5f );
testDefaultFont( 20, 1.75f );
testDefaultFont( 21, 1.75f );
testDefaultFont( 22, 1.75f );
testDefaultFont( 23, 2f );
testDefaultFont( 24, 2f );
testDefaultFont( 25, 2f );
testDefaultFont( 26, 2.25f );
}
private void testDefaultFont( int fontSize, float expectedScaleFactor ) {
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, fontSize ) );
assertScaleFactor( expectedScaleFactor );
}
@Test
void testInitialScaleFactorAndFontSizes() {
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1f, 12, -1 );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
FlatDarkLaf.setup();
assertScaleFactorAndFontSizes( 1.5f, 18, -1 );
}
@Test
void zoom_Metal() {
UIScale.setZoomFactor( 1.1f );
assertScaleFactor( 1.1f );
UIScale.setZoomFactor( 1.3f );
assertScaleFactor( 1.3f );
UIScale.setZoomFactor( 2.3f );
assertScaleFactor( 2.3f );
}
@Test
void zoom_1x() {
FlatLightLaf.setup();
testZoom( 0.7f, 0.7f, 8, -1 );
testZoom( 0.75f, 0.75f, 9, -1 );
testZoom( 0.8f, 0.8f, 10, -1 );
testZoom( 0.9f, 0.9f, 11, -1 );
testZoom( 1f, 1f, 12, -1 );
testZoom( 1.1f, 1.1f, 13, -1 );
testZoom( 1.2f, 1.2f, 14, -1 );
testZoom( 1.25f, 1.25f, 15, -1 );
testZoom( 1.3f, 1.3f, 16, -1 );
testZoom( 1.4f, 1.4f, 17, -1 );
testZoom( 1.5f, 1.5f, 18, -1 );
testZoom( 1.6f, 1.6f, 19, -1 );
testZoom( 1.7f, 1.7f, 20, -1 );
testZoom( 1.75f, 1.75f, 21, -1 );
testZoom( 1.8f, 1.8f, 22, -1 );
testZoom( 1.9f, 1.9f, 23, -1 );
testZoom( 2f, 2f, 24, -1 );
testZoom( 2.25f, 2.25f, 27, -1 );
testZoom( 2.5f, 2.5f, 30, -1 );
testZoom( 2.75f, 2.75f, 33, -1 );
testZoom( 3f, 3f, 36, -1 );
testZoom( 4f, 4f, 48, -1 );
}
@Test
void zoom_1_5x() {
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
FlatLightLaf.setup();
testZoom( 0.7f, 1.05f, 13, -1 );
testZoom( 0.75f, 1.13f, 14, -1 );
testZoom( 0.8f, 1.2f, 14, -1 );
testZoom( 0.9f, 1.35f, 16, -1 );
testZoom( 1f, 1.5f, 18, -1 );
testZoom( 1.1f, 1.65f, 20, -1 );
testZoom( 1.2f, 1.8f, 22, -1 );
testZoom( 1.25f, 1.88f, 23, -1 );
testZoom( 1.3f, 1.95f, 23, -1 );
testZoom( 1.4f, 2.1f, 25, -1 );
testZoom( 1.5f, 2.25f, 27, -1 );
testZoom( 1.6f, 2.4f, 29, -1 );
testZoom( 1.7f, 2.55f, 31, -1 );
testZoom( 1.75f, 2.63f, 32, -1 );
testZoom( 1.8f, 2.7f, 32, -1 );
testZoom( 1.9f, 2.85f, 34, -1 );
testZoom( 2f, 3f, 36, -1 );
testZoom( 2.25f, 3.38f, 41, -1 );
testZoom( 2.5f, 3.75f, 45, -1 );
testZoom( 2.75f, 4.13f, 50, -1 );
testZoom( 3f, 4.5f, 54, -1 );
testZoom( 4f, 6f, 72, -1 );
}
@Test
void zoomAppFont_1x() {
FlatLightLaf.setup();
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, 14 ) );
testZoom( 1f, 1.25f, 12, 14 );
testZoom( 1.1f, 1.38f, 13, 15 );
testZoom( 1.25f, 1.56f, 15, 17 );
testZoom( 1.5f, 1.88f, 18, 20 );
testZoom( 1.75f, 2.19f, 21, 23 );
testZoom( 2f, 2.5f, 24, 26 );
testZoom( 1f, 1.25f, 12, 13 );
testZoom( 2f, 2.5f, 24, 26 );
}
@Test
void zoomWithLafChange() {
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1f, 12, -1 );
testZoom( 1.1f, 1.1f, 13, -1 );
FlatDarkLaf.setup();
assertScaleFactorAndFontSizes( 1.1f, 13, -1 );
testZoom( 1.2f, 1.2f, 14, -1 );
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1.2f, 14, -1 );
testZoom( 1.3f, 1.3f, 16, -1 );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
FlatDarkLaf.setup();
assertScaleFactorAndFontSizes( 1.95f, 23, -1 );
testZoom( 1.4f, 2.1f, 25, -1 );
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 2.1f, 25, -1 );
testZoom( 1.5f, 2.25f, 27, -1 );
}
@Test
void zoomWithDefaultFontChange() {
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1f, 12, -1 );
float zoom1 = 1.4f;
testZoom( zoom1, zoom1, 17, -1 );
testDefaultFont( 8, z( zoom1, 1f ) );
testDefaultFont( 9, z( zoom1, 1f ) );
testDefaultFont( 10, z( zoom1, 1f ) );
testDefaultFont( 11, z( zoom1, 1f ) );
testDefaultFont( 12, z( zoom1, 1f ) );
testDefaultFont( 13, z( zoom1, 1f ) );
testDefaultFont( 14, z( zoom1, 1.25f ) );
testDefaultFont( 15, z( zoom1, 1.25f ) );
testDefaultFont( 16, z( zoom1, 1.25f ) );
testDefaultFont( 17, z( zoom1, 1.5f ) );
testDefaultFont( 18, z( zoom1, 1.5f ) );
testDefaultFont( 19, z( zoom1, 1.5f ) );
testDefaultFont( 20, z( zoom1, 1.75f ) );
testDefaultFont( 21, z( zoom1, 1.75f ) );
testDefaultFont( 22, z( zoom1, 1.75f ) );
testDefaultFont( 23, z( zoom1, 2f ) );
testDefaultFont( 24, z( zoom1, 2f ) );
testDefaultFont( 25, z( zoom1, 2f ) );
testDefaultFont( 26, z( zoom1, 2.25f ) );
float zoom2 = 1.8f;
testZoom( zoom2, 4.05f, 22, 33 );
testDefaultFont( 8, z( zoom2, 1f ) );
testDefaultFont( 9, z( zoom2, 1f ) );
testDefaultFont( 10, z( zoom2, 1f ) );
testDefaultFont( 11, z( zoom2, 1f ) );
testDefaultFont( 12, z( zoom2, 1f ) );
testDefaultFont( 13, z( zoom2, 1f ) );
testDefaultFont( 14, z( zoom2, 1.25f ) );
testDefaultFont( 15, z( zoom2, 1.25f ) );
testDefaultFont( 16, z( zoom2, 1.25f ) );
testDefaultFont( 17, z( zoom2, 1.5f ) );
testDefaultFont( 18, z( zoom2, 1.5f ) );
testDefaultFont( 19, z( zoom2, 1.5f ) );
testDefaultFont( 20, z( zoom2, 1.75f ) );
testDefaultFont( 21, z( zoom2, 1.75f ) );
testDefaultFont( 22, z( zoom2, 1.75f ) );
testDefaultFont( 23, z( zoom2, 2f ) );
testDefaultFont( 24, z( zoom2, 2f ) );
testDefaultFont( 25, z( zoom2, 2f ) );
testDefaultFont( 26, z( zoom2, 2.25f ) );
}
private static float z( float zoom, float scale ) {
// round scale factor to 1/100
return Math.round( (zoom * scale) * 100f ) / 100f;
}
private static void testZoom( float zoomFactor, float expectedScaleFactor,
int expectedLafFontSize, int expectedAppFontSize )
{
UIScale.setZoomFactor( zoomFactor );
assertScaleFactorAndFontSizes( expectedScaleFactor, expectedLafFontSize, expectedAppFontSize );
}
private static void assertScaleFactorAndFontSizes( float expectedScaleFactor,
int expectedLafFontSize, int expectedAppFontSize )
{
assertScaleFactor( expectedScaleFactor );
Font lafFont = UIManager.getLookAndFeelDefaults().getFont( "defaultFont" );
Font appFont = UIManager.getFont( "defaultFont" );
assertEquals( expectedLafFontSize, lafFont.getSize() );
if( expectedAppFontSize > 0 ) {
assertNotEquals( lafFont, appFont );
assertEquals( expectedAppFontSize, appFont.getSize() );
} else
assertEquals( lafFont, appFont );
}
private static void assertScaleFactor( float expectedScaleFactor ) {
assertEquals( expectedScaleFactor, UIScale.getUserScaleFactor() );
}
}

View File

@@ -43,15 +43,13 @@ tasks {
dependsOn( ":flatlaf-intellij-themes:jar" )
// dependsOn( ":flatlaf-natives-jna:jar" )
manifest {
attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" )
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
attributes( "Multi-Release" to "true" )
manifest.attributes(
"Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo",
"Multi-Release" to "true",
// allow loading FlatLaf native library in Java 24+ (see https://openjdk.org/jeps/472)
attributes( "Enable-Native-Access" to "ALL-UNNAMED" )
}
"Enable-Native-Access" to "ALL-UNNAMED",
)
exclude( "module-info.class" )
exclude( "META-INF/versions/*/module-info.class" )

View File

@@ -111,6 +111,10 @@ class ControlBar
UIScale.addPropertyChangeListener( e -> {
// update info label because user scale factor may change
updateInfoLabel();
// update "Font" menu (e.g. if zoom factor changed)
if( UIScale.PROP_ZOOM_FACTOR.equals( e.getPropertyName() ) )
frame.updateFontMenuItems();
} );
}
@@ -192,13 +196,15 @@ class ControlBar
String javaVendor = System.getProperty( "java.vendor" );
if( "Oracle Corporation".equals( javaVendor ) )
javaVendor = null;
float zoomFactor = UIScale.getZoomFactor();
double systemScaleFactor = UIScale.getSystemScaleFactor( getGraphicsConfiguration() );
float userScaleFactor = UIScale.getUserScaleFactor();
Font font = UIManager.getFont( "Label.font" );
String newInfo = "(Java " + System.getProperty( "java.version" )
+ (javaVendor != null ? ("; " + javaVendor) : "")
+ (systemScaleFactor != 1 ? ("; system scale factor " + systemScaleFactor) : "")
+ (userScaleFactor != 1 ? ("; user scale factor " + userScaleFactor) : "")
+ (zoomFactor != 1 ? ("; zoom " + zoomFactor) : "")
+ (systemScaleFactor != 1 ? ("; system scale " + systemScaleFactor) : "")
+ (userScaleFactor != 1 ? ("; user scale " + userScaleFactor) : "")
+ (systemScaleFactor == 1 && userScaleFactor == 1 ? "; no scaling" : "")
+ "; " + font.getFamily() + " " + font.getSize()
+ (font.isBold() ? " BOLD" : "")

View File

@@ -18,12 +18,14 @@ package com.formdev.flatlaf.demo;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Year;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.prefs.Preferences;
import javax.swing.*;
import javax.swing.text.DefaultEditorKit;
@@ -45,11 +47,14 @@ import com.formdev.flatlaf.extras.components.FlatButton.ButtonType;
import com.formdev.flatlaf.icons.FlatAbstractIcon;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.extras.FlatSVGUtils;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemFileChooser;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
import net.miginfocom.layout.ConstraintParser;
import net.miginfocom.layout.LC;
import net.miginfocom.layout.UnitValue;
@@ -71,6 +76,7 @@ class DemoFrame
Arrays.sort( availableFontFamilyNames );
initComponents();
initZommMenuItems();
updateFontMenuItems();
initAccentColors();
initFullWindowContent();
@@ -172,6 +178,48 @@ class DemoFrame
chooser.showSaveDialog( this );
}
private void openSystemActionPerformed() {
SystemFileChooser chooser = new SystemFileChooser();
chooser.setMultiSelectionEnabled( true );
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
"Text Files", "txt", "md" ) );
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
"PDF Files", "pdf" ) );
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
"Archives", "zip", "tar", "jar", "7z" ) );
if( chooser.showOpenDialog( this ) != SystemFileChooser.APPROVE_OPTION )
return;
File[] files = chooser.getSelectedFiles();
System.out.println( Arrays.toString( files ).replace( ",", "\n" ) );
}
private void saveAsSystemActionPerformed() {
SystemFileChooser chooser = new SystemFileChooser();
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
"Text Files", "txt", "md" ) );
chooser.addChoosableFileFilter( new SystemFileChooser.FileNameExtensionFilter(
"Images", "png", "gif", "jpg" ) );
if( chooser.showSaveDialog( this ) != SystemFileChooser.APPROVE_OPTION )
return;
File file = chooser.getSelectedFile();
System.out.println( file );
}
private void selectFolderSystemActionPerformed() {
SystemFileChooser chooser = new SystemFileChooser();
chooser.setFileSelectionMode( SystemFileChooser.DIRECTORIES_ONLY );
if( chooser.showOpenDialog( this ) != SystemFileChooser.APPROVE_OPTION )
return;
File directory = chooser.getSelectedFile();
System.out.println( directory );
}
private void exitActionPerformed() {
dispose();
}
@@ -286,6 +334,92 @@ class DemoFrame
showHints();
}
private void initZommMenuItems() {
float currentZoomFactor = UIScale.getZoomFactor();
UIScale.setSupportedZoomFactors( new float[] { 0.7f, 0.8f, 0.9f, 1f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.75f, 2f } );
ButtonGroup group = new ButtonGroup();
HashMap<Float, JCheckBoxMenuItem> items = new HashMap<>();
// add supported zoom factors to "Zoom" menu
zoomMenu.addSeparator();
for( float zoomFactor : UIScale.getSupportedZoomFactors() ) {
JCheckBoxMenuItem item = new JCheckBoxMenuItem( (int)(zoomFactor * 100) + "%" );
item.setSelected( zoomFactor == currentZoomFactor );
item.addActionListener( this::zoomFactorChanged );
zoomMenu.add( item );
group.add( item );
items.put( zoomFactor, item );
}
// update menu item selection if zoom factor changed
UIScale.addPropertyChangeListener( e -> {
if( UIScale.PROP_ZOOM_FACTOR.equals( e.getPropertyName() ) ) {
float newZoomFactor = UIScale.getZoomFactor();
JCheckBoxMenuItem item = items.get( newZoomFactor );
if( item != null )
item.setSelected( true );
zoomWindowBounds( this, (float) e.getOldValue(), (float) e.getNewValue() );
}
} );
}
private static void zoomWindowBounds( Window window, float oldZoomFactor, float newZoomFactor ) {
if( window instanceof Frame && ((Frame)window).getExtendedState() != Frame.NORMAL )
return;
Rectangle oldBounds = window.getBounds();
// zoom window bounds
float factor = (1f / oldZoomFactor) * newZoomFactor;
int newWidth = (int) (oldBounds.width * factor);
int newHeight = (int) (oldBounds.height * factor);
int newX = oldBounds.x - ((newWidth - oldBounds.width) / 2);
int newY = oldBounds.y - ((newHeight - oldBounds.height) / 2);
// get maximum window bounds (screen bounds minus screen insets)
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
Rectangle maxBounds = FlatUIUtils.subtractInsets( screenBounds, screenInsets );
// limit new window width/height
newWidth = Math.min( newWidth, maxBounds.width );
newHeight = Math.min( newHeight, maxBounds.height );
// move window into screen bounds
newX = Math.max( Math.min( newX, maxBounds.width - newWidth ), maxBounds.x );
newY = Math.max( Math.min( newY, maxBounds.height - newHeight ), maxBounds.y );
// set new window bounds
window.setBounds( newX, newY, newWidth, newHeight );
}
private void zoomFactorChanged( ActionEvent e ) {
String zoomFactor = e.getActionCommand();
float zoom = Integer.parseInt( zoomFactor.substring( 0, zoomFactor.length() - 1 ) ) / 100f;
if( UIScale.setZoomFactor( zoom ) )
FlatLaf.updateUI();
}
private void zoomReset() {
if( UIScale.zoomReset() )
FlatLaf.updateUI();
}
private void zoomIn() {
if( UIScale.zoomIn() )
FlatLaf.updateUI();
}
private void zoomOut() {
if( UIScale.zoomOut() )
FlatLaf.updateUI();
}
private void fontFamilyChanged( ActionEvent e ) {
String fontFamily = e.getActionCommand();
@@ -508,6 +642,9 @@ class DemoFrame
JMenuItem newMenuItem = new JMenuItem();
JMenuItem openMenuItem = new JMenuItem();
JMenuItem saveAsMenuItem = new JMenuItem();
JMenuItem openSystemMenuItem = new JMenuItem();
JMenuItem saveAsSystemMenuItem = new JMenuItem();
JMenuItem selectFolderSystemMenuItem = new JMenuItem();
JMenuItem closeMenuItem = new JMenuItem();
exitMenuItem = new JMenuItem();
JMenu editMenu = new JMenu();
@@ -533,6 +670,10 @@ class DemoFrame
JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem();
JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem();
JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem();
zoomMenu = new JMenu();
JMenuItem resetZoomMenuItem = new JMenuItem();
JMenuItem incrZoomMenuItem = new JMenuItem();
JMenuItem decrZoomMenuItem = new JMenuItem();
fontMenu = new JMenu();
JMenuItem restoreFontMenuItem = new JMenuItem();
JMenuItem incrFontMenuItem = new JMenuItem();
@@ -608,6 +749,25 @@ class DemoFrame
fileMenu.add(saveAsMenuItem);
fileMenu.addSeparator();
//---- openSystemMenuItem ----
openSystemMenuItem.setText("Open (System)...");
openSystemMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.SHIFT_DOWN_MASK));
openSystemMenuItem.addActionListener(e -> openSystemActionPerformed());
fileMenu.add(openSystemMenuItem);
//---- saveAsSystemMenuItem ----
saveAsSystemMenuItem.setText("Save As (System)...");
saveAsSystemMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.SHIFT_DOWN_MASK));
saveAsSystemMenuItem.addActionListener(e -> saveAsSystemActionPerformed());
fileMenu.add(saveAsSystemMenuItem);
//---- selectFolderSystemMenuItem ----
selectFolderSystemMenuItem.setText("Select Folder (System)...");
selectFolderSystemMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.SHIFT_DOWN_MASK));
selectFolderSystemMenuItem.addActionListener(e -> selectFolderSystemActionPerformed());
fileMenu.add(selectFolderSystemMenuItem);
fileMenu.addSeparator();
//---- closeMenuItem ----
closeMenuItem.setText("Close");
closeMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
@@ -778,25 +938,49 @@ class DemoFrame
}
menuBar.add(viewMenu);
//======== zoomMenu ========
{
zoomMenu.setText("Zoom");
//---- resetZoomMenuItem ----
resetZoomMenuItem.setText("Reset Zoom");
resetZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
resetZoomMenuItem.addActionListener(e -> zoomReset());
zoomMenu.add(resetZoomMenuItem);
//---- incrZoomMenuItem ----
incrZoomMenuItem.setText("Zoom In");
incrZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
incrZoomMenuItem.addActionListener(e -> zoomIn());
zoomMenu.add(incrZoomMenuItem);
//---- decrZoomMenuItem ----
decrZoomMenuItem.setText("Zoom Out");
decrZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
decrZoomMenuItem.addActionListener(e -> zoomOut());
zoomMenu.add(decrZoomMenuItem);
}
menuBar.add(zoomMenu);
//======== fontMenu ========
{
fontMenu.setText("Font");
//---- restoreFontMenuItem ----
restoreFontMenuItem.setText("Restore Font");
restoreFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
restoreFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK));
restoreFontMenuItem.addActionListener(e -> restoreFont());
fontMenu.add(restoreFontMenuItem);
//---- incrFontMenuItem ----
incrFontMenuItem.setText("Increase Font Size");
incrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
incrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK));
incrFontMenuItem.addActionListener(e -> incrFont());
fontMenu.add(incrFontMenuItem);
//---- decrFontMenuItem ----
decrFontMenuItem.setText("Decrease Font Size");
decrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
decrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK));
decrFontMenuItem.addActionListener(e -> decrFont());
fontMenu.add(decrFontMenuItem);
}
@@ -1045,6 +1229,7 @@ class DemoFrame
private JMenuItem exitMenuItem;
private JMenu scrollingPopupMenu;
private JMenuItem htmlMenuItem;
private JMenu zoomMenu;
private JMenu fontMenu;
private JMenu optionsMenu;
private JCheckBoxMenuItem windowDecorationsCheckBoxMenuItem;

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8"
JFDML JFormDesigner: "8.3" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -182,6 +182,27 @@ new FormModel {
"mnemonic": 83
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsActionPerformed", false ) )
} )
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
name: "separator9"
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "openSystemMenuItem"
"text": "Open (System)..."
"accelerator": static javax.swing.KeyStroke getKeyStroke( 79, 4291, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openSystemActionPerformed", false ) )
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "saveAsSystemMenuItem"
"text": "Save As (System)..."
"accelerator": static javax.swing.KeyStroke getKeyStroke( 83, 4291, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "saveAsSystemActionPerformed", false ) )
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "selectFolderSystemMenuItem"
"text": "Select Folder (System)..."
"accelerator": static javax.swing.KeyStroke getKeyStroke( 70, 4291, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "selectFolderSystemActionPerformed", false ) )
} )
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
name: "separator2"
} )
@@ -362,6 +383,31 @@ new FormModel {
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
} )
} )
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "zoomMenu"
"text": "Zoom"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "resetZoomMenuItem"
"text": "Reset Zoom"
"accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4226, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomReset", false ) )
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "incrZoomMenuItem"
"text": "Zoom In"
"accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4226, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomIn", false ) )
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "decrZoomMenuItem"
"text": "Zoom Out"
"accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4226, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomOut", false ) )
} )
} )
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "fontMenu"
"text": "Font"
@@ -371,19 +417,19 @@ new FormModel {
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "restoreFontMenuItem"
"text": "Restore Font"
"accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4226, false )
"accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4746, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "restoreFont", false ) )
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "incrFontMenuItem"
"text": "Increase Font Size"
"accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4226, false )
"accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4746, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "incrFont", false ) )
} )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "decrFontMenuItem"
"text": "Decrease Font Size"
"accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4226, false )
"accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4746, false )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decrFont", false ) )
} )
} )

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.demo;
import java.awt.Dimension;
import java.util.prefs.Preferences;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
@@ -27,6 +28,7 @@ import com.formdev.flatlaf.fonts.inter.FlatInterFont;
import com.formdev.flatlaf.fonts.jetbrains_mono.FlatJetBrainsMonoFont;
import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont;
import com.formdev.flatlaf.fonts.roboto_mono.FlatRobotoMonoFont;
import com.formdev.flatlaf.util.SystemFileChooser;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -73,6 +75,28 @@ public class FlatLafDemo
DemoPrefs.init( PREFS_ROOT_PATH );
DemoPrefs.initSystemScale();
// SystemFileChooser state storage
SystemFileChooser.setStateStore( new SystemFileChooser.StateStore() {
private static final String KEY_PREFIX = "fileChooser.";
private final Preferences state = Preferences.userRoot().node( PREFS_ROOT_PATH );
@Override
public String get( String key, String def ) {
String value = state.get( KEY_PREFIX + key, def );
System.out.println( "SystemFileChooser State GET " + key + " = " + value );
return value;
}
@Override
public void put( String key, String value ) {
System.out.println( "SystemFileChooser State PUT " + key + " = " + value );
if( value != null )
state.put( KEY_PREFIX + key, value );
else
state.remove( KEY_PREFIX + key );
}
} );
SwingUtilities.invokeLater( () -> {
// install fonts for lazy loading
FlatInterFont.installLazy();

View File

@@ -69,6 +69,7 @@ class TabsPanel
initTabWidthMode( widthPreferredTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_PREFERRED );
initTabWidthMode( widthEqualTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_EQUAL );
initTabWidthMode( widthCompactTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_COMPACT );
initTabWidthMode( widthIconOnlyTabbedPane, TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY );
}
private void initTabPlacementTabs( JTabbedPane tabbedPane ) {
@@ -262,7 +263,9 @@ class TabsPanel
private void initTabWidthMode( JTabbedPane tabbedPane, String tabWidthMode ) {
tabbedPane.putClientProperty( TABBED_PANE_TAB_WIDTH_MODE, tabWidthMode );
if( tabWidthMode.equals( TABBED_PANE_TAB_WIDTH_MODE_COMPACT ) ) {
if( tabWidthMode.equals( TABBED_PANE_TAB_WIDTH_MODE_COMPACT ) ||
tabWidthMode.equals( TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY ) )
{
tabbedPane.addTab( "Search", new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/search.svg", 16, 16 ), null );
tabbedPane.addTab( "Recents", new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/RecentlyUsed.svg", 16, 16 ), null );
tabbedPane.addTab( "Favorites", new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/favorite.svg", 16, 16 ), null );
@@ -390,6 +393,7 @@ class TabsPanel
widthPreferredTabbedPane = new JTabbedPane();
widthEqualTabbedPane = new JTabbedPane();
widthCompactTabbedPane = new JTabbedPane();
widthIconOnlyTabbedPane = new JTabbedPane();
JLabel minMaxTabWidthLabel = new JLabel();
minimumTabWidthTabbedPane = new JTabbedPane();
maximumTabWidthTabbedPane = new JTabbedPane();
@@ -684,6 +688,7 @@ class TabsPanel
"[]" +
"[]" +
"[]" +
"[]" +
"[]para" +
"[]" +
"[]" +
@@ -697,25 +702,56 @@ class TabsPanel
panel3.add(tabWidthModeLabel, "cell 0 0");
//---- tabWidthModeNoteLabel ----
tabWidthModeNoteLabel.setText("(preferred/equal/compact)");
tabWidthModeNoteLabel.setText("(preferred/equal/compact/iconOnly)");
tabWidthModeNoteLabel.setEnabled(false);
tabWidthModeNoteLabel.putClientProperty(FlatClientProperties.STYLE_CLASS, "small");
panel3.add(tabWidthModeNoteLabel, "cell 0 1");
//======== widthPreferredTabbedPane ========
{
widthPreferredTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel3.add(widthPreferredTabbedPane, "cell 0 2");
//======== widthEqualTabbedPane ========
{
widthEqualTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel3.add(widthEqualTabbedPane, "cell 0 3");
//======== widthCompactTabbedPane ========
{
widthCompactTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel3.add(widthCompactTabbedPane, "cell 0 4");
//======== widthIconOnlyTabbedPane ========
{
widthIconOnlyTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel3.add(widthIconOnlyTabbedPane, "cell 0 5");
//---- minMaxTabWidthLabel ----
minMaxTabWidthLabel.setText("Minimum/maximum tab width");
minMaxTabWidthLabel.putClientProperty(FlatClientProperties.STYLE_CLASS, "h3");
panel3.add(minMaxTabWidthLabel, "cell 0 5");
panel3.add(minimumTabWidthTabbedPane, "cell 0 6");
panel3.add(maximumTabWidthTabbedPane, "cell 0 7");
panel3.add(minMaxTabWidthLabel, "cell 0 6");
//======== minimumTabWidthTabbedPane ========
{
minimumTabWidthTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel3.add(minimumTabWidthTabbedPane, "cell 0 7");
//======== maximumTabWidthTabbedPane ========
{
maximumTabWidthTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel3.add(maximumTabWidthTabbedPane, "cell 0 8");
//---- tabAlignmentLabel ----
tabAlignmentLabel.setText("Tab title alignment");
tabAlignmentLabel.putClientProperty(FlatClientProperties.STYLE_CLASS, "h3");
panel3.add(tabAlignmentLabel, "cell 0 8");
panel3.add(tabAlignmentLabel, "cell 0 9");
//======== panel5 ========
{
@@ -742,17 +778,33 @@ class TabsPanel
tabAlignmentNoteLabel2.setEnabled(false);
tabAlignmentNoteLabel2.putClientProperty(FlatClientProperties.STYLE_CLASS, "small");
panel5.add(tabAlignmentNoteLabel2, "cell 1 0,alignx right,growx 0");
//======== tabAlignLeadingTabbedPane ========
{
tabAlignLeadingTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel5.add(tabAlignLeadingTabbedPane, "cell 0 1");
//======== tabAlignVerticalTabbedPane ========
{
tabAlignVerticalTabbedPane.setTabPlacement(SwingConstants.LEFT);
tabAlignVerticalTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel5.add(tabAlignVerticalTabbedPane, "cell 1 1 1 4,growy");
//======== tabAlignCenterTabbedPane ========
{
tabAlignCenterTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel5.add(tabAlignCenterTabbedPane, "cell 0 2");
//======== tabAlignTrailingTabbedPane ========
{
tabAlignTrailingTabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 28);
}
panel5.add(tabAlignTrailingTabbedPane, "cell 0 3");
}
panel3.add(panel5, "cell 0 9");
panel3.add(panel5, "cell 0 10");
}
panel6.add(panel3, "cell 2 0");
}
@@ -1027,6 +1079,7 @@ class TabsPanel
private JTabbedPane widthPreferredTabbedPane;
private JTabbedPane widthEqualTabbedPane;
private JTabbedPane widthCompactTabbedPane;
private JTabbedPane widthIconOnlyTabbedPane;
private JTabbedPane minimumTabWidthTabbedPane;
private JTabbedPane maximumTabWidthTabbedPane;
private JPanel panel5;

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "8.2.0.0.331" Java: "21" encoding: "UTF-8"
JFDML JFormDesigner: "8.3" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -337,7 +337,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[]0[][][][]para[][][]para[]0[]"
"$rowConstraints": "[]0[][][][][]para[][][]para[]0[]"
} ) {
name: "panel3"
auxiliary() {
@@ -355,7 +355,7 @@ new FormModel {
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "tabWidthModeNoteLabel"
"text": "(preferred/equal/compact)"
"text": "(preferred/equal/compact/iconOnly)"
"enabled": false
"$client.FlatLaf.styleClass": "small"
auxiliary() {
@@ -366,19 +366,28 @@ new FormModel {
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "widthPreferredTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "widthEqualTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "widthCompactTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "widthIconOnlyTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 5"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "minMaxTabWidthLabel"
"text": "Minimum/maximum tab width"
@@ -386,19 +395,21 @@ new FormModel {
auxiliary() {
"JavaCodeGenerator.variableLocal": true
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 5"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "minimumTabWidthTabbedPane"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 6"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "maximumTabWidthTabbedPane"
name: "minimumTabWidthTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 7"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "maximumTabWidthTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 8"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "tabAlignmentLabel"
"text": "Tab title alignment"
@@ -407,7 +418,7 @@ new FormModel {
"JavaCodeGenerator.variableLocal": true
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 8"
"value": "cell 0 9"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$columnConstraints": "[grow,fill]para[fill]"
@@ -439,27 +450,31 @@ new FormModel {
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "tabAlignLeadingTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "tabAlignVerticalTabbedPane"
"tabPlacement": 2
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 1 1 4,growy"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "tabAlignCenterTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormContainer( "javax.swing.JTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) {
name: "tabAlignTrailingTabbedPane"
"$client.JTabbedPane.tabHeight": 28
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 9"
"value": "cell 0 10"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 0"

View File

@@ -43,7 +43,8 @@ public class FlatDesktop
public static boolean isSupported( Action action ) {
if( SystemInfo.isJava_9_orLater ) {
try {
return Desktop.getDesktop().isSupported( Enum.valueOf( Desktop.Action.class, action.name() ) );
return Desktop.isDesktopSupported() &&
Desktop.getDesktop().isSupported( Enum.valueOf( Desktop.Action.class, action.name() ) );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
return false;

View File

@@ -524,7 +524,9 @@ public class FlatSVGIcon
private URL getIconURL( String name, boolean dark ) {
if( dark ) {
int dotIndex = name.lastIndexOf( '.' );
name = name.substring( 0, dotIndex ) + "_dark" + name.substring( dotIndex );
name = (dotIndex > 0)
? name.substring( 0, dotIndex ) + "_dark" + name.substring( dotIndex )
: name + "_dark";
}
ClassLoader cl = (classLoader != null) ? classLoader : FlatSVGIcon.class.getClassLoader();

View File

@@ -30,6 +30,7 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Set;
import java.util.function.Predicate;
import java.util.prefs.Preferences;
@@ -202,6 +203,7 @@ public class FlatUIDefaultsInspector
JFrame frame = new JFrame();
frame.setTitle( "UI Defaults Inspector" );
frame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
frame.setModalExclusionType( Dialog.ModalExclusionType.TOOLKIT_EXCLUDE );
frame.addWindowListener( new WindowAdapter() {
@Override
public void windowClosed( WindowEvent e ) {
@@ -309,7 +311,7 @@ public class FlatUIDefaultsInspector
Set<Entry<Object, Object>> defaultsSet = defaults.entrySet();
ArrayList<Item> items = new ArrayList<>( defaultsSet.size() );
HashSet<Object> keys = new HashSet<>( defaultsSet.size() );
Color[] pBaseColor = new Color[1];
AtomicReference<Color> pBaseColor = new AtomicReference<>();
for( Entry<Object,Object> e : defaultsSet ) {
Object key = e.getKey();
@@ -335,7 +337,7 @@ public class FlatUIDefaultsInspector
if( value instanceof DerivedColor ) {
Color resolvedColor = resolveDerivedColor( defaults, (String) key, (DerivedColor) value, pBaseColor );
if( resolvedColor != value )
info = new Color[] { resolvedColor, pBaseColor[0] };
info = new Color[] { resolvedColor, pBaseColor.get() };
}
// check whether key was overridden using UIManager.put(key,value)
@@ -350,9 +352,9 @@ public class FlatUIDefaultsInspector
return items.toArray( new Item[items.size()] );
}
private Color resolveDerivedColor( UIDefaults defaults, String key, Color color, Color[] pBaseColor ) {
private Color resolveDerivedColor( UIDefaults defaults, String key, Color color, AtomicReference<Color> pBaseColor ) {
if( pBaseColor != null )
pBaseColor[0] = null;
pBaseColor.set( null );
if( !(color instanceof DerivedColor) )
return color;
@@ -376,7 +378,7 @@ public class FlatUIDefaultsInspector
baseColor = resolveDerivedColor( defaults, (String) baseKey, baseColor, null );
if( pBaseColor != null )
pBaseColor[0] = baseColor;
pBaseColor.set( baseColor );
Color newColor = FlatUIUtils.deriveColor( color, baseColor );
@@ -775,6 +777,9 @@ public class FlatUIDefaultsInspector
@SuppressWarnings( "FormatString" ) // Error Prone
private static String color2hex( Color color ) {
if( color == null )
return "";
int rgb = color.getRGB();
boolean hasAlpha = color.getAlpha() != 255;
@@ -1016,28 +1021,36 @@ public class FlatUIDefaultsInspector
item = (Item) value;
init( table, item.key, isSelected, row );
// reset background, foreground and icon
if( !(item.value instanceof Color) ) {
// get color of value
valueColor = null;
if( item.value instanceof Color )
valueColor = (item.info instanceof Color[]) ? ((Color[])item.info)[0] : (Color) item.value;
else if( item.value instanceof FlatLineBorder )
valueColor = ((FlatLineBorder)item.value).getLineColor();
// reset background and foreground
if( valueColor == null ) {
setBackground( null );
setForeground( null );
}
if( !(item.value instanceof Icon) )
setIcon( null );
// value to string
value = item.getValueAsString();
super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
if( item.value instanceof Color ) {
Color color = (item.info instanceof Color[]) ? ((Color[])item.info)[0] : (Color) item.value;
boolean isDark = new HSLColor( color ).getLuminance() < 70 && color.getAlpha() >= 128;
valueColor = color;
// set foreground, if value has color
if( valueColor != null ) {
boolean isDark = new HSLColor( valueColor ).getLuminance() < 70 && valueColor.getAlpha() >= 128;
setForeground( isDark ? Color.white : Color.black );
} else if( item.value instanceof Icon ) {
}
// set icon
if( item.value instanceof Icon ) {
Icon icon = (Icon) item.value;
setIcon( new SafeIcon( icon ) );
}
} else
setIcon( null );
// set tooltip
String toolTipText = (item.value instanceof Object[])
@@ -1054,7 +1067,7 @@ public class FlatUIDefaultsInspector
@Override
protected void paintComponent( Graphics g ) {
if( item.value instanceof Color ) {
if( valueColor != null ) {
int width = getWidth();
int height = getHeight();
Color background = valueColor;

View File

@@ -484,7 +484,7 @@ public class FlatTabbedPane
// NOTE: enum names must be equal to allowed strings
public enum TabWidthMode { preferred, equal, compact }
public enum TabWidthMode { preferred, equal, compact, /** @since 3.7 */ iconOnly }
/**
* Returns how the tabs should be sized.

View File

@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
plugins {
`java-library`
`flatlaf-toolchain`
`flatlaf-module-info`
`flatlaf-publish`
}

View File

@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
plugins {
`java-library`
`flatlaf-toolchain`
`flatlaf-module-info`
`flatlaf-publish`
}

View File

@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
plugins {
`java-library`
`flatlaf-toolchain`
`flatlaf-module-info`
`flatlaf-publish`
}

View File

@@ -26,6 +26,7 @@ if( !rootProject.hasProperty( "release" ) )
plugins {
`java-library`
`flatlaf-toolchain`
`flatlaf-module-info`
`flatlaf-publish`
}

View File

@@ -65,6 +65,7 @@ public class FlatJidePainter
Color oldColor = g.getColor();
g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
float arc = UIScale.scale( (float) this.arc );
if( c instanceof JideSplitButton ) {
// For split buttons, this method is invoked twice:
@@ -74,6 +75,8 @@ public class FlatJidePainter
// the rounded rectangle with component bounds, but clip to the passed rectangle.
boolean horizontal = (((JideSplitButton)c).getOrientation() == SwingConstants.HORIZONTAL);
int width = horizontal ? c.getWidth() : c.getHeight();
int height = horizontal ? c.getHeight() : c.getWidth();
// for vertical orientation, the graphics context is rotated, but 1px wrong
if( !horizontal )
@@ -82,10 +85,13 @@ public class FlatJidePainter
Shape oldClip = g.getClip();
g.clipRect( rect.x, rect.y, rect.width, rect.height );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0,
horizontal ? c.getWidth() : c.getHeight(),
horizontal ? c.getHeight() : c.getWidth(),
0, UIScale.scale( (float) arc ) );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height, 0, arc );
if( borderColor != null ) {
g.setColor( borderColor );
FlatUIUtils.paintOutlinedComponent( (Graphics2D) g, 0, 0, width, height,
0, 0, 0, UIScale.scale( 1f ), arc, null, borderColor, null );
}
g.setClip( oldClip );
@@ -98,8 +104,15 @@ public class FlatJidePainter
if( !horizontal )
g.translate( 0, 1 );
} else {
FlatUIUtils.paintComponentBackground( (Graphics2D) g, rect.x, rect.y,
rect.width, rect.height, 0, UIScale.scale( (float) arc ) );
FlatUIUtils.paintComponentBackground( (Graphics2D) g,
rect.x, rect.y, rect.width, rect.height, 0, arc );
if( borderColor != null ) {
g.setColor( borderColor );
FlatUIUtils.paintOutlinedComponent( (Graphics2D) g,
rect.x, rect.y, rect.width, rect.height,
0, 0, 0, UIScale.scale( 1f ), arc, null, borderColor, null );
}
}
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );

View File

@@ -25,6 +25,7 @@ To build the library on Linux, some packages needs to be installed:
- `build-essential` - GCC and development tools
- `libxt-dev` - X11 toolkit development headers
- `libgtk-3-dev` - GTK 3 toolkit development headers
- `g++-aarch64-linux-gnu` - GNU C++ compiler for the arm64 architecture (only on
x86_64 Linux for cross-compiling for arm64 architecture)
@@ -32,19 +33,39 @@ To build the library on Linux, some packages needs to be installed:
### Ubuntu
~~~
sudo apt update
sudo apt install build-essential libxt-dev
sudo apt-get update
sudo apt-get install build-essential libxt-dev libgtk-3-dev
~~~
Only on x86_64 Linux for cross-compiling for arm64 architecture:
#### Cross-compile for arm64 architecture on x86_64 Linux
Only needed on x86_64 Linux if you want cross-compile for arm64 architecture:
~~~
sudo apt install g++-aarch64-linux-gnu
sudo apt-get install g++-aarch64-linux-gnu
~~~
Download `libgtk-3.so` for arm64 architecture:
~~~
cd flatlaf-natives/flatlaf-natives-linux/lib/aarch64
wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
~~~
### Fedora
~~~
sudo dnf group install c-development
sudo dnf install libXt-devel gtk3-devel
~~~
### CentOS
~~~
sudo yum install libXt-devel
sudo yum install libXt-devel gtk3-devel
~~~

View File

@@ -40,6 +40,11 @@ var javaHome = System.getProperty( "java.home" )
if( javaHome.endsWith( "jre" ) && !file( "${javaHome}/include" ).exists() )
javaHome += "/.."
interface InjectedOps {
@get:Inject val fs: FileSystemOperations
@get:Inject val e: ExecOperations
}
tasks {
register( "build-natives" ) {
group = "build"
@@ -65,15 +70,37 @@ tasks {
includes.from(
"${javaHome}/include",
"${javaHome}/include/linux"
"${javaHome}/include/linux",
// for GTK
"/usr/include/gtk-3.0",
"/usr/include/glib-2.0",
if( name.contains( "X86-64" ) ) "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
else "/usr/lib/aarch64-linux-gnu/glib-2.0/include",
"/usr/include/gdk-pixbuf-2.0",
"/usr/include/atk-1.0",
"/usr/include/cairo",
"/usr/include/pango-1.0",
"/usr/include/harfbuzz",
)
compilerArgs.addAll( toolChain.map {
when( it ) {
is Gcc, is Clang -> listOf()
is Gcc, is Clang -> listOf( "-fvisibility=hidden" )
else -> emptyList()
}
} )
doFirst {
// check required Java version
if( JavaVersion.current() < JavaVersion.VERSION_11 ) {
println()
println( "WARNING: Java 11 or later required to build Linux native library (running ${System.getProperty( "java.version" )})" )
println( " Native library built with older Java versions throw following exception when running in Java 17+:" )
println( " java.lang.UnsatisfiedLinkError: .../libjawt.so: version `SUNWprivate_1.1' not found" )
println()
}
}
}
withType<LinkSharedLibrary>().configureEach {
@@ -88,21 +115,23 @@ tasks {
linkerArgs.addAll( toolChain.map {
when( it ) {
is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}" )
is Gcc, is Clang -> listOf( "-L${jawtPath}", "-l${jawt}", "-lgtk-3" )
else -> emptyList()
}
} )
val injectedOps = project.objects.newInstance<InjectedOps>()
doLast {
// copy shared library to flatlaf-core resources
copy {
injectedOps.fs.copy {
from( linkedFile )
into( nativesDir )
rename( linkedFile.get().asFile.name, libraryName )
}
// dump( linkedFile.asFile.get(), true )
}
// dump( linkedFile, true, injectedOps )
}
if( org.gradle.internal.os.OperatingSystem.current().isLinux &&
@@ -128,7 +157,20 @@ tasks {
"-I", "${javaHome}/include/linux",
"-I", "$include",
// for GTK
"-I", "/usr/include/gtk-3.0",
"-I", "/usr/include/glib-2.0",
"-I", "/usr/lib/x86_64-linux-gnu/glib-2.0/include",
"-I", "/usr/include/gdk-pixbuf-2.0",
"-I", "/usr/include/atk-1.0",
"-I", "/usr/include/cairo",
"-I", "/usr/include/pango-1.0",
"-I", "/usr/include/harfbuzz",
"$src/ApiVersion.cpp",
"$src/GtkFileChooser.cpp",
"$src/GtkMessageDialog.cpp",
"$src/JNIUtils.cpp",
"$src/X11WmUtils.cpp",
)
}
@@ -152,49 +194,55 @@ tasks {
"-o", "$outDir/$libraryName",
"$objDir/ApiVersion.o",
"$objDir/GtkFileChooser.o",
"$objDir/GtkMessageDialog.o",
"$objDir/JNIUtils.o",
"$objDir/X11WmUtils.o",
"-lstdc++",
"-L${layout.projectDirectory}/lib/aarch64",
"-ljawt",
"-lgtk-3",
)
val injectedOps = project.objects.newInstance<InjectedOps>()
doLast {
// copy shared library to flatlaf-core resources
copy {
injectedOps.fs.copy {
from( "$outDir/$libraryName" )
into( nativesDir )
}
// dump( file( "$outDir/$libraryName" ), false )
}
// dump( file( "$outDir/$libraryName" ), false, injectedOps )
}
}
}
/*dump
interface InjectedExecOps { @get:Inject val execOps: ExecOperations }
val injected = project.objects.newInstance<InjectedExecOps>()
fun dump( dylib: File, disassemble: Boolean ) {
val dylibDir = dylib.parent
injected.execOps.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
injected.execOps.exec {
commandLine( "objdump",
// commands
"--archive-headers",
"--section-headers",
"--private-headers",
"--reloc",
"--dynamic-reloc",
"--syms",
// files
dylib )
standardOutput = FileOutputStream( "$dylibDir/objdump.txt" )
fun Task.dump( f: Any, disassemble: Boolean, injectedOps: InjectedOps ) {
doLast {
val dylib = if( f is RegularFileProperty) f.get().asFile else f as File
val dylibDir = dylib.parent
injectedOps.e.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
injectedOps.e.exec {
commandLine( "objdump",
// commands
"--archive-headers",
"--section-headers",
"--private-headers",
"--reloc",
"--dynamic-reloc",
"--syms",
// files
dylib )
standardOutput = FileOutputStream( "$dylibDir/objdump.txt" )
}
if( disassemble )
injectedOps.e.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
injectedOps.e.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
injectedOps.e.exec { commandLine( "hexdump", dylib ); standardOutput = FileOutputStream( "$dylibDir/hexdump.txt" ) }
}
if( disassemble )
injected.execOps.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
injected.execOps.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
injected.execOps.exec { commandLine( "hexdump", dylib ); standardOutput = FileOutputStream( "$dylibDir/hexdump.txt" ) }
}
dump*/

View File

@@ -24,7 +24,7 @@
// increase this version if changing API or functionality of native library
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeLinuxLibrary
#define API_VERSION_LINUX 3001
#define API_VERSION_LINUX 3003
//---- JNI methods ------------------------------------------------------------

View File

@@ -0,0 +1,307 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jawt.h>
#include <linux/jawt_md.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <glib/gi18n.h>
#include "JNIUtils.h"
#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
// declare external methods
extern Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return );
// declare internal methods
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList );
//---- helper -----------------------------------------------------------------
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0)
#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_ ## option) != 0)
#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
jclass stringClass = env->FindClass( "java/lang/String" );
return env->NewObjectArray( count, stringClass, NULL );
}
static void initFilters( GtkFileChooser* chooser, JNIEnv* env, jint fileTypeIndex, jobjectArray fileTypes ) {
jint length = env->GetArrayLength( fileTypes );
if( length <= 0 )
return;
GtkFileFilter* filter = NULL;
int filterIndex = 0;
for( int i = 0; i < length; i++ ) {
jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i );
if( jstr == NULL ) {
if( filter != NULL ) {
gtk_file_chooser_add_filter( chooser, filter );
if( fileTypeIndex == filterIndex )
gtk_file_chooser_set_filter( chooser, filter );
filter = NULL;
filterIndex++;
}
continue;
}
AutoReleaseStringUTF8 str( env, jstr );
if( filter == NULL ) {
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, str );
} else
gtk_file_filter_add_pattern( filter, str );
}
}
static GdkWindow* getGdkWindow( JNIEnv* env, jobject window ) {
// get the AWT
JAWT awt;
awt.version = JAWT_VERSION_1_4;
if( !JAWT_GetAWT( env, &awt ) )
return NULL;
// get Xlib window and display from AWT window
Display* display;
Window w = getWindowHandle( env, &awt, window, &display );
if( w == 0 )
return NULL;
// based on GetAllocNativeWindowHandle() from https://github.com/btzy/nativefiledialog-extended
// https://github.com/btzy/nativefiledialog-extended/blob/29e3bcb578345b9fa345d1d7683f00c150565ca3/src/nfd_gtk.cpp#L384-L437
GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay( display );
if( gdkDisplay == NULL ) {
// search for existing X11 display (there should only be one, even if multiple screens are connected)
GdkDisplayManager* displayManager = gdk_display_manager_get();
GSList* displays = gdk_display_manager_list_displays( displayManager );
for( GSList* l = displays; l; l = l->next ) {
if( GDK_IS_X11_DISPLAY( l->data ) ) {
gdkDisplay = GDK_DISPLAY( l->data );
break;
}
}
g_slist_free( displays );
// create our own X11 display
if( gdkDisplay == NULL ) {
gdk_set_allowed_backends( "x11" );
gdkDisplay = gdk_display_manager_open_display( displayManager, NULL );
gdk_set_allowed_backends( NULL );
if( gdkDisplay == NULL )
return NULL;
}
}
return gdk_x11_window_foreign_new_for_display( gdkDisplay, w );
}
static void handle_realize( GtkWidget* dialog, gpointer data ) {
GdkWindow* gdkOwner = static_cast<GdkWindow*>( data );
// make file dialog a transient of owner window,
// which centers file dialog on owner and keeps file dialog above owner
gdk_window_set_transient_for( gtk_widget_get_window( dialog ), gdkOwner );
// necessary because gdk_x11_window_foreign_new_for_display() increases the reference counter
g_object_unref( gdkOwner );
}
struct ResponseData {
JNIEnv* env;
jobject callback;
GSList* fileList;
ResponseData( JNIEnv* _env, jobject _callback ) {
env = _env;
callback = _callback;
fileList = NULL;
}
};
static void handle_response( GtkWidget* dialog, gint responseId, gpointer data ) {
// get filenames if user pressed OK
if( responseId == GTK_RESPONSE_ACCEPT ) {
ResponseData *response = static_cast<ResponseData*>( data );
if( response->callback != NULL ) {
GSList* fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
jobjectArray files = fileListToStringArray( response->env, fileList );
GtkWindow* window = GTK_WINDOW( dialog );
// invoke callback: boolean approve( String[] files, long hwnd );
jclass cls = response->env->GetObjectClass( response->callback );
jmethodID approveID = response->env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
if( approveID != NULL && !response->env->CallBooleanMethod( response->callback, approveID, files, window ) )
return; // keep dialog open
}
response->fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER( dialog ) );
}
// hide/destroy file dialog and quit loop
gtk_widget_hide( dialog );
gtk_widget_destroy( dialog );
gtk_main_quit();
}
//---- JNI methods ------------------------------------------------------------
extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open,
jstring title, jstring okButtonLabel, jstring currentName, jstring currentFolder,
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
{
// initialize GTK
if( !gtk_init_check( NULL, NULL ) )
return NULL;
// convert Java strings to C strings
AutoReleaseStringUTF8 ctitle( env, title );
AutoReleaseStringUTF8 cokButtonLabel( env, okButtonLabel );
AutoReleaseStringUTF8 ccurrentName( env, currentName );
AutoReleaseStringUTF8 ccurrentFolder( env, currentFolder );
// create GTK file chooser dialog
// https://docs.gtk.org/gtk3/class.FileChooserDialog.html
bool selectFolder = isOptionSet( FC_select_folder );
bool multiSelect = isOptionSet( FC_select_multiple );
GtkWidget* dialog = gtk_file_chooser_dialog_new(
(ctitle != NULL) ? ctitle
: (selectFolder ? (multiSelect ? _("Select Folders") : _("Select Folder"))
: (open ? ((multiSelect ? _("Open Files") : _("Open File"))) : _("Save File"))),
NULL, // can not use AWT X11 window as parent because GtkWindow is required
selectFolder ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
: (open ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE),
_("_Cancel"), GTK_RESPONSE_CANCEL,
(cokButtonLabel != NULL) ? cokButtonLabel
: (selectFolder ? _("_Select") : (open ? _("_Open") : _("_Save"))), GTK_RESPONSE_ACCEPT,
NULL ); // marks end of buttons
GtkFileChooser* chooser = GTK_FILE_CHOOSER( dialog );
// set current name and folder
if( !open && ccurrentName != NULL )
gtk_file_chooser_set_current_name( chooser, ccurrentName );
if( ccurrentFolder != NULL )
gtk_file_chooser_set_current_folder( chooser, ccurrentFolder );
// set options
if( isOptionSetOrClear( FC_select_multiple ) )
gtk_file_chooser_set_select_multiple( chooser, isOptionSet( FC_select_multiple ) );
if( isOptionSetOrClear( FC_show_hidden ) )
gtk_file_chooser_set_show_hidden( chooser, isOptionSet( FC_show_hidden ) );
if( isOptionSetOrClear( FC_local_only ) )
gtk_file_chooser_set_local_only( chooser, isOptionSet( FC_local_only ) );
if( isOptionSetOrClear( FC_do_overwrite_confirmation ) )
gtk_file_chooser_set_do_overwrite_confirmation( chooser, isOptionSet( FC_do_overwrite_confirmation ) );
if( isOptionSetOrClear( FC_create_folders ) )
gtk_file_chooser_set_create_folders( chooser, isOptionSet( FC_create_folders ) );
// initialize filter
initFilters( chooser, env, fileTypeIndex, fileTypes );
// setup modality
GdkWindow* gdkOwner = (owner != NULL) ? getGdkWindow( env, owner ) : NULL;
if( gdkOwner != NULL ) {
gtk_window_set_modal( GTK_WINDOW( dialog ), true );
// file dialog should use same screen as owner
GdkScreen* screen = gdk_window_get_screen( gdkOwner );
gtk_window_set_screen( GTK_WINDOW( dialog ), screen );
// set the transient when the file dialog is realized
g_signal_connect( dialog, "realize", G_CALLBACK( handle_realize ), gdkOwner );
// set light/dark appearance
if( dark >= 0 ) {
GtkSettings *settings = gtk_settings_get_for_screen( screen );
// get current GTK theme
gchar* currentGtkTheme;
g_object_get( settings, "gtk-theme-name", &currentGtkTheme, NULL );
const char* darkSuffix = "-dark";
bool isDarkGtkTheme = g_str_has_suffix( currentGtkTheme, darkSuffix );
if( isDarkGtkTheme && dark == 0 ) {
// current GTK theme is dark, but FlatLaf theme is light
// in this case, "gtk-application-prefer-dark-theme" does not work
// and there is no "gtk-application-prefer-light-theme" setting
// --> try to switch to light GTK theme (if available)
gchar* lightGtkTheme = g_strndup( currentGtkTheme, strlen( currentGtkTheme ) - strlen( darkSuffix ) );
gchar* themeDir = g_strdup_printf( "/usr/share/themes/%s", lightGtkTheme );
if( g_file_test( themeDir, G_FILE_TEST_IS_DIR ) )
g_object_set( settings, "gtk-theme-name", lightGtkTheme, NULL );
g_free( themeDir );
g_free( lightGtkTheme );
}
g_free( currentGtkTheme );
// let GTK know whether we prefer a dark theme
g_object_set( settings, "gtk-application-prefer-dark-theme", (dark == 1), NULL );
}
}
// show dialog
// (similar to what's done in sun_awt_X11_GtkFileDialogPeer.c)
ResponseData responseData( env, callback );
g_signal_connect( dialog, "response", G_CALLBACK( handle_response ), &responseData );
gtk_widget_show( dialog );
// necessary to bring file dialog to the front (and make it active)
// see issues:
// https://github.com/btzy/nativefiledialog-extended/issues/31
// https://github.com/mlabbe/nativefiledialog/pull/92
// https://github.com/guillaumechereau/noc/pull/11
if( GDK_IS_X11_DISPLAY( gtk_widget_get_display( GTK_WIDGET( dialog ) ) ) ) {
GdkWindow* gdkWindow = gtk_widget_get_window( GTK_WIDGET( dialog ) );
gdk_window_set_events( gdkWindow, static_cast<GdkEventMask>( gdk_window_get_events( gdkWindow ) | GDK_PROPERTY_CHANGE_MASK ) );
gtk_window_present_with_time( GTK_WINDOW( dialog ), gdk_x11_get_server_time( gdkWindow ) );
}
// start event loop (will be quit in respone handler)
gtk_main();
// canceled?
if( responseData.fileList == NULL )
return newJavaStringArray( env, 0 );
// convert GSList to Java string array
return fileListToStringArray( env, responseData.fileList );
}
static jobjectArray fileListToStringArray( JNIEnv* env, GSList* fileList ) {
guint count = g_slist_length( fileList );
jobjectArray array = newJavaStringArray( env, count );
GSList* it = fileList;
for( int i = 0; i < count; i++, it = it->next ) {
gchar* path = (gchar*) it->data;
jstring jpath = env->NewStringUTF( path );
g_free( path );
env->SetObjectArrayElement( array, i, jpath );
env->DeleteLocalRef( jpath );
}
g_slist_free( fileList );
return array;
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jawt.h>
#include <linux/jawt_md.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <glib/gi18n.h>
#include "JNIUtils.h"
#include "com_formdev_flatlaf_ui_FlatNativeLinuxLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
extern "C"
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showMessageDialog
( JNIEnv* env, jclass cls, jlong hwndParent, jint messageType, jstring primaryText, jstring secondaryText,
jint defaultButton, jobjectArray buttons )
{
GtkWindow* window = (GtkWindow*) hwndParent;
// convert message type
GtkMessageType gmessageType;
switch( messageType ) {
case /* JOptionPane.ERROR_MESSAGE */ 0: gmessageType = GTK_MESSAGE_ERROR; break;
case /* JOptionPane.INFORMATION_MESSAGE */ 1: gmessageType = GTK_MESSAGE_INFO; break;
case /* JOptionPane.WARNING_MESSAGE */ 2: gmessageType = GTK_MESSAGE_WARNING; break;
case /* JOptionPane.QUESTION_MESSAGE */ 3: gmessageType = GTK_MESSAGE_QUESTION; break;
default:
case /* JOptionPane.PLAIN_MESSAGE */ -1: gmessageType = GTK_MESSAGE_OTHER; break;
}
// convert Java strings to C strings
AutoReleaseStringUTF8 cprimaryText( env, primaryText );
AutoReleaseStringUTF8 csecondaryText( env, secondaryText );
// create GTK file chooser dialog
// https://docs.gtk.org/gtk3/class.MessageDialog.html
jint buttonCount = env->GetArrayLength( buttons );
GtkWidget* dialog = gtk_message_dialog_new( window, GTK_DIALOG_MODAL, gmessageType,
(buttonCount > 0) ? GTK_BUTTONS_NONE : GTK_BUTTONS_OK,
"%s", (const gchar*) cprimaryText );
if( csecondaryText != NULL )
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( dialog ), "%s", (const gchar*) csecondaryText );
// add buttons
for( int i = 0; i < buttonCount; i++ ) {
AutoReleaseStringUTF8 str( env, (jstring) env->GetObjectArrayElement( buttons, i ) );
gtk_dialog_add_button( GTK_DIALOG( dialog ), str, i );
}
// set default button
gtk_dialog_set_default_response( GTK_DIALOG( dialog ), MIN( MAX( defaultButton, 0 ), buttonCount - 1 ) );
// show message dialog
gint responseID = gtk_dialog_run( GTK_DIALOG( dialog ) );
gtk_widget_destroy( dialog );
// return -1 if closed with ESC key
return (responseID >= 0) ? responseID : -1;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// avoid inlining of printf()
#define _NO_CRT_STDIO_INLINE
#include <dlfcn.h>
#include "JNIUtils.h"
/**
* @author Karl Tauber
*/
//---- class AutoReleaseStringUTF8 --------------------------------------------
AutoReleaseStringUTF8::AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString ) {
env = _env;
javaString = _javaString;
chars = (javaString != NULL) ? env->GetStringUTFChars( javaString, NULL ) : NULL;
}
AutoReleaseStringUTF8::~AutoReleaseStringUTF8() {
if( chars != NULL )
env->ReleaseStringUTFChars( javaString, chars );
}
//---- JNI methods ------------------------------------------------------------
extern "C"
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_isLibAvailable
( JNIEnv* env, jclass cls, jstring libname )
{
AutoReleaseStringUTF8 clibname( env, libname );
void* lib = dlopen( clibname, RTLD_LAZY );
if( lib == NULL )
return false;
dlclose( lib );
return true;
}

View File

@@ -25,18 +25,21 @@
*/
bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
long data0, long data1, long data2, long data3, long data4 );
bool isWMHintSupported( Display* display, Window rootWindow, Atom atom );
// declare exported methods
Window getWindowHandle( JNIEnv* env, JAWT* awt, jobject window, Display** display_return );
// declare internal methods
static bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
long data0, long data1, long data2, long data3, long data4 );
static bool isWMHintSupported( Display* display, Window rootWindow, Atom atom );
//---- JNI methods ------------------------------------------------------------
/**
* Send _NET_WM_MOVERESIZE to window to initiate moving or resizing.
*
* https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45446104441728
* https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#id-1.5.4
* https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/x11/gdksurface-x11.c#L3841-3881
*/
extern "C"
@@ -79,7 +82,7 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xS
0 );
}
bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
static bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
long data0, long data1, long data2, long data3, long data4 )
{
// get the AWT
@@ -131,7 +134,7 @@ bool sendEvent( JNIEnv *env, jobject window, const char *atom_name,
}
bool isWMHintSupported( Display* display, Window rootWindow, Atom atom ) {
static bool isWMHintSupported( Display* display, Window rootWindow, Atom atom ) {
Atom type;
int format;
unsigned long n_atoms;

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtk/gtk.h>
#include <jni.h>
/**
* @author Karl Tauber
*/
//---- class AutoReleaseStringUTF8 --------------------------------------------
class AutoReleaseStringUTF8 {
JNIEnv* env;
jstring javaString;
const char* chars;
public:
AutoReleaseStringUTF8( JNIEnv* _env, jstring _javaString );
~AutoReleaseStringUTF8();
operator const gchar*() { return chars; }
};

View File

@@ -7,8 +7,36 @@
#ifdef __cplusplus
extern "C" {
#endif
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPLEFT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPLEFT 0L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOP
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOP 1L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPRIGHT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_TOPRIGHT 2L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_RIGHT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_RIGHT 3L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMRIGHT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMRIGHT 4L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOM
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOM 5L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMLEFT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_BOTTOMLEFT 6L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_LEFT
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_SIZE_LEFT 7L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_MOVE 8L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_folder 1L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_select_multiple 2L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_show_hidden 4L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_local_only 8L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_do_overwrite_confirmation 16L
#undef com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders
#define com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_FC_create_folders 32L
/*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: xMoveOrResizeWindow
@@ -25,6 +53,30 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xM
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xShowWindowMenu
(JNIEnv *, jclass, jobject, jint, jint);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: isLibAvailable
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_isLibAvailable
(JNIEnv *, jclass, jstring);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: showFileChooser
* Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeLinuxLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showFileChooser
(JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeLinuxLibrary
* Method: showMessageDialog
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_showMessageDialog
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
#ifdef __cplusplus
}
#endif

View File

@@ -43,8 +43,10 @@ var javaHome = System.getProperty( "java.home" )
if( javaHome.endsWith( "jre" ) )
javaHome += "/.."
interface InjectedExecOps { @get:Inject val execOps: ExecOperations }
val injected = project.objects.newInstance<InjectedExecOps>()
interface InjectedOps {
@get:Inject val fs: FileSystemOperations
@get:Inject val e: ExecOperations
}
tasks {
register( "build-natives" ) {
@@ -75,7 +77,7 @@ tasks {
compilerArgs.addAll( toolChain.map {
when( it ) {
is Gcc, is Clang -> listOf( "-x", "objective-c++", "-mmacosx-version-min=$minOs" )
is Gcc, is Clang -> listOf( "-x", "objective-c++", "-mmacosx-version-min=$minOs", "-fvisibility=hidden" )
else -> emptyList()
}
} )
@@ -96,12 +98,14 @@ tasks {
}
} )
val injectedOps = project.objects.newInstance<InjectedOps>()
doLast {
// sign shared library
// injected.execOps.exec { commandLine( "codesign", "-s", "FormDev Software GmbH", "--timestamp", "${linkedFile.asFile.get()}" ) }
// injectedOps.e.exec { commandLine( "codesign", "-s", "FormDev Software GmbH", "--timestamp", "${linkedFile.asFile.get()}" ) }
// copy shared library to flatlaf-core resources
copy {
injectedOps.fs.copy {
from( linkedFile )
into( nativesDir )
rename( linkedFile.get().asFile.name, libraryName )
@@ -110,9 +114,9 @@ tasks {
/*dump
val dylib = linkedFile.asFile.get()
val dylibDir = dylib.parent
injected.execOps.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
injected.execOps.exec { commandLine( "size", "-m", dylib ); standardOutput = FileOutputStream( "$dylibDir/size-m.txt" ) }
injected.execOps.exec {
injectedOps.e.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) }
injectedOps.e.exec { commandLine( "size", "-m", dylib ); standardOutput = FileOutputStream( "$dylibDir/size-m.txt" ) }
injectedOps.e.exec {
commandLine( "objdump",
// commands
"--archive-headers",
@@ -130,8 +134,8 @@ tasks {
dylib )
standardOutput = FileOutputStream( "$dylibDir/objdump.txt" )
}
injected.execOps.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
injected.execOps.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
injectedOps.e.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) }
injectedOps.e.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) }
dump*/
}
}

View File

@@ -46,7 +46,33 @@
JNI_COCOA_CATCH() \
}
#define JNI_THREAD_ENTER( jvm, returnValue ) \
JNIEnv *env; \
bool detach = false; \
switch( jvm->GetEnv( (void**) &env, JNI_VERSION_1_6 ) ) { \
case JNI_OK: break; \
case JNI_EDETACHED: \
if( jvm->AttachCurrentThread( (void**) &env, NULL ) != JNI_OK ) \
return returnValue; \
detach = true; \
break; \
default: return returnValue; \
} \
@try {
#define JNI_THREAD_EXIT( jvm ) \
} @finally { \
if( env->ExceptionCheck() ) \
env->ExceptionDescribe(); \
if( detach ) \
jvm->DetachCurrentThread(); \
}
jclass findClass( JNIEnv *env, const char* className, bool globalRef );
jfieldID getFieldID( JNIEnv *env, jclass cls, const char* fieldName, const char* fieldSignature, bool staticField );
jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const char* methodSignature, bool staticMethod );
NSString* JavaToNSString( JNIEnv *env, jstring javaString );
jstring NSToJavaString( JNIEnv *env, NSString *nsString );
jstring NormalizedPathJavaFromNSString( JNIEnv* env, NSString *nsString );

View File

@@ -13,6 +13,32 @@ extern "C" {
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_MEDIUM 1L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_BUTTONS_SPACING_LARGE 2L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseFiles 1L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canChooseDirectories 2L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_resolvesAliases 4L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsMultipleSelection
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsMultipleSelection 8L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_accessoryViewDisclosed
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_accessoryViewDisclosed 16L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsTagField 256L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canCreateDirectories 512L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_canSelectHiddenExtension 1024L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showsHiddenFiles 2048L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_extensionHidden 4096L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_allowsOtherFileTypes 8192L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_treatsFilePackagesAsDirectories 16384L
#undef com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showSingleFilterField
#define com_formdev_flatlaf_ui_FlatNativeMacLibrary_FC_showSingleFilterField 16777216L
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: setWindowRoundedBorder
@@ -53,6 +79,22 @@ JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_isWi
JNIEXPORT jboolean JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_toggleWindowFullScreen
(JNIEnv *, jclass, jobject);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: showFileChooser
* Signature: (Ljava/awt/Window;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILcom/formdev/flatlaf/ui/FlatNativeMacLibrary/FileChooserCallback;I[Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
(JNIEnv *, jclass, jobject, jint, jboolean, jstring, jstring, jstring, jstring, jstring, jstring, jstring, jint, jint, jobject, jint, jobjectArray);
/*
* Class: com_formdev_flatlaf_ui_FlatNativeMacLibrary
* Method: showMessageDialog
* Signature: (JILjava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showMessageDialog
(JNIEnv *, jclass, jlong, jint, jstring, jstring, jint, jobjectArray);
#ifdef __cplusplus
}
#endif

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
#include <jni.h>
#include "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
#import <jni.h>
#import "com_formdev_flatlaf_ui_FlatNativeLibrary.h"
/**
* @author Karl Tauber
@@ -24,7 +24,7 @@
// increase this version if changing API or functionality of native library
// also update version in Java class com.formdev.flatlaf.ui.FlatNativeMacLibrary
#define API_VERSION_MACOS 2001
#define API_VERSION_MACOS 2002
//---- JNI methods ------------------------------------------------------------

View File

@@ -75,3 +75,38 @@ jmethodID getMethodID( JNIEnv *env, jclass cls, const char* methodName, const ch
return methodID;
}
NSString* JavaToNSString( JNIEnv *env, jstring javaString ) {
if( javaString == NULL )
return NULL;
int len = env->GetStringLength( javaString );
const jchar* chars = env->GetStringChars( javaString, NULL );
if( chars == NULL )
return NULL;
NSString* nsString = [NSString stringWithCharacters:(unichar*)chars length:len];
env->ReleaseStringChars( javaString, chars );
return nsString;
}
jstring NSToJavaString( JNIEnv *env, NSString *nsString ) {
if( nsString == NULL )
return NULL;
jsize len = [nsString length];
unichar* buffer = (unichar*) calloc( len, sizeof( unichar ) );
if( buffer == NULL )
return NULL;
[nsString getCharacters:buffer];
jstring javaString = env->NewString( buffer, len );
free( buffer );
return javaString;
}
jstring NormalizedPathJavaFromNSString( JNIEnv* env, NSString *nsString ) {
return (nsString != NULL)
? NSToJavaString( env, [nsString precomposedStringWithCanonicalMapping] )
: NULL;
}

View File

@@ -0,0 +1,406 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h>
#import "JNIUtils.h"
#import "JNFRunLoop.h"
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
// declare internal methods
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count );
static jobjectArray urlsToStringArray( JNIEnv* env, NSArray* urls );
static NSArray* getDialogURLs( NSSavePanel* dialog );
//---- class FileChooserDelegate ----------------------------------------------
@interface FileChooserDelegate : NSObject <NSOpenSavePanelDelegate, NSWindowDelegate> {
NSArray* _filters;
JavaVM* _jvm;
jobject _callback;
NSMutableSet* _urlsSet;
}
@property (nonatomic, assign) NSSavePanel* dialog;
- (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex
:(NSString*)filterFieldLabel :(bool)showSingleFilterField;
- (void) selectFormat: (id)sender;
- (void) selectFormatAtIndex: (int)index;
@end
@implementation FileChooserDelegate
- (void) initFilterAccessoryView: (NSMutableArray*)filters :(int)filterIndex
:(NSString*)filterFieldLabel :(bool)showSingleFilterField
{
_filters = filters;
// get filter names
NSArray* filterNames = filters.lastObject;
[filters removeLastObject];
// do not add filter/format combobox if there is only one filter
if( filters.count <= 1 && !showSingleFilterField ) {
[self selectFormatAtIndex:0];
return;
}
// create label
NSTextField* label = [[NSTextField alloc] initWithFrame:NSZeroRect];
label.stringValue = (filterFieldLabel != NULL) ? filterFieldLabel : @"Format:";
label.editable = NO;
label.bordered = NO;
label.bezeled = NO;
label.drawsBackground = NO;
// create combobox
NSPopUpButton* popupButton = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
[popupButton addItemsWithTitles:filterNames];
[popupButton selectItemAtIndex:MIN( MAX( filterIndex, 0 ), filterNames.count - 1 )];
[popupButton setTarget:self];
[popupButton setAction:@selector(selectFormat:)];
// create view
NSView* accessoryView = [[NSView alloc] initWithFrame:NSZeroRect];
[accessoryView addSubview:label];
[accessoryView addSubview:popupButton];
// autolayout
label.translatesAutoresizingMaskIntoConstraints = NO;
popupButton.translatesAutoresizingMaskIntoConstraints = NO;
int labelWidth = label.intrinsicContentSize.width;
int gap = 12;
int popupButtonWidth = popupButton.intrinsicContentSize.width;
int popupButtonMinimumWidth = 140;
int totalWidth = labelWidth + gap + MAX( popupButtonWidth, popupButtonMinimumWidth );
[accessoryView addConstraints:@[
// horizontal layout
[label.leadingAnchor constraintEqualToAnchor:accessoryView.centerXAnchor constant:-(totalWidth / 2)],
[popupButton.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:gap],
[popupButton.widthAnchor constraintGreaterThanOrEqualToConstant:popupButtonMinimumWidth],
// vertical layout
[popupButton.topAnchor constraintEqualToAnchor:accessoryView.topAnchor constant:8],
[popupButton.bottomAnchor constraintEqualToAnchor:accessoryView.bottomAnchor constant:-8],
[label.firstBaselineAnchor constraintEqualToAnchor:popupButton.firstBaselineAnchor],
]];
[_dialog setAccessoryView:accessoryView];
// initial filter
[self selectFormatAtIndex:filterIndex];
}
- (void) selectFormat: (id)sender {
NSPopUpButton* popupButton = (NSPopUpButton*) sender;
[self selectFormatAtIndex:popupButton.indexOfSelectedItem];
}
- (void) selectFormatAtIndex: (int)index {
index = MIN( MAX( index, 0 ), _filters.count - 1 );
NSArray* fileTypes = [_filters objectAtIndex:index];
// use deprecated allowedFileTypes instead of newer allowedContentTypes (since macOS 11+)
// to support older macOS versions 10.14+ and because of some problems with allowedContentTypes:
// https://github.com/chromium/chromium/blob/d8e0032963b7ca4728ff4117933c0feb3e479b7a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm#L209-232
_dialog.allowedFileTypes = [fileTypes containsObject:@"*"] ? nil : fileTypes;
}
//---- NSOpenSavePanelDelegate ----
- (void) initCallback: (JavaVM*)jvm :(jobject)callback {
_jvm = jvm;
_callback = callback;
}
- (BOOL) panel: (id) sender validateURL:(NSURL*) url error:(NSError**) outError {
JNI_COCOA_TRY()
if( _callback == NULL )
return true;
NSArray* urls = getDialogURLs( sender );
// if multiple files are selected for opening, then the validateURL method
// is invoked for earch file, but our callback should be invoked only once for all files
if( urls != NULL && urls.count > 1 ) {
if( _urlsSet == NULL ) {
// invoked for first selected file --> invoke callback
_urlsSet = [NSMutableSet setWithArray:urls];
[_urlsSet removeObject:url];
} else {
// invoked for other selected files --> do not invoke callback
[_urlsSet removeObject:url];
if( _urlsSet.count == 0 )
_urlsSet = NULL;
return true;
}
}
JNI_THREAD_ENTER( _jvm, true )
jobjectArray files = urlsToStringArray( env, urls );
jlong window = (jlong) sender;
// invoke callback: boolean approve( String[] files, long hwnd );
jclass cls = env->GetObjectClass( _callback );
jmethodID approveID = env->GetMethodID( cls, "approve", "([Ljava/lang/String;J)Z" );
if( approveID != NULL && !env->CallBooleanMethod( _callback, approveID, files, window ) ) {
_urlsSet = NULL;
return false; // keep dialog open
}
JNI_THREAD_EXIT( _jvm )
JNI_COCOA_CATCH()
return true;
}
//---- NSWindowDelegate ----
- (void) windowDidBecomeMain:(NSNotification *) notification {
JNI_COCOA_TRY()
// Disable main menu bar because the file dialog is modal and it should be not possible
// to select any menu item. Otherwiese an action could show a Swing dialog, which would
// be shown under the file dialog.
//
// NOTE: It is not necessary to re-enable the main menu bar because Swing does this itself.
// When the file dialog is closed and a Swing window becomes active,
// macOS sends windowDidBecomeMain (and windowDidBecomeKey) message to AWTWindow,
// which invokes [self activateWindowMenuBar],
// which invokes [CMenuBar activate:menuBar modallyDisabled:isDisabled],
// which updates main menu bar.
NSMenu* mainMenu = [NSApp mainMenu];
int count = [mainMenu numberOfItems];
for( int i = 0; i < count; i++ ) {
NSMenuItem* menuItem = [mainMenu itemAtIndex:i];
NSMenu *subenu = [menuItem submenu];
if( [subenu isJavaMenu] )
[menuItem setEnabled:NO];
}
JNI_COCOA_CATCH()
}
@end
//---- helper -----------------------------------------------------------------
#define isOptionSet( option ) ((optionsSet & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
#define isOptionClear( option ) ((optionsClear & com_formdev_flatlaf_ui_FlatNativeMacLibrary_ ## option) != 0)
#define isOptionSetOrClear( option ) isOptionSet( option ) || isOptionClear( option )
static jobjectArray newJavaStringArray( JNIEnv* env, jsize count ) {
jclass stringClass = env->FindClass( "java/lang/String" );
return env->NewObjectArray( count, stringClass, NULL );
}
static NSMutableArray* initFilters( JNIEnv* env, jobjectArray fileTypes ) {
jint length = env->GetArrayLength( fileTypes );
if( length <= 0 )
return NULL;
NSMutableArray* filterNames = [NSMutableArray array];
NSMutableArray* filters = [NSMutableArray array];
NSString* filterName = NULL;
NSMutableArray* filter = NULL;
for( int i = 0; i < length; i++ ) {
jstring jstr = (jstring) env->GetObjectArrayElement( fileTypes, i );
if( jstr == NULL ) {
if( filter != NULL ) {
if( filter.count > 0 ) {
[filterNames addObject:filterName];
[filters addObject:filter];
}
filterName = NULL;
filter = NULL;
}
continue;
}
NSString* str = JavaToNSString( env, jstr );
env->DeleteLocalRef( jstr );
if( filter == NULL ) {
filterName = str;
filter = [NSMutableArray array];
} else
[filter addObject:str];
}
if( filters.count == 0 )
return NULL;
// add filter names to array (removed again after creating combobox)
[filters addObject:filterNames];
return filters;
}
//---- JNI methods ------------------------------------------------------------
extern "C"
JNIEXPORT jobjectArray JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showFileChooser
( JNIEnv* env, jclass cls, jobject owner, jint dark, jboolean open,
jstring title, jstring prompt, jstring message, jstring filterFieldLabel,
jstring nameFieldLabel, jstring nameFieldStringValue, jstring directoryURL,
jint optionsSet, jint optionsClear, jobject callback, jint fileTypeIndex, jobjectArray fileTypes )
{
JNI_COCOA_ENTER()
JavaVM* jvm;
if( env->GetJavaVM( &jvm ) != JNI_OK )
return NULL;
// convert Java strings to NSString (on Java thread)
NSString* nsTitle = JavaToNSString( env, title );
NSString* nsPrompt = JavaToNSString( env, prompt );
NSString* nsMessage = JavaToNSString( env, message );
NSString* nsFilterFieldLabel = JavaToNSString( env, filterFieldLabel );
NSString* nsNameFieldLabel = JavaToNSString( env, nameFieldLabel );
NSString* nsNameFieldStringValue = JavaToNSString( env, nameFieldStringValue );
NSString* nsDirectoryURL = JavaToNSString( env, directoryURL );
NSMutableArray* filters = initFilters( env, fileTypes );
NSArray* urls = NULL;
NSArray** purls = &urls;
// show file dialog on macOS thread
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
JNI_COCOA_TRY()
// create open/save panel
NSSavePanel* dialog = open ? [NSOpenPanel openPanel] : [NSSavePanel savePanel];
// set appearance
if( dark == 1 )
dialog.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
else if( dark == 0 )
dialog.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
if( nsTitle != NULL )
dialog.title = nsTitle;
if( nsPrompt != NULL )
dialog.prompt = nsPrompt;
if( nsMessage != NULL )
dialog.message = nsMessage;
if( nsNameFieldLabel != NULL )
dialog.nameFieldLabel = nsNameFieldLabel;
if( nsNameFieldStringValue != NULL )
dialog.nameFieldStringValue = nsNameFieldStringValue;
if( nsDirectoryURL != NULL )
dialog.directoryURL = [NSURL fileURLWithPath:nsDirectoryURL isDirectory:YES];
// set open options
if( open ) {
NSOpenPanel* openDialog = (NSOpenPanel*) dialog;
bool canChooseFiles = isOptionSet( FC_canChooseFiles );
bool canChooseDirectories = isOptionSet( FC_canChooseDirectories );
if( !canChooseFiles && !canChooseDirectories )
canChooseFiles = true;
openDialog.canChooseFiles = canChooseFiles;
openDialog.canChooseDirectories = canChooseDirectories;
if( isOptionSetOrClear( FC_resolvesAliases ) )
openDialog.resolvesAliases = isOptionSet( FC_resolvesAliases );
if( isOptionSetOrClear( FC_allowsMultipleSelection ) )
openDialog.allowsMultipleSelection = isOptionSet( FC_allowsMultipleSelection );
}
// set options
if( isOptionSetOrClear( FC_showsTagField ) )
dialog.showsTagField = isOptionSet( FC_showsTagField );
if( isOptionSetOrClear( FC_canCreateDirectories ) )
dialog.canCreateDirectories = isOptionSet( FC_canCreateDirectories );
if( isOptionSetOrClear( FC_canSelectHiddenExtension ) )
dialog.canSelectHiddenExtension = isOptionSet( FC_canSelectHiddenExtension );
if( isOptionSetOrClear( FC_showsHiddenFiles) )
dialog.showsHiddenFiles = isOptionSet( FC_showsHiddenFiles);
if( isOptionSetOrClear( FC_extensionHidden ) )
dialog.extensionHidden = isOptionSet( FC_extensionHidden );
if( isOptionSetOrClear( FC_allowsOtherFileTypes ) )
dialog.allowsOtherFileTypes = isOptionSet( FC_allowsOtherFileTypes );
if( isOptionSetOrClear( FC_treatsFilePackagesAsDirectories ) )
dialog.treatsFilePackagesAsDirectories = isOptionSet( FC_treatsFilePackagesAsDirectories );
FileChooserDelegate* delegate = [FileChooserDelegate new];
delegate.dialog = dialog;
// initialize filter accessory view
if( filters != NULL ) {
[delegate initFilterAccessoryView:filters :fileTypeIndex :nsFilterFieldLabel :isOptionSet( FC_showSingleFilterField )];
if( open && isOptionSetOrClear( FC_accessoryViewDisclosed ) )
((NSOpenPanel*)dialog).accessoryViewDisclosed = isOptionSet( FC_accessoryViewDisclosed );
}
// initialize callback
if( callback != NULL )
[delegate initCallback :jvm :callback];
// set file dialog delegate
dialog.delegate = delegate;
// show dialog
NSModalResponse response = [dialog runModal];
[delegate release];
if( response != NSModalResponseOK ) {
*purls = @[];
return;
}
*purls = getDialogURLs( dialog );
JNI_COCOA_CATCH()
}];
if( urls == NULL )
return NULL;
// convert URLs to Java string array
return urlsToStringArray( env, urls );
JNI_COCOA_EXIT()
}
static NSArray* getDialogURLs( NSSavePanel* dialog ) {
if( [dialog isKindOfClass:[NSOpenPanel class]] )
return [[NSArray alloc] initWithArray: static_cast<NSOpenPanel*>(dialog).URLs];
NSURL* url = dialog.URL;
// use '[[NSArray alloc] initWithObject:url]' here because '@[url]' crashes on macOS 10.14
return (url != NULL) ? [[NSArray alloc] initWithObject:url] : @[];
}
static jobjectArray urlsToStringArray( JNIEnv* env, NSArray* urls ) {
jsize count = (urls != NULL) ? urls.count : 0;
jobjectArray array = newJavaStringArray( env, count );
for( int i = 0; i < count; i++ ) {
jstring filename = NormalizedPathJavaFromNSString( env, [urls[i] path] );
env->SetObjectArrayElement( array, i, filename );
env->DeleteLocalRef( filename );
}
return array;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#import <jni.h>
#import "JNIUtils.h"
#import "JNFRunLoop.h"
#import "com_formdev_flatlaf_ui_FlatNativeMacLibrary.h"
/**
* @author Karl Tauber
* @since 3.7
*/
extern "C"
JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatNativeMacLibrary_showMessageDialog
( JNIEnv* env, jclass cls, jlong hwndParent, jint alertStyle, jstring messageText, jstring informativeText,
jint defaultButton, jobjectArray buttons )
{
JNI_COCOA_ENTER()
// convert Java strings to NSString (on Java thread)
NSString* nsMessageText = JavaToNSString( env, messageText );
NSString* nsInformativeText = JavaToNSString( env, informativeText );
jint buttonCount = env->GetArrayLength( buttons );
NSMutableArray* nsButtons = [NSMutableArray array];
for( int i = 0; i < buttonCount; i++ ) {
NSString* nsButton = JavaToNSString( env, (jstring) env->GetObjectArrayElement( buttons, i ) );
[nsButtons addObject:nsButton];
}
jint result = -1;
jint* presult = &result;
// show alert on macOS thread
[FlatJNFRunLoop performOnMainThreadWaiting:YES withBlock:^(){
NSAlert* alert = [[NSAlert alloc] init];
// use appearance from parent window
NSWindow* parent = (NSWindow*) hwndParent;
if( parent != NULL )
alert.window.appearance = parent.appearance;
// use empty string because if alert.messageText is not set it displays "Alert"
alert.messageText = (nsMessageText != NULL) ? nsMessageText : @"";
if( nsInformativeText != NULL )
alert.informativeText = nsInformativeText;
// alert style
switch( alertStyle ) {
case /* JOptionPane.ERROR_MESSAGE */ 0: alert.alertStyle = NSAlertStyleCritical; break;
default:
case /* JOptionPane.INFORMATION_MESSAGE */ 1: alert.alertStyle = NSAlertStyleInformational; break;
case /* JOptionPane.WARNING_MESSAGE */ 2: alert.alertStyle = NSAlertStyleWarning; break;
}
// add buttons
for( int i = 0; i < nsButtons.count; i++ ) {
NSButton* b = [alert addButtonWithTitle:nsButtons[i]];
if( i == defaultButton )
alert.window.defaultButtonCell = b.cell;
}
// show alert
NSInteger response = [alert runModal];
// if no buttons added, which shows a single OK button, the response is 0 when clicking OK
// if buttons added, response is 1000+buttonIndex
*presult = MAX( response - NSAlertFirstButtonReturn, 0 );
}];
return result;
JNI_COCOA_EXIT()
}

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