Merge main into system-file-chooser

This commit is contained in:
Karl Tauber
2025-06-12 11:29:17 +02:00
50 changed files with 1351 additions and 272 deletions

View File

@@ -21,6 +21,8 @@ import java.awt.IllegalComponentStateException;
import java.awt.Window;
import java.util.Objects;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.SwingConstants;
/**
@@ -1209,12 +1211,15 @@ public interface FlatClientProperties
/**
* Specifies whether all text is selected when the text component gains focus.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Components</strong> {@link javax.swing.text.JTextComponent} (and subclasses),
* {@link javax.swing.JComboBox} (since 3.6) and {@link javax.swing.JSpinner} (since 3.6)<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #SELECT_ALL_ON_FOCUS_POLICY_NEVER},
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ONCE} (default) or
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}
*
* @see #SELECT_ALL_ON_MOUSE_CLICK
*/
String SELECT_ALL_ON_FOCUS_POLICY = "JTextField.selectAllOnFocusPolicy";
@@ -1229,6 +1234,12 @@ public interface FlatClientProperties
* Select all text when the text component gains focus for the first time
* and selection was not modified (is at end of text).
* This is the default.
* <p>
* <b>Limitations:</b>
* For {@link JFormattedTextField} and {@link JSpinner} this behaves
* as {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}.
* This is because of special behavior of {@link JFormattedTextField}
* that did not allow implementation of {@code "once"}.
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
*/
@@ -1241,6 +1252,19 @@ public interface FlatClientProperties
*/
String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always";
/**
* Specifies whether all text is selected when when clicking with the mouse
* into the text field (and if "select all on focus" policy is enabled).
* <p>
* <strong>Components</strong> {@link javax.swing.text.JTextComponent} (and subclasses),
* {@link javax.swing.JComboBox} and {@link javax.swing.JSpinner}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
* @since 3.6
*/
String SELECT_ALL_ON_MOUSE_CLICK = "JTextField.selectAllOnMouseClick";
/**
* Placeholder text that is only painted if the text field is empty.
* <p>

View File

@@ -413,22 +413,37 @@ public class IntelliJTheme
key.equals( "Tree.rightChildIndent" ) )
return; // ignore
// ignore icons
if( key.endsWith( "Icon" ) )
return; // ignore
// map keys
key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() )
return; // ignore key
// exclude properties
// exclude properties (1st level)
int dot = key.indexOf( '.' );
if( dot > 0 && uiKeyExcludes.contains( key.substring( 0, dot + 1 ) ) )
if( dot > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot + 1 ) ) )
return;
// exclude properties (2st level)
int dot2 = (dot > 0) ? key.indexOf( '.', dot + 1 ) : -1;
if( dot2 > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot2 + 1 ) ) )
return;
// exclude properties (contains)
for( String s : uiKeyExcludesContains ) {
if( key.contains( s ) )
return;
}
if( uiKeyDoNotOverride.contains( key ) && jsonUIKeys.contains( key ) )
return;
jsonUIKeys.add( key );
String valueStr = value.toString();
String valueStr = value.toString().trim();
// map named colors
String uiValue = namedColors.get( valueStr );
@@ -657,7 +672,8 @@ public class IntelliJTheme
}
}
private static final Set<String> uiKeyExcludes;
private static final Set<String> uiKeyExcludesStartsWith;
private static final String[] uiKeyExcludesContains;
private static final Set<String> uiKeyDoNotOverride;
/** Rename UI default keys (key --> value). */
private static final Map<String, String> uiKeyMapping = new HashMap<>();
@@ -669,7 +685,7 @@ public class IntelliJTheme
static {
// IntelliJ UI properties that are not used in FlatLaf
uiKeyExcludes = new HashSet<>( Arrays.asList(
uiKeyExcludesStartsWith = new HashSet<>( Arrays.asList(
"ActionButton.", "ActionToolbar.", "ActionsList.", "AppInspector.", "AssignedMnemonic.", "Autocomplete.",
"AvailableMnemonic.",
"Badge.", "Banner.", "BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
@@ -703,14 +719,24 @@ public class IntelliJTheme
// possible typos in .theme.json files
"Checkbox.", "Toolbar.", "Tooltip.", "UiDesigner.", "link."
) );
uiKeyExcludesContains = new String[] {
".darcula."
};
uiKeyDoNotOverride = new HashSet<>( Arrays.asList(
"TabbedPane.selectedForeground"
) );
// "*."
uiKeyMapping.put( "*.fontFace", "" ); // ignore (used in OnePauintxi themes)
uiKeyMapping.put( "*.fontSize", "" ); // ignore (used in OnePauintxi themes)
// Button
uiKeyMapping.put( "Button.minimumSize", "" ); // ignore (used in Material Theme UI Lite)
// CheckBox.iconSize
uiKeyMapping.put( "CheckBox.iconSize", "" ); // ignore (used in Rider themes)
// ComboBox
uiKeyMapping.put( "ComboBox.background", "" ); // ignore
uiKeyMapping.put( "ComboBox.buttonBackground", "" ); // ignore
@@ -751,6 +777,9 @@ public class IntelliJTheme
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
// RadioButton
uiKeyMapping.put( "RadioButton.iconSize", "" ); // ignore (used in Rider themes)
// ScrollBar
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" );

View File

@@ -649,22 +649,26 @@ class UIDefaultsLoader
if( value.indexOf( ',' ) >= 0 ) {
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
? parseFloat( parts.get( 5 ) )
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
try {
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
? parseFloat( parts.get( 5 ) )
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
return (LazyValue) t -> {
return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
return (LazyValue) t -> {
return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
} catch( RuntimeException ex ) {
throw new IllegalArgumentException( "invalid border '" + value + "' (" + ex.getMessage() + ")" );
}
} else
return parseInstance( value, resolver, addonClassLoaders );
}
@@ -735,7 +739,7 @@ class UIDefaultsLoader
Integer.parseInt( numbers.get( 1 ) ),
Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException ex ) {
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid insets '" + value + "'" );
}
}
@@ -748,7 +752,7 @@ class UIDefaultsLoader
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException ex ) {
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid size '" + value + "'" );
}
}
@@ -1379,17 +1383,17 @@ class UIDefaultsLoader
break;
}
}
} catch( IOException ex ) {
throw new IllegalArgumentException( ex );
} catch( RuntimeException | IOException ex ) {
throw new IllegalArgumentException( "invalid font '" + value + "' (" + ex.getMessage() + ")" );
}
if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
throw new IllegalArgumentException( "invalid font '" + value + "': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" );
if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+bold' and '-bold'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+italic' and '-italic'" );
}
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
@@ -1529,7 +1533,7 @@ class UIDefaultsLoader
return (LazyValue) t -> {
return new GrayFilter( brightness, contrast, alpha );
};
} catch( NumberFormatException ex ) {
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
}
}

View File

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

View File

@@ -882,7 +882,7 @@ public class FlatComboBoxUI
GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
if( gc != null ) {
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right );
} else {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

View File

@@ -32,7 +32,6 @@ import java.awt.Panel;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
@@ -312,7 +311,7 @@ public class FlatPopupFactory
return null;
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
int screenTop = screenBounds.y + screenInsets.top;
// place tooltip above mouse location if there is enough space

View File

@@ -246,7 +246,7 @@ public class FlatPopupMenuUI
// (always subtract screen insets because there is no API to detect whether
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
return FlatUIUtils.subtractInsets( screenBounds, screenInsets );
}

View File

@@ -914,7 +914,7 @@ public class FlatTitlePane
// screen insets are in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8243925)
// and except for Java 8 on secondary screens where primary screen is scaled
Insets screenInsets = window.getToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
// maximized bounds are required in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and

View File

@@ -26,6 +26,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Paint;
@@ -34,6 +35,7 @@ import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
@@ -414,6 +416,17 @@ public class FlatUIUtils
return (fullScreenWindow != null && fullScreenWindow == SwingUtilities.windowForComponent( c ));
}
/** @since 3.7 */
public static Insets getScreenInsets( GraphicsConfiguration gc ) {
// on Linux, getScreenInsets() may report wrong values in multi-screen setups
// see https://github.com/apache/netbeans/issues/8532#issuecomment-2909687016
if( SystemInfo.isLinux &&
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length > 1 )
return new Insets( 0, 0, 0, 0 );
return Toolkit.getDefaultToolkit().getScreenInsets( gc );
}
public static Boolean isRoundRect( Component c ) {
return (c instanceof JComponent)
? FlatClientProperties.clientPropertyBooleanStrict(

View File

@@ -331,7 +331,7 @@ public abstract class FlatWindowResizer
protected Rectangle getParentBounds() {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle bounds = gc.getBounds();
Insets insets = window.getToolkit().getScreenInsets( gc );
Insets insets = FlatUIUtils.getScreenInsets( gc );
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom );

View File

@@ -276,7 +276,7 @@ RootPane.inactiveBorderColor = lighten(@background,5%,derived)
#---- ScrollBar ----
ScrollBar.track = lighten(@background,1%,derived noAutoInverse)
ScrollBar.thumb = lighten($ScrollBar.track,10%,derived noAutoInverse)
ScrollBar.thumb = lighten($ScrollBar.track,15%,derived noAutoInverse)
ScrollBar.hoverTrackColor = lighten($ScrollBar.track,4%,derived noAutoInverse)
ScrollBar.hoverThumbColor = lighten($ScrollBar.thumb,10%,derived noAutoInverse)
ScrollBar.pressedThumbColor = lighten($ScrollBar.thumb,15%,derived noAutoInverse)

View File

@@ -574,12 +574,12 @@ RootPane.honorDialogMinimumSizeOnResize = true
ScrollBar.width = 10
ScrollBar.minimumButtonSize = 12,12
ScrollBar.minimumThumbSize = 10,10
ScrollBar.minimumThumbSize = 18,18
ScrollBar.maximumThumbSize = 100000,100000
ScrollBar.trackInsets = 0,0,0,0
ScrollBar.thumbInsets = 0,0,0,0
ScrollBar.thumbInsets = 2,2,2,2
ScrollBar.trackArc = 0
ScrollBar.thumbArc = 0
ScrollBar.thumbArc = 999
ScrollBar.hoverThumbWithTrack = false
ScrollBar.pressedThumbWithTrack = false
ScrollBar.showButtons = false
@@ -588,15 +588,8 @@ ScrollBar.buttonArrowColor = @buttonArrowColor
ScrollBar.buttonDisabledArrowColor = @buttonDisabledArrowColor
ScrollBar.allowsAbsolutePositioning = true
[mac]ScrollBar.minimumThumbSize = 18,18
[mac]ScrollBar.thumbInsets = 2,2,2,2
[mac]ScrollBar.thumbArc = 999
[mac]ScrollBar.hoverThumbWithTrack = true
[linux]ScrollBar.minimumThumbSize = 18,18
[linux]ScrollBar.thumbInsets = 2,2,2,2
[linux]ScrollBar.thumbArc = 999
#---- ScrollPane ----

View File

@@ -282,7 +282,7 @@ RootPane.inactiveBorderColor = darken(@background,30%,derived)
#---- ScrollBar ----
ScrollBar.track = lighten(@background,1%,derived noAutoInverse)
ScrollBar.thumb = darken($ScrollBar.track,10%,derived noAutoInverse)
ScrollBar.thumb = darken($ScrollBar.track,15%,derived noAutoInverse)
ScrollBar.hoverTrackColor = darken($ScrollBar.track,3%,derived noAutoInverse)
ScrollBar.hoverThumbColor = darken($ScrollBar.thumb,10%,derived noAutoInverse)
ScrollBar.pressedThumbColor = darken($ScrollBar.thumb,20%,derived noAutoInverse)

View File

@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
MenuBar.selectionArc = 8
MenuBar.selectionBackground = lighten(@menuBackground,15%,derived)
MenuBar.selectionForeground = @foreground
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
#---- MenuItem ----

View File

@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
MenuBar.selectionArc = 8
MenuBar.selectionBackground = darken(@menuBackground,15%,derived)
MenuBar.selectionForeground = @foreground
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
#---- MenuItem ----

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.awt.Color;
import java.awt.Dimension;
@@ -29,6 +30,7 @@ import javax.swing.border.Border;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.DerivedColor;
@@ -452,6 +454,73 @@ public class TestUIDefaultsLoader
return ((LazyValue)v).createValue( null );
}
//---- invalid values -----------------------------------------------------
@Test
void parseInvalidValue() {
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", null ) );
assertThrows( new NumberFormatException( "invalid integer or float '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", null ) );
assertThrows( new NumberFormatException( "invalid integer or float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", null ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3,4'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3,4", null ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", null ) );
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", null ) );
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", null ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", null ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", null ) );
}
@Test
void parseInvalidValueWithJavaType() {
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", boolean.class ) );
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", Boolean.class ) );
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", char.class ) );
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", Character.class ) );
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", int.class ) );
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", Integer.class ) );
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", float.class ) );
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", Float.class ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3", Insets.class ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", Insets.class ) );
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", Dimension.class ) );
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", Dimension.class ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", Color.class ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", Color.class ) );
}
@Test
void parseInvalidBorders() {
assertThrows( new IllegalArgumentException( "invalid border '1,abc,3,4' (invalid insets '1,abc,3,4')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,abc,3,4", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3' (invalid insets '1,2,3')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,,,' (invalid insets '1,2,3,,,')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,,,", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f0' (invalid color '#f0')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f0", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5abc' (invalid float '2.5abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5abc", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5,6abc' (invalid integer '6abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5,6abc", null ) );
}
@Test
void parseInvalidFonts() {
// size
assertThrows( new IllegalArgumentException( "invalid font '12abc' (invalid integer '12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "12abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+12abc' (invalid integer '+12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+12abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+3abc' (invalid integer '+3abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+3abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '-4abc' (invalid integer '-4abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "-4abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '150abc%' (invalid integer '150abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "150abc%", null ) );
assertThrows( new IllegalArgumentException( "invalid font 'bold 13abc Monospaced' (invalid integer '13abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold 13abc Monospaced", null ) );
// invalid combinations of styles
assertThrows( new IllegalArgumentException( "invalid font 'bold +italic': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold +italic", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+bold -bold': can not use '+bold' and '-bold'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+bold -bold", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+italic -italic': can not use '+italic' and '-italic'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+italic -italic", null ) );
}
private void assertThrows( Throwable expected, Executable executable ) {
Throwable actual = assertThrowsExactly( expected.getClass(), executable );
assertEquals( expected.getMessage(), actual.getMessage() );
}
//---- class TestInstance -------------------------------------------------
@SuppressWarnings( "EqualsHashCode" ) // Error Prone