1 /*
2  * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.awt.im;
27 
28 import java.awt.AWTEvent;
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33 import java.awt.Graphics2D;
34 import java.awt.Point;
35 import java.awt.Rectangle;
36 import java.awt.Toolkit;
37 import java.awt.event.InputMethodEvent;
38 import java.awt.event.InputMethodListener;
39 import java.awt.event.WindowEvent;
40 import java.awt.event.WindowAdapter;
41 import java.awt.font.FontRenderContext;
42 import java.awt.font.TextHitInfo;
43 import java.awt.font.TextLayout;
44 import java.awt.geom.Rectangle2D;
45 import java.awt.im.InputMethodRequests;
46 import java.text.AttributedCharacterIterator;
47 import javax.swing.JFrame;
48 import javax.swing.JPanel;
49 import javax.swing.border.LineBorder;
50 
51 /**
52  * A composition area is used to display text that's being composed
53  * using an input method in its own user interface environment,
54  * typically in a root window.
55  *
56  * @author JavaSoft International
57  */
58 
59 // This class is final due to the 6607310 fix. Refer to the CR for details.
60 public final class CompositionArea extends JPanel implements InputMethodListener {
61 
62     private CompositionAreaHandler handler;
63 
64     private TextLayout composedTextLayout;
65     private TextHitInfo caret = null;
66     private JFrame compositionWindow;
67     private static final int TEXT_ORIGIN_X = 5;
68     private static final int TEXT_ORIGIN_Y = 15;
69     private static final int PASSIVE_WIDTH = 480;
70     private static final int WIDTH_MARGIN=10;
71     private static final int HEIGHT_MARGIN=3;
72 
CompositionArea()73     CompositionArea() {
74         // create composition window with localized title
75         String windowTitle = Toolkit.getProperty("AWT.CompositionWindowTitle", "Input Window");
76         compositionWindow =
77             (JFrame)InputMethodContext.createInputMethodWindow(windowTitle, null, true);
78 
79         setOpaque(true);
80         setBorder(LineBorder.createGrayLineBorder());
81         setForeground(Color.black);
82         setBackground(Color.white);
83 
84         // if we get the focus, we still want to let the client's
85         // input context handle the event
86         enableInputMethods(true);
87         enableEvents(AWTEvent.KEY_EVENT_MASK);
88 
89         compositionWindow.getContentPane().add(this);
90         compositionWindow.addWindowListener(new FrameWindowAdapter());
91         addInputMethodListener(this);
92         compositionWindow.enableInputMethods(false);
93         compositionWindow.pack();
94         Dimension windowSize = compositionWindow.getSize();
95         Dimension screenSize = (getToolkit()).getScreenSize();
96         compositionWindow.setLocation(screenSize.width - windowSize.width-20,
97                                     screenSize.height - windowSize.height-100);
98         compositionWindow.setVisible(false);
99     }
100 
101     /**
102      * Sets the composition area handler that currently owns this
103      * composition area, and its input context.
104      */
setHandlerInfo(CompositionAreaHandler handler, InputContext inputContext)105     synchronized void setHandlerInfo(CompositionAreaHandler handler, InputContext inputContext) {
106         this.handler = handler;
107         ((InputMethodWindow) compositionWindow).setInputContext(inputContext);
108     }
109 
110     /**
111      * @see java.awt.Component#getInputMethodRequests
112      */
getInputMethodRequests()113     public InputMethodRequests getInputMethodRequests() {
114         return handler;
115     }
116 
117     // returns a 0-width rectangle
getCaretRectangle(TextHitInfo caret)118     private Rectangle getCaretRectangle(TextHitInfo caret) {
119         int caretLocation = 0;
120         TextLayout layout = composedTextLayout;
121         if (layout != null) {
122             caretLocation = Math.round(layout.getCaretInfo(caret)[0]);
123         }
124         Graphics g = getGraphics();
125         FontMetrics metrics = null;
126         try {
127             metrics = g.getFontMetrics();
128         } finally {
129             g.dispose();
130         }
131         return new Rectangle(TEXT_ORIGIN_X + caretLocation,
132                              TEXT_ORIGIN_Y - metrics.getAscent(),
133                              0, metrics.getAscent() + metrics.getDescent());
134     }
135 
paint(Graphics g)136     public void paint(Graphics g) {
137         super.paint(g);
138         g.setColor(getForeground());
139         TextLayout layout = composedTextLayout;
140         if (layout != null) {
141             layout.draw((Graphics2D) g, TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
142         }
143         if (caret != null) {
144             Rectangle rectangle = getCaretRectangle(caret);
145             g.setXORMode(getBackground());
146             g.fillRect(rectangle.x, rectangle.y, 1, rectangle.height);
147             g.setPaintMode();
148         }
149     }
150 
151     // shows/hides the composition window
setCompositionAreaVisible(boolean visible)152     void setCompositionAreaVisible(boolean visible) {
153         compositionWindow.setVisible(visible);
154     }
155 
156     // returns true if composition area is visible
isCompositionAreaVisible()157     boolean isCompositionAreaVisible() {
158         return compositionWindow.isVisible();
159     }
160 
161     // workaround for the Solaris focus lost problem
162     class FrameWindowAdapter extends WindowAdapter {
windowActivated(WindowEvent e)163         public void windowActivated(WindowEvent e) {
164             requestFocus();
165         }
166     }
167 
168     // InputMethodListener methods - just forward to the current handler
inputMethodTextChanged(InputMethodEvent event)169     public void inputMethodTextChanged(InputMethodEvent event) {
170         handler.inputMethodTextChanged(event);
171     }
172 
caretPositionChanged(InputMethodEvent event)173     public void caretPositionChanged(InputMethodEvent event) {
174         handler.caretPositionChanged(event);
175     }
176 
177     /**
178      * Sets the text and caret to be displayed in this composition area.
179      * Shows the window if it contains text, hides it if not.
180      */
setText(AttributedCharacterIterator composedText, TextHitInfo caret)181     void setText(AttributedCharacterIterator composedText, TextHitInfo caret) {
182         composedTextLayout = null;
183         if (composedText == null) {
184             // there's no composed text to display, so hide the window
185             compositionWindow.setVisible(false);
186             this.caret = null;
187         } else {
188             /* since we have composed text, make sure the window is shown.
189                This is necessary to get a valid graphics object. See 6181385.
190             */
191             if (!compositionWindow.isVisible()) {
192                 compositionWindow.setVisible(true);
193             }
194 
195             Graphics g = getGraphics();
196 
197             if (g == null) {
198                 return;
199             }
200 
201             try {
202                 updateWindowLocation();
203 
204                 FontRenderContext context = ((Graphics2D)g).getFontRenderContext();
205                 composedTextLayout = new TextLayout(composedText, context);
206                 Rectangle2D bounds = composedTextLayout.getBounds();
207 
208                 this.caret = caret;
209 
210                 // Resize the composition area to just fit the text.
211                 FontMetrics metrics = g.getFontMetrics();
212                 Rectangle2D maxCharBoundsRec = metrics.getMaxCharBounds(g);
213                 int newHeight = (int)maxCharBoundsRec.getHeight() + HEIGHT_MARGIN;
214                 int newFrameHeight = newHeight +compositionWindow.getInsets().top
215                                                +compositionWindow.getInsets().bottom;
216                 // If it's a passive client, set the width always to PASSIVE_WIDTH (480px)
217                 InputMethodRequests req = handler.getClientInputMethodRequests();
218                 int newWidth = (req==null) ? PASSIVE_WIDTH : (int)bounds.getWidth() + WIDTH_MARGIN;
219                 int newFrameWidth = newWidth + compositionWindow.getInsets().left
220                                              + compositionWindow.getInsets().right;
221                 setPreferredSize(new Dimension(newWidth, newHeight));
222                 compositionWindow.setSize(new Dimension(newFrameWidth, newFrameHeight));
223 
224                 // show the composed text
225                 paint(g);
226             }
227             finally {
228                 g.dispose();
229             }
230         }
231     }
232 
233     /**
234      * Sets the caret to be displayed in this composition area.
235      * The text is not changed.
236      */
setCaret(TextHitInfo caret)237     void setCaret(TextHitInfo caret) {
238         this.caret = caret;
239         if (compositionWindow.isVisible()) {
240             Graphics g = getGraphics();
241             try {
242                 paint(g);
243             } finally {
244                 g.dispose();
245             }
246         }
247     }
248 
249     /**
250      * Positions the composition window near (usually below) the
251      * insertion point in the client component if the client
252      * component is an active client (below-the-spot input).
253      */
updateWindowLocation()254     void updateWindowLocation() {
255         InputMethodRequests req = handler.getClientInputMethodRequests();
256         if (req == null) {
257             // not an active client
258             return;
259         }
260 
261         Point windowLocation = new Point();
262 
263         Rectangle caretRect = req.getTextLocation(null);
264         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
265         Dimension windowSize = compositionWindow.getSize();
266         final int SPACING = 2;
267 
268         if (caretRect.x + windowSize.width > screenSize.width) {
269             windowLocation.x = screenSize.width - windowSize.width;
270         } else {
271             windowLocation.x = caretRect.x;
272         }
273 
274         if (caretRect.y + caretRect.height + SPACING + windowSize.height > screenSize.height) {
275             windowLocation.y = caretRect.y - SPACING - windowSize.height;
276         } else {
277             windowLocation.y = caretRect.y + caretRect.height + SPACING;
278         }
279 
280         compositionWindow.setLocation(windowLocation);
281     }
282 
283     // support for InputMethodRequests methods
getTextLocation(TextHitInfo offset)284     Rectangle getTextLocation(TextHitInfo offset) {
285         Rectangle rectangle = getCaretRectangle(offset);
286         Point location = getLocationOnScreen();
287         rectangle.translate(location.x, location.y);
288         return rectangle;
289     }
290 
getLocationOffset(int x, int y)291    TextHitInfo getLocationOffset(int x, int y) {
292         TextLayout layout = composedTextLayout;
293         if (layout == null) {
294             return null;
295         } else {
296             Point location = getLocationOnScreen();
297             x -= location.x + TEXT_ORIGIN_X;
298             y -= location.y + TEXT_ORIGIN_Y;
299             if (layout.getBounds().contains(x, y)) {
300                 return layout.hitTestChar(x, y);
301             } else {
302                 return null;
303             }
304         }
305     }
306 
307     // Disables or enables decorations of the composition window
setCompositionAreaUndecorated(boolean setUndecorated)308     void setCompositionAreaUndecorated(boolean setUndecorated){
309           if (compositionWindow.isDisplayable()){
310               compositionWindow.removeNotify();
311           }
312           compositionWindow.setUndecorated(setUndecorated);
313           compositionWindow.pack();
314     }
315 
316     // Proclaim serial compatibility with 1.7.0
317     private static final long serialVersionUID = -1057247068746557444L;
318 
319 }
320