/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.scenegraph.swing; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JTextField; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import org.simantics.scenegraph.ExportableWidget.InputWidget; import org.simantics.scenegraph.ExportableWidget.OutputWidget; import org.simantics.scenegraph.utils.DummyComponent; @OutputWidget("value") @InputWidget("value") public class MonitorNode extends ComponentNode implements ActionListener, FocusListener, PropertyChangeListener, KeyListener { /** * */ private static final long serialVersionUID = 7073028693751719102L; protected boolean editable = true; protected String value = ""; protected String tooltip = ""; protected double borderWidth = 0; protected transient ActionListener actionListener = null; private boolean doResize = false; protected Font font = null; protected Color color = null; protected int halign = JTextField.LEFT; // See JTextField for value options static class TextField extends JTextField { private static final int X_INSET = 5; private static final int Y_INSET = -1; private static final long serialVersionUID = -668522226693100386L; private Border lineBorder; private final MonitorNode node; public TextField(double borderWidth, MonitorNode node) { if(borderWidth < 0.1) { lineBorder = new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET); } else { lineBorder = new CompoundBorder(LineBorder.createGrayLineBorder(), new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET)); } this.node = node; setHorizontalAlignment(JTextField.CENTER); setAlignmentY(JTextField.CENTER_ALIGNMENT); } // workaround for 4530952 @Override public void setText(String s) { if (getText().equals(s)) { return; } super.setText(s); } @Override public void setBorder(Border border) { super.setBorder(lineBorder); } public void setBorder(double borderWidth) { if(borderWidth < 0.1) { lineBorder = new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET); } else { lineBorder = new CompoundBorder(LineBorder.createGrayLineBorder(), new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET)); } super.setBorder(lineBorder); } @Override public Point getToolTipLocation(MouseEvent event) { Point2D p2d = node.localToControl(event.getPoint()); Point p = new Point((int)p2d.getX(),-20+(int)p2d.getY()); return p; } } @Override public String toString() { return super.toString() + "[editable=" + editable + ", value=" + value + "]"; } private void markResize() { //System.out.println("MonitorNode.markResize()"); doResize = true; } @Override public void init() { component = new TextField(borderWidth,this); component.setEditable(editable); component.setEnabled(editable); component.addActionListener(this); component.addFocusListener(this); component.addKeyListener(this); super.init(); } @SyncField("editable") public void setEditable(boolean value) { this.editable = value; if(component != null) { component.setEditable(value); component.setEnabled(value); } } @PropertySetter("Stroke Width") @SyncField("borderWidth") public void setBorderWidth(Float borderWidth) { this.borderWidth = borderWidth; if(component != null) { ((TextField)component).setBorder(borderWidth); } } @SyncField("value") public void setText(String value) { this.value = value; // RemoteViewer does not have component initialized if (component != null) { //System.out.println("MonitorNode.setText(" + value + ")"); component.setText(value); markResize(); component.repaint(); } } @SyncField("tooltip") public void setToolTipText(String tooltip) { this.tooltip = tooltip; if (component != null) { component.setToolTipText(tooltip); } } @PropertySetter("Font") @SyncField("font") public void setFont(Font font) { this.font = font; if (component != null) { setComponentFont(font); markResize(); } } @PropertySetter("Color") @SyncField("color") public void setColor(Color color) { this.color = color; if (component != null) { component.setForeground(color); markResize(); } } @SyncField("halign") public void setHorizontalAlign(int halign) { this.halign = halign; } @Override public void render(Graphics2D g2d) { if (doResize) recalculateSize(g2d); doResize = false; if (component != null) { synchronized (component) { if (component.getHorizontalAlignment() != halign) component.setHorizontalAlignment(halign); super.render(g2d); } } } private void recalculateSize(Graphics2D g2d) { //System.out.println("MonitorNode.recalculateSize(" + value + ")"); if (component == null || value == null) return; Font font = getComponentFont(); if (font != null) { String measuredValue = value; //String measuredValue = value + "x"; // If bounds are NOT set, calculate size.. if (bounds == null) { FontMetrics metrics = component.getFontMetrics(font); Rectangle2D size = metrics.getStringBounds(measuredValue, g2d); int xPadding = 15; int yPadding = 2; // setSize(xPadding + (int) Math.ceil(size.getWidth()), yPadding + (int) Math.ceil(size.getHeight())); setBounds(new Rectangle2D.Double(0, 0, xPadding + (int) Math.ceil(size.getWidth()), yPadding + (int) Math.ceil(size.getHeight()))); //component.setScrollOffset(0); } else { // ... If bounds are set, change font size to get the text fit // into the bounds. Find the best fit through bisection. // min is assumed to fit within the bounds. It will not be searched float min = 6; // First find an upper bound that no longer fits within the bounds. // First guess is 62. float max = 62; final float upperLimit = 200; while (max < upperLimit) { font = font.deriveFont(max); FontMetrics metrics = component.getFontMetrics(font); Rectangle2D fbounds = metrics.getStringBounds(measuredValue, g2d); if (!fits(bounds, fbounds)) break; max += 20; } if (max < upperLimit) { // Bisect the largest font size in [min,max] that fits the bounds. while (true) { float half = (max + min) / 2; float interval = max - min; font = font.deriveFont(half); FontMetrics metrics = component.getFontMetrics(font); Rectangle2D fbounds = metrics.getStringBounds(measuredValue, g2d); if (fits(bounds, fbounds)) { // Fits within bounds, bisect [half, max] if (interval <= 1) { break; } min = half; } else { // Does not fit within bounds, bisect [min, half] if (interval <= 1) { font = font.deriveFont(min); break; } max = half; } } } setComponentFont(font); // setSize((int)bounds.getWidth(), (int)bounds.getHeight()); } } } private boolean fits(Rectangle2D parent, Rectangle2D child) { return parent.getWidth() >= child.getWidth() && parent.getHeight() >= child.getHeight(); } public String getText() { return value; } public Font getFont() { return font; } @Override public void propertyChange(PropertyChangeEvent evt) { if("value".equals(evt.getPropertyName()) && component != null) { synchronized(component) { component.setText((String)evt.getNewValue()); markResize(); component.repaint(); } } else if("editable".equals(evt.getPropertyName()) && component != null) { synchronized(component) { component.setEditable((Boolean)evt.getNewValue()); component.setEnabled((Boolean)evt.getNewValue()); } } } public void setActionListener(ActionListener actionListener) { this.actionListener = actionListener; } @Override public void actionPerformed(ActionEvent e) { // performAction(e); loseFocus(); } void loseFocus() { if (component != null) if (component.isFocusOwner()) if (container.getParent() != null) container.getParent().requestFocusInWindow(); // Lose focus } @Override public void focusGained(FocusEvent arg0) { if (component != null) { component.selectAll(); } } @Override public void focusLost(FocusEvent arg0) { if (component != null) { ActionEvent e = new ActionEvent(component, ActionEvent.ACTION_PERFORMED, component.getText()); performAction(e); } } /** * Wrapper method to send event to serverside * * @param e */ @ServerSide public void performAction(ActionEvent e) { if (actionListener != null) { //System.out.println("MonitorNode.performAction(" + e + ")"); actionListener.actionPerformed(e); } } @Override public void keyPressed(KeyEvent e) { } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { if (e.getModifiers() == 0 && e.getKeyCode() == KeyEvent.VK_ESCAPE) { // ESC without modifiers == CANCEL edit // TODO: signal about cancellation loseFocus(); } } public String widgetGet(String name) { if("value".equals(name)) { return ""+value; } return null; } public void widgetSet(String name, String value) { if("value".equals(name)) { ActionEvent e = new ActionEvent(new DummyComponent(), ActionEvent.ACTION_PERFORMED, value); performAction(e); } } }