1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.swing;
14 import java.awt.Color;
16 import java.awt.FontMetrics;
17 import java.awt.Graphics2D;
18 import java.awt.Point;
19 import java.awt.event.ActionEvent;
20 import java.awt.event.ActionListener;
21 import java.awt.event.FocusEvent;
22 import java.awt.event.FocusListener;
23 import java.awt.event.KeyEvent;
24 import java.awt.event.KeyListener;
25 import java.awt.event.MouseEvent;
26 import java.awt.geom.Point2D;
27 import java.awt.geom.Rectangle2D;
28 import java.beans.PropertyChangeEvent;
29 import java.beans.PropertyChangeListener;
31 import javax.swing.JTextField;
32 import javax.swing.border.Border;
33 import javax.swing.border.CompoundBorder;
34 import javax.swing.border.EmptyBorder;
35 import javax.swing.border.LineBorder;
37 import org.simantics.scenegraph.ExportableWidget.InputWidget;
38 import org.simantics.scenegraph.ExportableWidget.OutputWidget;
39 import org.simantics.scenegraph.utils.DummyComponent;
41 @OutputWidget("value")
43 public class MonitorNode extends ComponentNode<JTextField> implements ActionListener, FocusListener, PropertyChangeListener, KeyListener {
47 private static final long serialVersionUID = 7073028693751719102L;
49 protected boolean editable = true;
50 protected String value = "";
51 protected String tooltip = "";
52 protected double borderWidth = 0;
54 protected transient ActionListener actionListener = null;
56 private boolean doResize = false;
58 protected Font font = null;
59 protected Color color = null;
60 protected int halign = JTextField.LEFT; // See JTextField for value options
62 static class TextField extends JTextField {
63 private static final int X_INSET = 5;
64 private static final int Y_INSET = -1;
65 private static final long serialVersionUID = -668522226693100386L;
66 private Border lineBorder;
67 private final MonitorNode node;
69 public TextField(double borderWidth, MonitorNode node) {
70 if(borderWidth < 0.1) {
71 lineBorder = new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET);
73 lineBorder = new CompoundBorder(LineBorder.createGrayLineBorder(), new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET));
76 setHorizontalAlignment(JTextField.CENTER);
77 setAlignmentY(JTextField.CENTER_ALIGNMENT);
80 // workaround for 4530952
82 public void setText(String s) {
83 if (getText().equals(s)) {
91 public void setBorder(Border border) {
92 super.setBorder(lineBorder);
95 public void setBorder(double borderWidth) {
96 if(borderWidth < 0.1) {
97 lineBorder = new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET);
99 lineBorder = new CompoundBorder(LineBorder.createGrayLineBorder(), new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET));
102 super.setBorder(lineBorder);
106 public Point getToolTipLocation(MouseEvent event) {
107 Point2D p2d = node.localToControl(event.getPoint());
108 Point p = new Point((int)p2d.getX(),-20+(int)p2d.getY());
114 public String toString() {
115 return super.toString() + "[editable=" + editable + ", value=" + value + "]";
118 private void markResize() {
119 //System.out.println("MonitorNode.markResize()");
125 component = new TextField(borderWidth,this);
126 component.setEditable(editable);
127 component.setEnabled(editable);
128 component.addActionListener(this);
129 component.addFocusListener(this);
130 component.addKeyListener(this);
134 @SyncField("editable")
135 public void setEditable(boolean value) {
136 this.editable = value;
138 if(component != null) {
139 component.setEditable(value);
140 component.setEnabled(value);
144 @PropertySetter("Stroke Width")
145 @SyncField("borderWidth")
146 public void setBorderWidth(Float borderWidth) {
147 this.borderWidth = borderWidth;
148 if(component != null) {
149 ((TextField)component).setBorder(borderWidth);
154 public void setText(String value) {
156 // RemoteViewer does not have component initialized
157 if (component != null) {
158 //System.out.println("MonitorNode.setText(" + value + ")");
159 component.setText(value);
165 @SyncField("tooltip")
166 public void setToolTipText(String tooltip) {
167 this.tooltip = tooltip;
168 if (component != null) {
169 component.setToolTipText(tooltip);
174 @PropertySetter("Font")
176 public void setFont(Font font) {
178 if (component != null) {
179 setComponentFont(font);
184 @PropertySetter("Color")
186 public void setColor(Color color) {
188 if (component != null) {
189 component.setForeground(color);
195 public void setHorizontalAlign(int halign) {
196 this.halign = halign;
200 public void render(Graphics2D g2d) {
202 recalculateSize(g2d);
204 if (component != null) {
205 synchronized (component) {
206 if (component.getHorizontalAlignment() != halign)
207 component.setHorizontalAlignment(halign);
213 private void recalculateSize(Graphics2D g2d) {
214 //System.out.println("MonitorNode.recalculateSize(" + value + ")");
215 if (component == null || value == null)
218 Font font = getComponentFont();
220 String measuredValue = value;
221 //String measuredValue = value + "x";
222 // If bounds are NOT set, calculate size..
223 if (bounds == null) {
224 FontMetrics metrics = component.getFontMetrics(font);
225 Rectangle2D size = metrics.getStringBounds(measuredValue, g2d);
228 // setSize(xPadding + (int) Math.ceil(size.getWidth()), yPadding + (int) Math.ceil(size.getHeight()));
229 setBounds(new Rectangle2D.Double(0, 0, xPadding + (int) Math.ceil(size.getWidth()), yPadding + (int) Math.ceil(size.getHeight())));
230 //component.setScrollOffset(0);
232 // ... If bounds are set, change font size to get the text fit
233 // into the bounds. Find the best fit through bisection.
235 // min is assumed to fit within the bounds. It will not be searched
238 // First find an upper bound that no longer fits within the bounds.
239 // First guess is 62.
241 final float upperLimit = 200;
242 while (max < upperLimit) {
243 font = font.deriveFont(max);
244 FontMetrics metrics = component.getFontMetrics(font);
245 Rectangle2D fbounds = metrics.getStringBounds(measuredValue, g2d);
246 if (!fits(bounds, fbounds))
250 if (max < upperLimit) {
251 // Bisect the largest font size in [min,max] that fits the bounds.
253 float half = (max + min) / 2;
254 float interval = max - min;
255 font = font.deriveFont(half);
256 FontMetrics metrics = component.getFontMetrics(font);
257 Rectangle2D fbounds = metrics.getStringBounds(measuredValue, g2d);
258 if (fits(bounds, fbounds)) {
259 // Fits within bounds, bisect [half, max]
265 // Does not fit within bounds, bisect [min, half]
267 font = font.deriveFont(min);
275 setComponentFont(font);
276 // setSize((int)bounds.getWidth(), (int)bounds.getHeight());
281 private boolean fits(Rectangle2D parent, Rectangle2D child) {
282 return parent.getWidth() >= child.getWidth() && parent.getHeight() >= child.getHeight();
285 public String getText() {
289 public Font getFont() {
294 public void propertyChange(PropertyChangeEvent evt) {
295 if("value".equals(evt.getPropertyName()) && component != null) {
296 synchronized(component) {
297 component.setText((String)evt.getNewValue());
301 } else if("editable".equals(evt.getPropertyName()) && component != null) {
302 synchronized(component) {
303 component.setEditable((Boolean)evt.getNewValue());
304 component.setEnabled((Boolean)evt.getNewValue());
310 public void setActionListener(ActionListener actionListener) {
311 this.actionListener = actionListener;
315 public void actionPerformed(ActionEvent e) {
321 if (component != null)
322 if (component.isFocusOwner())
323 if (container.getParent() != null)
324 container.getParent().requestFocusInWindow(); // Lose focus
328 public void focusGained(FocusEvent arg0) {
329 if (component != null) {
330 component.selectAll();
335 public void focusLost(FocusEvent arg0) {
336 if (component != null) {
337 ActionEvent e = new ActionEvent(component, ActionEvent.ACTION_PERFORMED, component.getText());
343 * Wrapper method to send event to serverside
348 public void performAction(ActionEvent e) {
349 if (actionListener != null) {
350 //System.out.println("MonitorNode.performAction(" + e + ")");
351 actionListener.actionPerformed(e);
356 public void keyPressed(KeyEvent e) {
360 public void keyTyped(KeyEvent e) {
364 public void keyReleased(KeyEvent e) {
365 if (e.getModifiers() == 0 && e.getKeyCode() == KeyEvent.VK_ESCAPE) {
366 // ESC without modifiers == CANCEL edit
367 // TODO: signal about cancellation
372 public String widgetGet(String name) {
373 if("value".equals(name)) {
379 public void widgetSet(String name, String value) {
380 if("value".equals(name)) {
381 ActionEvent e = new ActionEvent(new DummyComponent(), ActionEvent.ACTION_PERFORMED, value);