From d46d8a3a895e18a530d41952a68112464ecf4726 Mon Sep 17 00:00:00 2001 From: weisj Date: Mon, 14 Oct 2019 20:56:03 +0200 Subject: [PATCH] Improved value storage for ColorChooser. Made invalid input signal for ColorChooser less intrusive. Added error visualization for FormattedTextField. Fixed error where ListCellRenderer could not be loaded from defaults. --- .../ui/colorchooser/ColorValueFormatter.java | 65 +++++++++-------- .../colorchooser/DarkColorChooserPanel.java | 4 +- .../ui/filechooser/DarkFileChooserUI.java | 29 +++++--- .../filechooser/DarkFileChooserUIBridge.java | 9 +-- .../ui/filechooser/DarkFilePaneUIBridge.java | 4 +- .../darklaf/ui/filechooser/FileTextField.java | 39 ++++++++++ .../com/weis/darklaf/ui/list/DarkListUI.java | 4 ++ .../ui/text/DarkFormattedTextFieldUI.java | 72 ++++++++++++++++++- .../weis/darklaf/ui/text/DarkTextFieldUI.java | 2 +- .../properties/ui/colorChooser.properties | 2 +- .../darklaf/properties/ui/list.properties | 3 +- .../theme/darcula/darcula_defaults.properties | 2 +- src/test/java/FileChooserDemo.java | 15 ++++ 13 files changed, 198 insertions(+), 52 deletions(-) create mode 100644 src/main/java/com/weis/darklaf/ui/filechooser/FileTextField.java diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/ColorValueFormatter.java b/src/main/java/com/weis/darklaf/ui/colorchooser/ColorValueFormatter.java index 45b0c6dc..559c39c0 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/ColorValueFormatter.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/ColorValueFormatter.java @@ -33,6 +33,8 @@ import javax.swing.text.BadLocationException; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.DocumentFilter; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.text.ParseException; @@ -42,7 +44,8 @@ import static java.util.Locale.ENGLISH; /** * @author Jannis Weis */ -public final class ColorValueFormatter extends JFormattedTextField.AbstractFormatter implements FocusListener { +public final class ColorValueFormatter extends JFormattedTextField.AbstractFormatter implements FocusListener, + ActionListener { private final int fieldIndex; private final int radix; @@ -50,6 +53,7 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma private DarkColorModel model; private boolean transparencyEnabled; private JFormattedTextField text; + private final Timer errorTimer; private final DocumentFilter filter = new DocumentFilter() { @Override public void remove(@NotNull final FilterBypass fb, final int offset, @@ -58,7 +62,7 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma fb.remove(offset, length); commit(); } else { - Toolkit.getDefaultToolkit().beep(); + error(); } } @@ -75,7 +79,7 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma return; } } - Toolkit.getDefaultToolkit().beep(); + error(); } @Override @@ -90,7 +94,7 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma return; } } - Toolkit.getDefaultToolkit().beep(); + error(); } }; @@ -99,6 +103,20 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma this.fieldIndex = index; this.radix = hex ? 16 : 10; this.hex = hex; + this.errorTimer = new Timer(UIManager.getInt("ColorChooser.errorDelay"), this); + errorTimer.setRepeats(false); + } + + protected void error() { + text.putClientProperty("JTextField.hasError", true); + text.repaint(); + errorTimer.restart(); + } + + @Override + public void actionPerformed(final ActionEvent e) { + text.putClientProperty("JTextField.hasError", false); + text.repaint(); } @NotNull @@ -112,6 +130,8 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma } private void commit() { + text.putClientProperty("JTextField.hasError", false); + text.repaint(); SwingUtilities.invokeLater(() -> { try { if (text != null) { @@ -179,10 +199,7 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma return model.getDefault(fieldIndex); } if (hex) { - var hexStr = text; - if (hexStr.length() == 6) { - hexStr += "FF"; - } + var hexStr = String.format("%1$-" + getHexLength() + "s", text).replaceAll(" ", "F"); int r = Integer.valueOf(hexStr.substring(0, 2), 16); checkRange(r, 0, 255); int g = Integer.valueOf(hexStr.substring(2, 4), 16); @@ -191,7 +208,7 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma checkRange(b, 0, 255); int alpha = Integer.valueOf(hexStr.substring(6, 8), 16); checkRange(alpha, 0, 255); - return Integer.valueOf(text, radix); + return new Color(r, g, b, alpha); } else { var value = Integer.valueOf(text, this.radix); var min = model.getMinimum(fieldIndex); @@ -215,27 +232,19 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma @Contract("null -> fail") @Override public String valueToString(final Object object) throws ParseException { - if (object instanceof Integer) { - if (radix == 10) { + if (object instanceof Integer && !hex) { return object.toString(); + } else if (object instanceof Color && hex) { + var c = (Color) object; + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + int a = c.getAlpha(); + if (getHexLength() == 8) { + return String.format("%02X%02X%02X%02X", r, g, b, a); + } else { + return String.format("%02X%02X%02X", r, g, b); } - if (hex) { - if (getHexLength() == 8) { - var hexStr = String.format("%08X", object); - hexStr = hexStr.substring(2) + hexStr.substring(0, 2); - return hexStr; - } else { - return String.format("%06X", (0xFFFFFF & (Integer) object)); - } - } - int value = (Integer) object; - int index = getLength(); - char[] array = new char[index]; - while (0 < index--) { - array[index] = Character.forDigit(value & 0x0F, this.radix); - value >>= 4; - } - return new String(array).toUpperCase(ENGLISH); } throw new ParseException("illegal object", 0); } diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java b/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java index b4ee61c0..3c54af14 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/DarkColorChooserPanel.java @@ -375,9 +375,9 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements isChanging = true; boolean transparencyEnabled = isColorTransparencySelectionEnabled(); if (transparencyEnabled) { - textHex.setValue(c.getRGB()); + textHex.setValue(c); } else { - textHex.setValue(new Color(c.getRed(), c.getGreen(), c.getBlue()).getRGB()); + textHex.setValue(ColorUtil.removeAlpha(c)); } isChanging = changingOld; } diff --git a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUI.java b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUI.java index 12ab8243..38a3adec 100644 --- a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUI.java +++ b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUI.java @@ -32,6 +32,8 @@ import sun.swing.FilePane; import javax.accessibility.AccessibleContext; import javax.swing.*; import javax.swing.border.EmptyBorder; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; import javax.swing.filechooser.FileSystemView; import javax.swing.plaf.ComponentUI; import java.awt.*; @@ -225,13 +227,7 @@ public class DarkFileChooserUI extends DarkFileChooserUIBridge { populateFileNameLabel(); fileNamePanel.add(fileNameLabel); - @SuppressWarnings("serial") // anonymous class - JTextField tmp2 = new JTextField(35) { - public Dimension getMaximumSize() { - return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height); - } - }; - fileNameTextField = tmp2; + fileNameTextField = new FileTextField(); fileNamePanel.add(fileNameTextField); fileNameLabel.setLabelFor(fileNameTextField); fileNameTextField.addFocusListener( @@ -262,9 +258,22 @@ public class DarkFileChooserUI extends DarkFileChooserUIBridge { filterComboBoxModel = createFilterComboBoxModel(); fc.addPropertyChangeListener(filterComboBoxModel); filterComboBox = new JComboBox<>(filterComboBoxModel); - if (filterComboBox.getItemCount() == 0) { - filterComboBox.setEnabled(false); - } + filterComboBoxModel.addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(final ListDataEvent e) { + filterComboBox.setEnabled(filterComboBox.getItemCount() > 1); + } + + @Override + public void intervalRemoved(final ListDataEvent e) { + filterComboBox.setEnabled(filterComboBox.getItemCount() > 1); + } + + @Override + public void contentsChanged(final ListDataEvent e) { + filterComboBox.setEnabled(filterComboBox.getItemCount() > 1); + } + }); filterComboBox.putClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY, filesOfTypeLabelText); filesOfTypeLabel.setLabelFor(filterComboBox); diff --git a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUIBridge.java b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUIBridge.java index 32627945..5a90eeb6 100644 --- a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUIBridge.java +++ b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFileChooserUIBridge.java @@ -52,6 +52,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.Locale; +import java.util.Objects; import java.util.Vector; @@ -475,7 +476,7 @@ public class DarkFileChooserUIBridge extends BasicFileChooserUI { if (o != e.getOldValue()) { cc.applyComponentOrientation(o); } - } else if (s == "FileChooser.useShellFolder") { + } else if (Objects.equals(s, "FileChooser.useShellFolder")) { doDirectoryChanged(e); } else if (s.equals("ancestor")) { if (e.getOldValue() == null && e.getNewValue() != null) { @@ -1269,10 +1270,10 @@ public class DarkFileChooserUIBridge extends BasicFileChooserUI { public void propertyChange(final PropertyChangeEvent e) { String prop = e.getPropertyName(); - if (prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) { + if (Objects.equals(prop, JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY)) { filters = (FileFilter[]) e.getNewValue(); fireContentsChanged(this, -1, -1); - } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) { + } else if (Objects.equals(prop, JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) { fireContentsChanged(this, -1, -1); } } @@ -1298,7 +1299,7 @@ public class DarkFileChooserUIBridge extends BasicFileChooserUI { found = true; } } - if (found == false) { + if (!found) { getFileChooser().addChoosableFileFilter(currentFilter); } } diff --git a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java index 4e768bcb..a932f827 100644 --- a/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java +++ b/src/main/java/com/weis/darklaf/ui/filechooser/DarkFilePaneUIBridge.java @@ -486,7 +486,7 @@ public class DarkFilePaneUIBridge extends JPanel implements PropertyChangeListen listViewWindowsStyle = UIManager.getBoolean("FileChooser.listViewWindowsStyle"); readOnly = UIManager.getBoolean("FileChooser.readOnly"); - // TODO: On windows, get the following localized strings from the OS + // TUDU: On windows, get the following localized strings from the OS viewMenuLabelText = UIManager.getString("FileChooser.viewMenuLabelText", l); @@ -1797,7 +1797,7 @@ public class DarkFilePaneUIBridge extends JPanel implements PropertyChangeListen setHorizontalAlignment(alignment); // formatting cell text - // TODO: it's rather a temporary trick, to be revised + // TUDU: it's rather a temporary trick, to be revised String text; if (value == null) { text = ""; diff --git a/src/main/java/com/weis/darklaf/ui/filechooser/FileTextField.java b/src/main/java/com/weis/darklaf/ui/filechooser/FileTextField.java new file mode 100644 index 00000000..7a43e36f --- /dev/null +++ b/src/main/java/com/weis/darklaf/ui/filechooser/FileTextField.java @@ -0,0 +1,39 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.weis.darklaf.ui.filechooser; + +import javax.swing.*; +import java.awt.*; + +public class FileTextField extends JTextField { + + public FileTextField() { + super(35); + } + + public Dimension getMaximumSize() { + return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height); + } + +} diff --git a/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java b/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java index 08adcd57..2903d86a 100644 --- a/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java +++ b/src/main/java/com/weis/darklaf/ui/list/DarkListUI.java @@ -15,6 +15,10 @@ import java.awt.event.MouseEvent; */ public class DarkListUI extends DarkListUIBridge { + static { + UIManager.put("List.cellRenderer", new DarkListCellRenderer()); + } + @NotNull @Contract("_ -> new") public static ComponentUI createUI(final JComponent list) { diff --git a/src/main/java/com/weis/darklaf/ui/text/DarkFormattedTextFieldUI.java b/src/main/java/com/weis/darklaf/ui/text/DarkFormattedTextFieldUI.java index 359fb22a..9015d40a 100644 --- a/src/main/java/com/weis/darklaf/ui/text/DarkFormattedTextFieldUI.java +++ b/src/main/java/com/weis/darklaf/ui/text/DarkFormattedTextFieldUI.java @@ -27,16 +27,86 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.plaf.ComponentUI; +import javax.swing.text.Document; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.ParseException; /** * @author Jannis Weis */ -public class DarkFormattedTextFieldUI extends DarkTextFieldUI { +public class DarkFormattedTextFieldUI extends DarkTextFieldUI implements PropertyChangeListener, DocumentListener { + + private JFormattedTextField textField; @NotNull @Contract("_ -> new") public static ComponentUI createUI(final JComponent c) { return new DarkFormattedTextFieldUI(); } + + protected String getPropertyPrefix() { + return "FormattedTextField"; + } + + @Override + public void installUI(final JComponent c) { + textField = (JFormattedTextField) c; + super.installUI(c); + } + + @Override + public void propertyChange(final PropertyChangeEvent evt) { + super.propertyChange(evt); + if ("document".equals(evt.getPropertyName())) { + var oldDoc = evt.getOldValue(); + var newDoc = evt.getNewValue(); + if (oldDoc instanceof Document) { + ((Document) oldDoc).removeDocumentListener(this); + } + if (newDoc instanceof Document) { + ((Document) newDoc).addDocumentListener(this); + } + } + } + + @Override + protected void installListeners() { + super.installListeners(); + textField.getDocument().addDocumentListener(this); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + textField.getDocument().removeDocumentListener(this); + } + + @Override + public void insertUpdate(final DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(final DocumentEvent e) { + update(); + } + + @Override + public void changedUpdate(final DocumentEvent e) { + update(); + } + + protected void update() { + if (textField == null) return; + try { + textField.getFormatter().stringToValue(textField.getText()); + textField.putClientProperty("JTextField.hasError", false); + } catch (ParseException e) { + textField.putClientProperty("JTextField.hasError", true); + } + } } diff --git a/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java b/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java index 6d2886d2..8ef07b97 100644 --- a/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java +++ b/src/main/java/com/weis/darklaf/ui/text/DarkTextFieldUI.java @@ -116,7 +116,7 @@ public class DarkTextFieldUI extends DarkTextFieldUIBridge implements PropertyCh @Contract("null -> false") protected static boolean hasError(final Component c) { - return c instanceof JComponent && Boolean.TRUE.equals(((JComponent) c).getClientProperty("error")); + return c instanceof JComponent && Boolean.TRUE.equals(((JComponent) c).getClientProperty("JTextField.hasError")); } public static Color getBackgroundColor(@NotNull final JTextComponent c) { diff --git a/src/main/resources/com/weis/darklaf/properties/ui/colorChooser.properties b/src/main/resources/com/weis/darklaf/properties/ui/colorChooser.properties index f41b8821..46670157 100644 --- a/src/main/resources/com/weis/darklaf/properties/ui/colorChooser.properties +++ b/src/main/resources/com/weis/darklaf/properties/ui/colorChooser.properties @@ -35,7 +35,7 @@ ColorChooser.swatchesSwatchSize = 15,15 ColorChooser.swatchBorderColor = %borderSecondary ColorChooser.swatchGridColor = %gridLine ColorChooser.sliderShadow = %shadow - +ColorChooser.errorDelay = 600 #Icons ColorChooser.pipette.icon = misc/pipette.svg[aware] ColorChooser.pipetteRollover.icon = misc/pipette_rollover.svg[aware] diff --git a/src/main/resources/com/weis/darklaf/properties/ui/list.properties b/src/main/resources/com/weis/darklaf/properties/ui/list.properties index e4b06a37..4061493d 100644 --- a/src/main/resources/com/weis/darklaf/properties/ui/list.properties +++ b/src/main/resources/com/weis/darklaf/properties/ui/list.properties @@ -22,7 +22,6 @@ # # suppress inspection "UnusedProperty" for whole file ListUI = com.weis.darklaf.ui.list.DarkListUI -List.cellRenderer = com.weis.darklaf.ui.list.DarkListCellRenderer List.border = null List.background = %background List.focusSelectedCellHighlightBorder = com.weis.darklaf.ui.list.DarkListCellFocusBorder @@ -30,4 +29,4 @@ List.focusCellHighlightBorder = com.weis.darklaf.ui.list.DarkListCellBor List.cellNoFocusBorder = com.weis.darklaf.ui.list.DarkListCellBorder List.dropLineColor = %dropForeground List.selectionBackground = %highlightFillFocus -List.focusBorderColor = %borderFocus +List.focusBorderColor = %borderFocus \ No newline at end of file diff --git a/src/main/resources/com/weis/darklaf/theme/darcula/darcula_defaults.properties b/src/main/resources/com/weis/darklaf/theme/darcula/darcula_defaults.properties index 85db3a0b..f5b5d24b 100644 --- a/src/main/resources/com/weis/darklaf/theme/darcula/darcula_defaults.properties +++ b/src/main/resources/com/weis/darklaf/theme/darcula/darcula_defaults.properties @@ -118,7 +118,7 @@ glowError = cf6767 glowErrorLine = cf6767 glowFocusError = c2413c -glowFocusErrorLine = c2413c +glowFocusErrorLine = cf6767 glowWarning = cf6b30 glowWarningLine = cf6b30 \ No newline at end of file diff --git a/src/test/java/FileChooserDemo.java b/src/test/java/FileChooserDemo.java index 88825da4..12dac4e7 100644 --- a/src/test/java/FileChooserDemo.java +++ b/src/test/java/FileChooserDemo.java @@ -1,6 +1,9 @@ import com.weis.darklaf.LafManager; import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; +import java.io.File; public final class FileChooserDemo { @@ -8,6 +11,18 @@ public final class FileChooserDemo { SwingUtilities.invokeLater(() -> { LafManager.install(); var chooser = new JFileChooser(System.getProperty("user.home")); + var filter = new FileNameExtensionFilter("JSON files", "json"); + chooser.addChoosableFileFilter(new FileFilter() { + @Override + public boolean accept(final File f) { + return f.isDirectory() || filter.accept(f); + } + + @Override + public String getDescription() { + return filter.getDescription(); + } + }); chooser.setMultiSelectionEnabled(true); var frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);