]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/swing/MonitorNode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph.swing / src / org / simantics / scenegraph / swing / MonitorNode.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.scenegraph.swing;\r
13 \r
14 import java.awt.Color;\r
15 import java.awt.Font;\r
16 import java.awt.FontMetrics;\r
17 import java.awt.Graphics2D;\r
18 import java.awt.Point;\r
19 import java.awt.event.ActionEvent;\r
20 import java.awt.event.ActionListener;\r
21 import java.awt.event.FocusEvent;\r
22 import java.awt.event.FocusListener;\r
23 import java.awt.event.KeyEvent;\r
24 import java.awt.event.KeyListener;\r
25 import java.awt.event.MouseEvent;\r
26 import java.awt.geom.Point2D;\r
27 import java.awt.geom.Rectangle2D;\r
28 import java.beans.PropertyChangeEvent;\r
29 import java.beans.PropertyChangeListener;\r
30 \r
31 import javax.swing.JTextField;\r
32 import javax.swing.border.Border;\r
33 import javax.swing.border.CompoundBorder;\r
34 import javax.swing.border.EmptyBorder;\r
35 import javax.swing.border.LineBorder;\r
36 \r
37 import org.simantics.scenegraph.ExportableWidget.InputWidget;\r
38 import org.simantics.scenegraph.ExportableWidget.OutputWidget;\r
39 import org.simantics.scenegraph.utils.DummyComponent;\r
40 \r
41 @OutputWidget("value")\r
42 @InputWidget("value")\r
43 public class MonitorNode extends ComponentNode<JTextField> implements ActionListener, FocusListener, PropertyChangeListener, KeyListener {\r
44     /**\r
45      * \r
46      */\r
47     private static final long serialVersionUID = 7073028693751719102L;\r
48 \r
49     protected boolean editable = true;\r
50     protected String value = "";\r
51     protected String tooltip = "";\r
52     protected double borderWidth = 0;\r
53 \r
54     protected transient ActionListener actionListener = null;\r
55 \r
56     private boolean doResize = false;\r
57 \r
58     protected Font font = null;\r
59     protected Color color = null;\r
60     protected int halign = JTextField.LEFT;  // See JTextField for value options\r
61 \r
62     static class TextField extends JTextField {\r
63         private static final int X_INSET = 5;\r
64         private static final int Y_INSET = -1;\r
65         private static final long serialVersionUID = -668522226693100386L;\r
66         private Border lineBorder;\r
67         private final MonitorNode node;\r
68 \r
69         public TextField(double borderWidth, MonitorNode node) {\r
70             if(borderWidth < 0.1) {\r
71                 lineBorder = new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET);\r
72             } else {\r
73                 lineBorder = new CompoundBorder(LineBorder.createGrayLineBorder(), new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET));\r
74             }\r
75             this.node = node;\r
76             setHorizontalAlignment(JTextField.CENTER);\r
77             setAlignmentY(JTextField.CENTER_ALIGNMENT);\r
78         }\r
79 \r
80         // workaround for 4530952\r
81         @Override\r
82         public void setText(String s) {\r
83             if (getText().equals(s)) {\r
84                 return;\r
85             }\r
86             super.setText(s);\r
87 \r
88         }\r
89 \r
90         @Override\r
91         public void setBorder(Border border) {\r
92             super.setBorder(lineBorder);\r
93         }\r
94 \r
95         public void setBorder(double borderWidth) {\r
96             if(borderWidth < 0.1) {\r
97                 lineBorder = new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET);\r
98             } else {\r
99                 lineBorder = new CompoundBorder(LineBorder.createGrayLineBorder(), new EmptyBorder(Y_INSET, X_INSET, Y_INSET, X_INSET));\r
100             }\r
101 \r
102             super.setBorder(lineBorder);\r
103         }\r
104 \r
105         @Override\r
106         public Point getToolTipLocation(MouseEvent event) {\r
107             Point2D p2d = node.localToControl(event.getPoint());\r
108             Point p = new Point((int)p2d.getX(),-20+(int)p2d.getY());\r
109             return p;\r
110         }\r
111     }\r
112 \r
113     @Override\r
114     public String toString() {\r
115         return super.toString() + "[editable=" + editable + ", value=" + value + "]";\r
116     }\r
117 \r
118     private void markResize() {\r
119         //System.out.println("MonitorNode.markResize()");\r
120         doResize = true;\r
121     }\r
122 \r
123     @Override\r
124     public void init() {\r
125         component = new TextField(borderWidth,this);\r
126         component.setEditable(editable);\r
127         component.setEnabled(editable);\r
128         component.addActionListener(this);\r
129         component.addFocusListener(this);\r
130         component.addKeyListener(this);\r
131         super.init();\r
132     }\r
133 \r
134     @SyncField("editable")\r
135     public void setEditable(boolean value) {\r
136         this.editable = value;\r
137 \r
138         if(component != null) {\r
139             component.setEditable(value);\r
140             component.setEnabled(value);\r
141         }\r
142     }\r
143 \r
144     @PropertySetter("Stroke Width")\r
145     @SyncField("borderWidth")\r
146     public void setBorderWidth(Float borderWidth) {\r
147         this.borderWidth = borderWidth;\r
148         if(component != null) {\r
149             ((TextField)component).setBorder(borderWidth);\r
150         }\r
151     }\r
152 \r
153     @SyncField("value")\r
154     public void setText(String value) {\r
155         this.value = value;\r
156         // RemoteViewer does not have component initialized\r
157         if (component != null) {\r
158             //System.out.println("MonitorNode.setText(" + value + ")");\r
159             component.setText(value);\r
160             markResize();\r
161             component.repaint();\r
162         }\r
163     }\r
164 \r
165     @SyncField("tooltip")\r
166     public void setToolTipText(String tooltip) {\r
167         this.tooltip = tooltip;\r
168         if (component != null) {\r
169             component.setToolTipText(tooltip);\r
170         }\r
171     }\r
172 \r
173 \r
174     @PropertySetter("Font")\r
175     @SyncField("font")\r
176     public void setFont(Font font) {\r
177         this.font = font;\r
178         if (component != null) {\r
179             setComponentFont(font);\r
180             markResize();\r
181         }\r
182     }\r
183 \r
184     @PropertySetter("Color")\r
185     @SyncField("color")\r
186     public void setColor(Color color) {\r
187         this.color = color;\r
188         if (component != null) {\r
189             component.setForeground(color);\r
190             markResize();\r
191         }\r
192     }\r
193 \r
194     @SyncField("halign")\r
195     public void setHorizontalAlign(int halign) {\r
196         this.halign = halign;\r
197     }\r
198 \r
199     @Override\r
200     public void render(Graphics2D g2d) {\r
201         if (doResize)\r
202             recalculateSize(g2d);\r
203         doResize = false;\r
204                 if (component != null) {\r
205                         synchronized (component) {\r
206                                 if (component.getHorizontalAlignment() != halign)\r
207                                         component.setHorizontalAlignment(halign);\r
208                                 super.render(g2d);\r
209                         }\r
210                 }\r
211     }\r
212 \r
213     private void recalculateSize(Graphics2D g2d) {\r
214         //System.out.println("MonitorNode.recalculateSize(" + value + ")");\r
215         if (component == null || value == null)\r
216             return;\r
217 \r
218         Font font = getComponentFont();\r
219         if (font != null) {\r
220             String measuredValue = value;\r
221             //String measuredValue = value + "x";\r
222             // If bounds are NOT set, calculate size..\r
223             if (bounds == null) {\r
224                 FontMetrics metrics = component.getFontMetrics(font);\r
225                 Rectangle2D size = metrics.getStringBounds(measuredValue, g2d);\r
226                 int xPadding = 15;\r
227                 int yPadding = 2;\r
228 //                setSize(xPadding + (int) Math.ceil(size.getWidth()), yPadding + (int) Math.ceil(size.getHeight()));\r
229                 setBounds(new Rectangle2D.Double(0, 0, xPadding + (int) Math.ceil(size.getWidth()), yPadding + (int) Math.ceil(size.getHeight())));\r
230                 //component.setScrollOffset(0);\r
231             } else {\r
232                 // ... If bounds are set, change font size to get the text fit\r
233                 // into the bounds. Find the best fit through bisection.\r
234 \r
235                 // min is assumed to fit within the bounds. It will not be searched\r
236                 float min = 6;\r
237 \r
238                 // First find an upper bound that no longer fits within the bounds.\r
239                 // First guess is 62.\r
240                 float max = 62;\r
241                 final float upperLimit = 200;\r
242                 while (max < upperLimit) {\r
243                     font = font.deriveFont(max);\r
244                     FontMetrics metrics = component.getFontMetrics(font);\r
245                     Rectangle2D fbounds = metrics.getStringBounds(measuredValue, g2d);\r
246                     if (!fits(bounds, fbounds))\r
247                         break;\r
248                     max += 20;\r
249                 }\r
250                 if (max < upperLimit) {\r
251                     // Bisect the largest font size in [min,max] that fits the bounds.\r
252                     while (true) {\r
253                         float half = (max + min) / 2;\r
254                         float interval = max - min;\r
255                         font = font.deriveFont(half);\r
256                         FontMetrics metrics = component.getFontMetrics(font);\r
257                         Rectangle2D fbounds = metrics.getStringBounds(measuredValue, g2d);\r
258                         if (fits(bounds, fbounds)) {\r
259                             // Fits within bounds, bisect [half, max]\r
260                             if (interval <= 1) {\r
261                                 break;\r
262                             }\r
263                             min = half;\r
264                         } else {\r
265                             // Does not fit within bounds, bisect [min, half]\r
266                             if (interval <= 1) {\r
267                                 font = font.deriveFont(min);\r
268                                 break;\r
269                             }\r
270                             max = half;\r
271                         }\r
272                     }\r
273                 }\r
274 \r
275                 setComponentFont(font);\r
276 //                setSize((int)bounds.getWidth(), (int)bounds.getHeight());\r
277             }\r
278         }\r
279     }\r
280 \r
281     private boolean fits(Rectangle2D parent, Rectangle2D child) {\r
282         return parent.getWidth() >= child.getWidth() && parent.getHeight() >= child.getHeight();\r
283     }\r
284 \r
285     public String getText() {\r
286         return value;\r
287     }\r
288 \r
289     public Font getFont() {\r
290         return font;\r
291     }\r
292 \r
293     @Override\r
294     public void propertyChange(PropertyChangeEvent evt) {\r
295         if("value".equals(evt.getPropertyName()) && component != null) {\r
296             synchronized(component) {\r
297                     component.setText((String)evt.getNewValue());\r
298                     markResize();\r
299                     component.repaint();\r
300             }\r
301         } else if("editable".equals(evt.getPropertyName()) && component != null) {\r
302             synchronized(component) {\r
303                 component.setEditable((Boolean)evt.getNewValue());\r
304                 component.setEnabled((Boolean)evt.getNewValue());\r
305             }\r
306         }\r
307     }\r
308 \r
309 \r
310     public void setActionListener(ActionListener actionListener) {\r
311         this.actionListener = actionListener;\r
312     }\r
313 \r
314     @Override\r
315     public void actionPerformed(ActionEvent e) {\r
316 //        performAction(e);\r
317         loseFocus();\r
318     }\r
319 \r
320     void loseFocus() {\r
321         if (component != null)\r
322             if (component.isFocusOwner())\r
323                 if (container.getParent() != null)\r
324                     container.getParent().requestFocusInWindow(); // Lose focus\r
325     }\r
326 \r
327     @Override\r
328     public void focusGained(FocusEvent arg0) {\r
329         if (component != null) {\r
330                 component.selectAll();\r
331         }\r
332     }\r
333 \r
334     @Override\r
335     public void focusLost(FocusEvent arg0) {\r
336         if (component != null) {\r
337             ActionEvent e = new ActionEvent(component, ActionEvent.ACTION_PERFORMED, component.getText());\r
338             performAction(e);\r
339         }\r
340     }\r
341 \r
342     /**\r
343      * Wrapper method to send event to serverside\r
344      * \r
345      * @param e\r
346      */\r
347     @ServerSide\r
348     public void performAction(ActionEvent e) {\r
349         if (actionListener != null) {\r
350             //System.out.println("MonitorNode.performAction(" + e + ")");\r
351             actionListener.actionPerformed(e);\r
352         }\r
353     }\r
354 \r
355     @Override\r
356     public void keyPressed(KeyEvent e) {\r
357     }\r
358 \r
359     @Override\r
360     public void keyTyped(KeyEvent e) {\r
361     }\r
362 \r
363     @Override\r
364     public void keyReleased(KeyEvent e) {\r
365         if (e.getModifiers() == 0 && e.getKeyCode() == KeyEvent.VK_ESCAPE) {\r
366             // ESC without modifiers == CANCEL edit\r
367             // TODO: signal about cancellation\r
368             loseFocus();\r
369         }\r
370     }\r
371     \r
372     public String widgetGet(String name) {\r
373         if("value".equals(name)) {\r
374             return ""+value;\r
375         }\r
376         return null;\r
377     }\r
378     \r
379     public void widgetSet(String name, String value) {\r
380         if("value".equals(name)) {\r
381             ActionEvent e = new ActionEvent(new DummyComponent(), ActionEvent.ACTION_PERFORMED, value);\r
382             performAction(e);\r
383         }\r
384     }\r
385 }\r