TextComponent: support placeholder text that is displayed if text field is empty (set client property "JTextField.placeholderText" to a string)

This commit is contained in:
Karl Tauber
2019-12-17 18:08:45 +01:00
parent e4f7fed523
commit 62765ab6ca
11 changed files with 187 additions and 10 deletions

View File

@@ -3,6 +3,8 @@ FlatLaf Change Log
## Unreleased
- TextComponent: Support placeholder text that is displayed if text field is
empty (set client property "JTextField.placeholderText" to a string).
- TextComponent: Scale caret width on HiDPI screens when running on Java 8.
- ProgressBar: If progress text is visible:
- use smaller font

View File

@@ -80,6 +80,14 @@ public interface FlatClientProperties
*/
String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder";
/**
* Placeholder text that is only painted if the text field is empty.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} or {@link javax.swing.JComboBox}<br>
* <strong>Value type</strong> {@link java.lang.String}
*/
String PLACEHOLDER_TEXT = "JTextField.placeholderText";
/**
* Checks whether a client property of a component has the given value.
*/

View File

@@ -52,6 +52,7 @@ import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -239,7 +240,8 @@ public class FlatComboBoxUI
} else if( editor != null && source == comboBox && propertyName == "componentOrientation" ) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
editor.applyComponentOrientation( o );
}
} else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) )
editor.repaint();
}
};
}

View File

@@ -39,8 +39,10 @@ import javax.swing.plaf.ComponentUI;
*
* <!-- FlatTextFieldUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault FormattedTextField.placeholderForeground Color
*
* @author Karl Tauber
*/

View File

@@ -17,14 +17,17 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPasswordFieldUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -51,6 +54,7 @@ import com.formdev.flatlaf.util.SystemInfo;
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault PasswordField.placeholderForeground Color
*
* @author Karl Tauber
*/
@@ -60,6 +64,7 @@ public class FlatPasswordFieldUI
protected int focusWidth;
protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color placeholderForeground;
private FocusListener focusListener;
@@ -75,9 +80,11 @@ public class FlatPasswordFieldUI
if( SystemInfo.IS_MAC )
LookAndFeel.installProperty( getComponent(), "echoChar", '\u2022' );
String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 );
@@ -88,6 +95,8 @@ public class FlatPasswordFieldUI
protected void uninstallDefaults() {
super.uninstallDefaults();
placeholderForeground = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@@ -107,9 +116,18 @@ public class FlatPasswordFieldUI
focusListener = null;
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
}
@Override
protected void paintSafely( Graphics g ) {
FlatTextFieldUI.paintBackground( g, getComponent(), focusWidth, isIntelliJTheme );
FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground );
super.paintSafely( g );
}

View File

@@ -20,9 +20,12 @@ import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JSpinner;
@@ -33,6 +36,7 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}.
@@ -57,6 +61,7 @@ import javax.swing.text.JTextComponent;
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault TextField.placeholderForeground Color
*
* @author Karl Tauber
*/
@@ -66,6 +71,7 @@ public class FlatTextFieldUI
protected int focusWidth;
protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color placeholderForeground;
private FocusListener focusListener;
@@ -77,9 +83,11 @@ public class FlatTextFieldUI
protected void installDefaults() {
super.installDefaults();
String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 );
@@ -90,6 +98,8 @@ public class FlatTextFieldUI
protected void uninstallDefaults() {
super.uninstallDefaults();
placeholderForeground = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@@ -109,9 +119,18 @@ public class FlatTextFieldUI
focusListener = null;
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
}
@Override
protected void paintSafely( Graphics g ) {
paintBackground( g, getComponent(), focusWidth, isIntelliJTheme );
paintPlaceholder( g, getComponent(), placeholderForeground );
super.paintSafely( g );
}
@@ -152,6 +171,31 @@ public class FlatTextFieldUI
}
}
static void paintPlaceholder( Graphics g, JTextComponent c, Color placeholderForeground ) {
// check whether text component is empty
if( c.getDocument().getLength() > 0 )
return;
// check for JComboBox
Container parent = c.getParent();
JComponent jc = (parent instanceof JComboBox) ? (JComboBox<?>) parent : c;
// get placeholder text
Object placeholder = jc.getClientProperty( FlatClientProperties.PLACEHOLDER_TEXT );
if( !(placeholder instanceof String) )
return;
// compute placeholder location
Insets insets = c.getInsets();
FontMetrics fm = c.getFontMetrics( c.getFont() );
int x = insets.left;
int y = insets.top + fm.getAscent() + ((c.getHeight() - insets.top - insets.bottom - fm.getHeight()) / 2);
// paint placeholder
g.setColor( placeholderForeground );
FlatUIUtils.drawString( c, g, (String) placeholder, x, y );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ), c );

View File

@@ -147,8 +147,8 @@ Component.hideMnemonics=true
#---- EditorPane ----
EditorPane.border=com.formdev.flatlaf.ui.FlatMarginBorder
EditorPane.background=@textComponentBackground
EditorPane.margin=@textComponentMargin
EditorPane.background=@textComponentBackground
#---- FileChooser ----
@@ -172,8 +172,9 @@ FileView.floppyDriveIcon=com.formdev.flatlaf.icons.FlatFileViewFloppyDriveIcon
#---- FormattedTextField ----
FormattedTextField.border=com.formdev.flatlaf.ui.FlatBorder
FormattedTextField.background=@textComponentBackground
FormattedTextField.margin=@textComponentMargin
FormattedTextField.background=@textComponentBackground
FormattedTextField.placeholderForeground=@disabledText
#---- HelpButton ----
@@ -247,8 +248,9 @@ OptionPane.warningIcon=com.formdev.flatlaf.icons.FlatOptionPaneWarningIcon
#---- PasswordField ----
PasswordField.border=com.formdev.flatlaf.ui.FlatBorder
PasswordField.background=@textComponentBackground
PasswordField.margin=@textComponentMargin
PasswordField.background=@textComponentBackground
PasswordField.placeholderForeground=@disabledText
#---- PopupMenu ----
@@ -381,22 +383,23 @@ TableHeader.cellBorder=2,3,2,3
#---- TextArea ----
TextArea.border=com.formdev.flatlaf.ui.FlatMarginBorder
TextArea.background=@textComponentBackground
TextArea.margin=@textComponentMargin
TextArea.background=@textComponentBackground
#---- TextField ----
TextField.border=com.formdev.flatlaf.ui.FlatBorder
TextField.background=@textComponentBackground
TextField.margin=@textComponentMargin
TextField.background=@textComponentBackground
TextField.placeholderForeground=@disabledText
#---- TextPane ----
TextPane.border=com.formdev.flatlaf.ui.FlatMarginBorder
TextPane.background=@textComponentBackground
TextPane.margin=@textComponentMargin
TextPane.background=@textComponentBackground
#---- TitledBorder ----

View File

@@ -62,21 +62,25 @@ class BasicComponentsPanel
JLabel spinnerLabel = new JLabel();
JSpinner spinner1 = new JSpinner();
JSpinner spinner2 = new JSpinner();
JComboBox<String> comboBox6 = new JComboBox<>();
JLabel textFieldLabel = new JLabel();
JTextField textField1 = new JTextField();
JTextField textField2 = new JTextField();
JTextField textField3 = new JTextField();
JTextField textField4 = new JTextField();
JTextField textField6 = new JTextField();
JLabel formattedTextFieldLabel = new JLabel();
JFormattedTextField formattedTextField1 = new JFormattedTextField();
JFormattedTextField formattedTextField2 = new JFormattedTextField();
JFormattedTextField formattedTextField3 = new JFormattedTextField();
JFormattedTextField formattedTextField4 = new JFormattedTextField();
JFormattedTextField formattedTextField5 = new JFormattedTextField();
JLabel passwordFieldLabel = new JLabel();
JPasswordField passwordField1 = new JPasswordField();
JPasswordField passwordField2 = new JPasswordField();
JPasswordField passwordField3 = new JPasswordField();
JPasswordField passwordField4 = new JPasswordField();
JPasswordField passwordField5 = new JPasswordField();
JLabel textAreaLabel = new JLabel();
JScrollPane scrollPane1 = new JScrollPane();
JTextArea textArea1 = new JTextArea();
@@ -304,6 +308,11 @@ class BasicComponentsPanel
spinner2.setEnabled(false);
add(spinner2, "cell 2 5,growx");
//---- comboBox6 ----
comboBox6.setEditable(true);
comboBox6.putClientProperty("JTextField.placeholderText", "placeholder");
add(comboBox6, "cell 5 5,growx");
//---- textFieldLabel ----
textFieldLabel.setText("JTextField:");
add(textFieldLabel, "cell 0 6");
@@ -328,6 +337,10 @@ class BasicComponentsPanel
textField4.setEditable(false);
add(textField4, "cell 4 6,growx");
//---- textField6 ----
textField6.putClientProperty("JTextField.placeholderText", "placeholder");
add(textField6, "cell 5 6,growx");
//---- formattedTextFieldLabel ----
formattedTextFieldLabel.setText("JFormattedTextField:");
add(formattedTextFieldLabel, "cell 0 7");
@@ -352,6 +365,10 @@ class BasicComponentsPanel
formattedTextField4.setEditable(false);
add(formattedTextField4, "cell 4 7,growx");
//---- formattedTextField5 ----
formattedTextField5.putClientProperty("JTextField.placeholderText", "placeholder");
add(formattedTextField5, "cell 5 7,growx");
//---- passwordFieldLabel ----
passwordFieldLabel.setText("JPasswordField:");
add(passwordFieldLabel, "cell 0 8");
@@ -376,6 +393,10 @@ class BasicComponentsPanel
passwordField4.setEditable(false);
add(passwordField4, "cell 4 8,growx");
//---- passwordField5 ----
passwordField5.putClientProperty("JTextField.placeholderText", "placeholder");
add(passwordField5, "cell 5 8,growx");
//---- textAreaLabel ----
textAreaLabel.setText("JTextArea:");
add(textAreaLabel, "cell 0 9");

View File

@@ -253,6 +253,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 5,growx"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "comboBox6"
"editable": true
"$client.JTextField.placeholderText": "placeholder"
auxiliary() {
"JavaCodeGenerator.typeParameters": "String"
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 5,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "textFieldLabel"
"text": "JTextField:"
@@ -287,6 +297,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 6,growx"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "textField6"
"$client.JTextField.placeholderText": "placeholder"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 6,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "formattedTextFieldLabel"
"text": "JFormattedTextField:"
@@ -321,6 +337,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 7,growx"
} )
add( new FormComponent( "javax.swing.JFormattedTextField" ) {
name: "formattedTextField5"
"$client.JTextField.placeholderText": "placeholder"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 7,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "passwordFieldLabel"
"text": "JPasswordField:"
@@ -355,6 +377,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 8,growx"
} )
add( new FormComponent( "javax.swing.JPasswordField" ) {
name: "passwordField5"
"$client.JTextField.placeholderText": "placeholder"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 8,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "textAreaLabel"
"text": "JTextArea:"

View File

@@ -93,21 +93,25 @@ public class FlatComponentsTest
JLabel spinnerLabel = new JLabel();
JSpinner spinner1 = new JSpinner();
JSpinner spinner2 = new JSpinner();
JComboBox<String> comboBox7 = new JComboBox<>();
JLabel textFieldLabel = new JLabel();
JTextField textField1 = new JTextField();
JTextField textField2 = new JTextField();
JTextField textField3 = new JTextField();
JTextField textField4 = new JTextField();
JTextField textField6 = new JTextField();
JLabel formattedTextFieldLabel = new JLabel();
JFormattedTextField formattedTextField1 = new JFormattedTextField();
JFormattedTextField formattedTextField2 = new JFormattedTextField();
JFormattedTextField formattedTextField3 = new JFormattedTextField();
JFormattedTextField formattedTextField4 = new JFormattedTextField();
JFormattedTextField formattedTextField5 = new JFormattedTextField();
JLabel passwordFieldLabel = new JLabel();
JPasswordField passwordField1 = new JPasswordField();
JPasswordField passwordField2 = new JPasswordField();
JPasswordField passwordField3 = new JPasswordField();
JPasswordField passwordField4 = new JPasswordField();
JPasswordField passwordField5 = new JPasswordField();
JLabel textAreaLabel = new JLabel();
JScrollPane scrollPane1 = new JScrollPane();
JTextArea textArea1 = new JTextArea();
@@ -447,6 +451,11 @@ public class FlatComponentsTest
spinner2.setEnabled(false);
add(spinner2, "cell 2 6,growx");
//---- comboBox7 ----
comboBox7.setEditable(true);
comboBox7.putClientProperty("JTextField.placeholderText", "placeholder");
add(comboBox7, "cell 5 6,growx");
//---- textFieldLabel ----
textFieldLabel.setText("JTextField:");
add(textFieldLabel, "cell 0 7");
@@ -471,6 +480,10 @@ public class FlatComponentsTest
textField4.setEditable(false);
add(textField4, "cell 4 7,growx");
//---- textField6 ----
textField6.putClientProperty("JTextField.placeholderText", "placeholder");
add(textField6, "cell 5 7,growx");
//---- formattedTextFieldLabel ----
formattedTextFieldLabel.setText("JFormattedTextField:");
add(formattedTextFieldLabel, "cell 0 8");
@@ -495,6 +508,10 @@ public class FlatComponentsTest
formattedTextField4.setEditable(false);
add(formattedTextField4, "cell 4 8,growx");
//---- formattedTextField5 ----
formattedTextField5.putClientProperty("JTextField.placeholderText", "placeholder");
add(formattedTextField5, "cell 5 8,growx");
//---- passwordFieldLabel ----
passwordFieldLabel.setText("JPasswordField:");
add(passwordFieldLabel, "cell 0 9");
@@ -519,6 +536,10 @@ public class FlatComponentsTest
passwordField4.setEditable(false);
add(passwordField4, "cell 4 9,growx");
//---- passwordField5 ----
passwordField5.putClientProperty("JTextField.placeholderText", "placeholder");
add(passwordField5, "cell 5 9,growx");
//---- textAreaLabel ----
textAreaLabel.setText("JTextArea:");
add(textAreaLabel, "cell 0 10");

View File

@@ -313,6 +313,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 6,growx"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "comboBox7"
"editable": true
"$client.JTextField.placeholderText": "placeholder"
auxiliary() {
"JavaCodeGenerator.typeParameters": "String"
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 6,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "textFieldLabel"
"text": "JTextField:"
@@ -347,6 +357,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 7,growx"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "textField6"
"$client.JTextField.placeholderText": "placeholder"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 7,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "formattedTextFieldLabel"
"text": "JFormattedTextField:"
@@ -381,6 +397,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 8,growx"
} )
add( new FormComponent( "javax.swing.JFormattedTextField" ) {
name: "formattedTextField5"
"$client.JTextField.placeholderText": "placeholder"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 8,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "passwordFieldLabel"
"text": "JPasswordField:"
@@ -415,6 +437,12 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 9,growx"
} )
add( new FormComponent( "javax.swing.JPasswordField" ) {
name: "passwordField5"
"$client.JTextField.placeholderText": "placeholder"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 9,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "textAreaLabel"
"text": "JTextArea:"
@@ -949,7 +977,7 @@ new FormModel {
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 790, 750 )
"size": new java.awt.Dimension( 865, 750 )
} )
}
}