/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2016 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 3 for more details. * * You should have received a copy of the GNU General Public License version 3 * along with this work; if not, see http://www.gnu.org/licenses/ * * * Please visit http://neilcsmith.net if you need additional information or * have any questions. */ package net.neilcsmith.praxis.live.pxr.editors; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyEditor; import java.lang.reflect.InvocationTargetException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.border.EmptyBorder; import net.neilcsmith.praxis.core.Argument; import net.neilcsmith.praxis.core.types.PNumber; import org.openide.explorer.propertysheet.InplaceEditor; import org.openide.explorer.propertysheet.PropertyEnv; import org.openide.explorer.propertysheet.PropertyModel; /** * * @author Neil C Smith (http://neilcsmith.net) */ class NumberInplaceEditor extends JComponent implements InplaceEditor { private static final Logger LOG = Logger.getLogger(NumberInplaceEditor.class.getName()); private static final Color ACTIVE_COLOR = Color.WHITE; private static final Color INACTIVE_COLOR = Color.GRAY; private static final DecimalFormat FORMATTER = new DecimalFormat("####0.0####"); private static final long IGNORE_CLICK_TIME = 500 * 1000000; private PropertyEditor propertyEditor; private PropertyModel propertyModel; private final List<ActionListener> listeners; private JTextField textField; private Object initialValue; private PNumber currentValue; private final double min; private final double max; private final double skew; NumberInplaceEditor(double min, double max, double skew) { this.min = min; this.max = max; this.skew = skew < 0.125 ? 0.125 : skew > 8 ? 8 : skew; listeners = new ArrayList<>(); initThis(); initComponents(); } private void initThis() { setFocusable(true); setLayout(new GridLayout()); MouseHandler handler = new MouseHandler(); addMouseListener(handler); addMouseMotionListener(handler); } private void initComponents() { textField = new JTextField(); textField.setVisible(false); textField.setBorder(new EmptyBorder(0,0,0,0)); textField.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { fireActionEvent(true); } }); add(textField); } @Override protected void paintComponent(Graphics g) { if (textField.isVisible()) { return; } Rectangle bounds = new Rectangle(3, 0, getWidth() - 3, getHeight()); paintValue(g, bounds, currentValue.value(), true); } void paintValue(Graphics g, Rectangle box, double value, boolean highlight) { Color c = g.getColor(); String stringValue = FORMATTER.format(value); double delta = (value - min) / (max - min); delta = Math.pow(delta, 1.0/skew); int x = (int) (delta * (box.width - 1)); x += box.x; FontMetrics fm = g.getFontMetrics(); if (!highlight) { g.setColor(INACTIVE_COLOR); g.drawLine(x, box.y, x, box.height); g.setColor(c); g.drawString(stringValue, box.x, box.y + (box.height - fm.getHeight()) / 2 + fm.getAscent()); } else { g.setColor(INACTIVE_COLOR); g.drawString(stringValue, box.x, box.y + (box.height - fm.getHeight()) / 2 + fm.getAscent()); g.setColor(ACTIVE_COLOR); g.drawLine(x, box.y, x, box.height); g.setColor(c); } } @Override public void connect(PropertyEditor pe, PropertyEnv env) { this.propertyEditor = pe; initialValue = pe.getValue(); textField.setVisible(false); AWTEvent event = EventQueue.getCurrentEvent(); LOG.log(Level.FINE, "Invoking Event: {0}", event); if (event instanceof KeyEvent) { EventQueue.invokeLater(new Runnable() { @Override public void run() { focusTextField(); } }); } reset(); } @Override public JComponent getComponent() { return this; } @Override public void clear() { propertyEditor = null; propertyModel = null; } @Override public void setValue(Object o) { try { currentValue = PNumber.coerce((Argument) o); } catch (Exception ex) { LOG.log(Level.FINE, "Exception in setValue()", ex); if (currentValue == null) { currentValue = PNumber.valueOf(0); } } if (textField.isVisible()) { textField.setText(currentValue.toString()); } } @Override public Object getValue() { if (textField.isVisible()) { try { return PNumber.valueOf(textField.getText()); } catch (Exception ex) { LOG.log(Level.FINE, "Exception in getValue()", ex); } } return currentValue; } @Override public boolean supportsTextEntry() { return true; } @Override public void reset() { LOG.fine("Reset Called"); setValue(initialValue); } @Override public void addActionListener(ActionListener al) { listeners.add(al); } @Override public void removeActionListener(ActionListener al) { listeners.remove(al); } private void fireActionEvent(boolean success) { ActionEvent ev = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, success ? COMMAND_SUCCESS : COMMAND_FAILURE); for (ActionListener l : listeners.toArray(new ActionListener[0])) { l.actionPerformed(ev); } } @Override public KeyStroke[] getKeyStrokes() { return new KeyStroke[0]; } @Override public PropertyEditor getPropertyEditor() { return propertyEditor; } @Override public PropertyModel getPropertyModel() { return propertyModel; } @Override public void setPropertyModel(PropertyModel pm) { this.propertyModel = pm; } @Override public boolean isKnownComponent(Component c) { return c == this || c == textField; } private void updateValue(double value) { currentValue = PNumber.valueOf(value); try { propertyModel.setValue(currentValue); } catch (InvocationTargetException ex) { } repaint(); } private void focusTextField() { textField.setText(currentValue.toString()); textField.setVisible(true); textField.selectAll(); textField.requestFocusInWindow(); } private class MouseHandler extends MouseAdapter { private int startX; private int valueX; private boolean dragging; private long clickTime; @Override public void mousePressed(MouseEvent me) { if (textField.isVisible()) { return; } clickTime = System.nanoTime(); startX = me.getX(); double delta = (currentValue.value() - min) / (max - min); delta = Math.pow(delta, 1.0/skew); valueX = (int) (delta * (getWidth()) + 0.5); } @Override public void mouseReleased(MouseEvent me) { if (dragging) { dragging = false; update(me); fireActionEvent(true); } else if ((System.nanoTime() - clickTime) < IGNORE_CLICK_TIME) { focusTextField(); } else { fireActionEvent(false); } } @Override public void mouseDragged(MouseEvent me) { dragging = true; update(me); } private void update(MouseEvent me) { int curX = valueX + (me.getX() - startX); double delta = curX / (double) getWidth(); delta = delta < 0 ? 0 : delta > 1 ? 1 : delta; delta = Math.pow(delta, skew); delta = delta * (max - min) + min; updateValue(delta); } } }