diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 58c287ad2..a1d6327f7 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -1058,6 +1058,8 @@ AppearanceCfg.lbl.ttip.separateInsideOutside = Use a separate appearance for out AppearanceCfg.lbl.ttip.separateLeftSideRightSide = Use a separate appearance for left and right side(s) AppearanceCfg.lbl.AppearanceEdges = Appearance for edges: AppearanceCfg.lbl.ttip.AppearanceEdges = Select the appearance that should be used for the edges +AppearanceCfg.placeholder.HexColor = Hex color +AppearanceCfg.ttip.HexColor = Hexadecimal representation of the color ! Texture Wrap Modes TextureWrap.Repeat = Repeat diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/AppearancePanel.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/AppearancePanel.java index 3966cd51b..60ac64b6f 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/AppearancePanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/AppearancePanel.java @@ -17,6 +17,7 @@ import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JComboBox; import javax.swing.JSeparator; +import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.JOptionPane; @@ -27,6 +28,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import info.openrocket.core.util.Invalidatable; +import info.openrocket.swing.gui.widgets.PlaceholderTextField; import net.miginfocom.swing.MigLayout; import info.openrocket.core.appearance.Appearance; import info.openrocket.core.appearance.AppearanceBuilder; @@ -126,7 +128,7 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati } } - private class ColorActionListener implements ActionListener { + private abstract class ColorActionListener { private final String valueName; private final Object o; @@ -139,23 +141,35 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati Changes the color of the selected component to @param color: color to change the component to */ - private void changeComponentColor(Color color) { + protected void setComponentColor(Color color) { try { - final Method setMethod = o.getClass().getMethod( - "set" + valueName, ORColor.class); + final Method setMethod = o.getClass().getMethod("set" + valueName, ORColor.class); if (color == null) return; try { - setMethod.invoke(o, ColorConversion - .fromAwtColor(color)); + setMethod.invoke(o, ColorConversion.fromAwtColor(color)); } catch (Throwable e1) { - Application.getExceptionHandler() - .handleErrorCondition(e1); + Application.getExceptionHandler().handleErrorCondition(e1); } } catch (Throwable e1) { Application.getExceptionHandler().handleErrorCondition(e1); } + } + protected ORColor getComponentColor() { + try { + final Method getMethod = o.getClass().getMethod("get" + valueName); + return (ORColor) getMethod.invoke(o); + } catch (Throwable e1) { + Application.getExceptionHandler().handleErrorCondition(e1); + return null; + } + } + } + + private class ColorButtonActionListener extends ColorActionListener implements ActionListener { + ColorButtonActionListener(final Object o, final String valueName) { + super(o, valueName); } /** @@ -166,10 +180,7 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati @Override public void actionPerformed(ActionEvent colorClickEvent) { try { - final Method getMethod = o.getClass().getMethod( - "get" + valueName); - ORColor c = (ORColor) getMethod - .invoke(o); + ORColor c = getComponentColor(); Color awtColor = ColorConversion.toAwtColor(c); colorChooser.setColor(awtColor); @@ -180,7 +191,7 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { Color selected = colorChooser.getColor(); - changeComponentColor(selected); + setComponentColor(selected); } }; model.addChangeListener(changeListener); @@ -191,14 +202,14 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati new ActionListener() { @Override public void actionPerformed(ActionEvent okEvent) { - changeComponentColor(colorChooser.getColor()); + setComponentColor(colorChooser.getColor()); // Unbind listener to avoid the current component's appearance to change with other components model.removeChangeListener(changeListener); } }, new ActionListener() { @Override public void actionPerformed(ActionEvent cancelEvent) { - changeComponentColor(awtColor); + setComponentColor(awtColor); // Unbind listener to avoid the current component's appearance to change with other components model.removeChangeListener(changeListener); } @@ -210,6 +221,28 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati } } + private class ColorHexActionListener extends ColorActionListener implements ActionListener { + public ColorHexActionListener(final Object o, final String valueName) { + super(o, valueName); + } + + @Override + public void actionPerformed(ActionEvent e) { + JTextField field = (JTextField) e.getSource(); + String hex = field.getText(); + try { + ORColor color = ColorConversion.fromHexColor(hex); + if (color == null) { + field.setText(ColorConversion.toHexColor(getComponentColor())); + return; + } + setComponentColor(ColorConversion.toAwtColor(color)); + } catch (IllegalArgumentException ex) { + field.setText(ColorConversion.toHexColor(getComponentColor())); + } + } + } + /** * Appearance panel for the appearance of a rocket component. * @param document current document @@ -271,10 +304,17 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati } final JButton figureColorButton = new JButton( new ColorIcon(figureColor)); + PlaceholderTextField figureColorHexField = new PlaceholderTextField(7); + figureColorHexField.setPlaceholder(trans.get("AppearanceCfg.placeholder.HexColor")); + figureColorHexField.setToolTipText(trans.get("AppearanceCfg.ttip.HexColor")); + figureColorHexField.setText(ColorConversion.toHexColor(c.getColor())); ab.addChangeListener(new StateChangeListener() { @Override - public void stateChanged(EventObject e) { figureColorButton.setIcon(new ColorIcon(c.getColor())); } + public void stateChanged(EventObject e) { + figureColorButton.setIcon(new ColorIcon(c.getColor())); + figureColorHexField.setText(ColorConversion.toHexColor(c.getColor())); + } }); c.addChangeListener(new StateChangeListener() { @@ -285,11 +325,12 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati col = ((SwingPreferences) Application.getPreferences()).getDefaultColor(c.getClass()); } figureColorButton.setIcon(new ColorIcon(col)); + figureColorHexField.setText(ColorConversion.toHexColor(col)); } }); - figureColorButton - .addActionListener(new ColorActionListener(c, "Color")); + figureColorButton.addActionListener(new ColorButtonActionListener(c, "Color")); + figureColorHexField.addActionListener(new ColorHexActionListener(c, "Color")); BooleanModel fDefault = new BooleanModel(c.getColor() == null); register(fDefault); @@ -342,9 +383,15 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati {// Figure Color add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor"))); + JPanel colorPanel = new JPanel(new MigLayout("ins 0")); + colorPanel.add(figureColorButton); + figureColorHexField.setColumns(7); + colorPanel.add(figureColorHexField); fDefault.addEnableComponent(figureColorButton, false); - add(figureColorButton); + fDefault.addEnableComponent(figureColorHexField, false); + add(colorPanel, "growx"); order.add(figureColorButton); + order.add(figureColorHexField); } order.add(saveAsDefault); @@ -532,8 +579,13 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati }); JButton colorButton = new JButton(new ColorIcon(builder.getPaint())); + PlaceholderTextField colorHexField = new PlaceholderTextField(7); + colorHexField.setPlaceholder(trans.get("AppearanceCfg.placeholder.HexColor")); + colorHexField.setToolTipText(trans.get("AppearanceCfg.ttip.HexColor")); + colorHexField.setText(ColorConversion.toHexColor(builder.getPaint())); - colorButton.addActionListener(new ColorActionListener(builder, "Paint")); + colorButton.addActionListener(new ColorButtonActionListener(builder, "Paint")); + colorHexField.addActionListener(new ColorHexActionListener(builder, "Paint")); // Texture Header Row panel.add(new StyledLabel(trans.get("AppearanceCfg.lbl.Appearance"), @@ -630,9 +682,15 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati // TODO: move the separate columns in two separate panels instead of adding them in a zig-zag way // Color panel.add(new JLabel(trans.get("AppearanceCfg.lbl.color.Color"))); + JPanel colorPanel = new JPanel(new MigLayout("ins 0")); + colorPanel.add(colorButton); + colorHexField.setColumns(7); + colorPanel.add(colorHexField); mDefault.addEnableComponent(colorButton, false); - panel.add(colorButton); + mDefault.addEnableComponent(colorHexField, false); + panel.add(colorPanel, "growx"); order.add(colorButton); + order.add(colorHexField); // Scale panel.add(new JLabel(trans.get("AppearanceCfg.lbl.texture.scale")), "gapleft para"); @@ -674,7 +732,7 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati panel.add(spinShine, "split 3, w 60"); panel.add(unitShine); panel.add(slideShine, "w 100lp"); - order.add(order.indexOf(colorButton) + 1, ((SpinnerEditor) spinShine.getEditor()).getTextField()); + order.add(order.indexOf(colorHexField) + 1, ((SpinnerEditor) spinShine.getEditor()).getTextField()); // Offset panel.add(new JLabel(trans.get("AppearanceCfg.lbl.texture.offset")), "gapleft para"); @@ -749,6 +807,7 @@ public class AppearancePanel extends JPanel implements Invalidatable, Invalidati @Override public void stateChanged(EventObject e) { colorButton.setIcon(new ColorIcon(builder.getPaint())); + colorHexField.setText(ColorConversion.toHexColor(builder.getPaint())); if (lastOpacity != builder.getOpacity()) { opacityModel.stateChanged(null); lastOpacity = builder.getOpacity(); diff --git a/swing/src/main/java/info/openrocket/swing/gui/util/ColorConversion.java b/swing/src/main/java/info/openrocket/swing/gui/util/ColorConversion.java index 225974992..6ed1e73ec 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/util/ColorConversion.java +++ b/swing/src/main/java/info/openrocket/swing/gui/util/ColorConversion.java @@ -27,4 +27,28 @@ public class ColorConversion { String hexColor = String.format("#%02x%02x%02x", c.getRed(), c.getGreen(), c.getBlue()); return String.format("%s", hexColor, content); } + + public static String toHexColor(ORColor c) { + if (c == null) { + return null; + } + return String.format("#%02x%02x%02x", c.getRed(), c.getGreen(), c.getBlue()).toUpperCase(); + } + + public static ORColor fromHexColor(String hexColor) { + if (hexColor == null || hexColor.isBlank()) { + return null; + } + if (hexColor.startsWith("#")) { + hexColor = hexColor.substring(1); + } + hexColor = hexColor.trim(); + if (hexColor.length() != 6 || hexColor.matches("^#[0-9A-Fa-f]{6}$")) { + throw new IllegalArgumentException("Invalid hex color: " + hexColor); + } + int red = Integer.parseInt(hexColor.substring(0, 2), 16); + int green = Integer.parseInt(hexColor.substring(2, 4), 16); + int blue = Integer.parseInt(hexColor.substring(4, 6), 16); + return new ORColor(red, green, blue); + } }