blob: ff8bfb60fa973b2912ea63a4d7e5402cee1026e2 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.awt.im;
27
28import java.awt.AWTEvent;
29import java.awt.Color;
30import java.awt.Dimension;
31import java.awt.FontMetrics;
32import java.awt.Graphics;
33import java.awt.Graphics2D;
34import java.awt.Point;
35import java.awt.Rectangle;
36import java.awt.Toolkit;
37import java.awt.event.InputMethodEvent;
38import java.awt.event.InputMethodListener;
39import java.awt.event.WindowEvent;
40import java.awt.event.WindowAdapter;
41import java.awt.font.FontRenderContext;
42import java.awt.font.TextHitInfo;
43import java.awt.font.TextLayout;
44import java.awt.geom.Rectangle2D;
45import java.awt.im.InputMethodRequests;
46import java.text.AttributedCharacterIterator;
47import javax.swing.JFrame;
48import javax.swing.JPanel;
49import 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
59public class CompositionArea extends JPanel implements InputMethodListener {
60
61 private CompositionAreaHandler handler;
62
63 private TextLayout composedTextLayout;
64 private TextHitInfo caret = null;
65 private JFrame compositionWindow;
66 private final static int TEXT_ORIGIN_X = 5;
67 private final static int TEXT_ORIGIN_Y = 15;
68 private final static int PASSIVE_WIDTH = 480;
69 private final static int WIDTH_MARGIN=10;
70 private final static int HEIGHT_MARGIN=3;
71
72 CompositionArea() {
73 // create composition window with localized title
74 String windowTitle = Toolkit.getProperty("AWT.CompositionWindowTitle", "Input Window");
75 compositionWindow =
76 (JFrame)InputMethodContext.createInputMethodWindow(windowTitle, null, true);
77
78 setOpaque(true);
79 setBorder(LineBorder.createGrayLineBorder());
80 setForeground(Color.black);
81 setBackground(Color.white);
82
83 // if we get the focus, we still want to let the client's
84 // input context handle the event
85 enableInputMethods(true);
86 enableEvents(AWTEvent.KEY_EVENT_MASK);
87
88 compositionWindow.getContentPane().add(this);
89 compositionWindow.addWindowListener(new FrameWindowAdapter());
90 addInputMethodListener(this);
91 compositionWindow.enableInputMethods(false);
92 compositionWindow.pack();
93 Dimension windowSize = compositionWindow.getSize();
94 Dimension screenSize = (getToolkit()).getScreenSize();
95 compositionWindow.setLocation(screenSize.width - windowSize.width-20,
96 screenSize.height - windowSize.height-100);
97 compositionWindow.setVisible(false);
98 }
99
100 /**
101 * Sets the composition area handler that currently owns this
102 * composition area, and its input context.
103 */
104 synchronized void setHandlerInfo(CompositionAreaHandler handler, InputContext inputContext) {
105 this.handler = handler;
106 ((InputMethodWindow) compositionWindow).setInputContext(inputContext);
107 }
108
109 /**
110 * @see java.awt.Component#getInputMethodRequests
111 */
112 public InputMethodRequests getInputMethodRequests() {
113 return handler;
114 }
115
116 // returns a 0-width rectangle
117 private Rectangle getCaretRectangle(TextHitInfo caret) {
118 int caretLocation = 0;
119 TextLayout layout = composedTextLayout;
120 if (layout != null) {
121 caretLocation = Math.round(layout.getCaretInfo(caret)[0]);
122 }
123 Graphics g = getGraphics();
124 FontMetrics metrics = null;
125 try {
126 metrics = g.getFontMetrics();
127 } finally {
128 g.dispose();
129 }
130 return new Rectangle(TEXT_ORIGIN_X + caretLocation,
131 TEXT_ORIGIN_Y - metrics.getAscent(),
132 0, metrics.getAscent() + metrics.getDescent());
133 }
134
135 public void paint(Graphics g) {
136 super.paint(g);
137 g.setColor(getForeground());
138 TextLayout layout = composedTextLayout;
139 if (layout != null) {
140 layout.draw((Graphics2D) g, TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
141 }
142 if (caret != null) {
143 Rectangle rectangle = getCaretRectangle(caret);
144 g.setXORMode(getBackground());
145 g.fillRect(rectangle.x, rectangle.y, 1, rectangle.height);
146 g.setPaintMode();
147 }
148 }
149
150 // shows/hides the composition window
151 void setCompositionAreaVisible(boolean visible) {
152 compositionWindow.setVisible(visible);
153 }
154
155 // returns true if composition area is visible
156 boolean isCompositionAreaVisible() {
157 return compositionWindow.isVisible();
158 }
159
160 // workaround for the Solaris focus lost problem
161 class FrameWindowAdapter extends WindowAdapter {
162 public void windowActivated(WindowEvent e) {
163 requestFocus();
164 }
165 }
166
167 // InputMethodListener methods - just forward to the current handler
168 public void inputMethodTextChanged(InputMethodEvent event) {
169 handler.inputMethodTextChanged(event);
170 }
171
172 public void caretPositionChanged(InputMethodEvent event) {
173 handler.caretPositionChanged(event);
174 }
175
176 /**
177 * Sets the text and caret to be displayed in this composition area.
178 * Shows the window if it contains text, hides it if not.
179 */
180 void setText(AttributedCharacterIterator composedText, TextHitInfo caret) {
181 composedTextLayout = null;
182 if (composedText == null) {
183 // there's no composed text to display, so hide the window
184 compositionWindow.setVisible(false);
185 this.caret = null;
186 } else {
187 /* since we have composed text, make sure the window is shown.
188 This is necessary to get a valid graphics object. See 6181385.
189 */
190 if (!compositionWindow.isVisible()) {
191 compositionWindow.setVisible(true);
192 }
193
194 Graphics g = getGraphics();
195
196 if (g == null) {
197 return;
198 }
199
200 try {
201 updateWindowLocation();
202
203 FontRenderContext context = ((Graphics2D)g).getFontRenderContext();
204 composedTextLayout = new TextLayout(composedText, context);
205 Rectangle2D bounds = composedTextLayout.getBounds();
206
207 this.caret = caret;
208
209 // Resize the composition area to just fit the text.
210 FontMetrics metrics = g.getFontMetrics();
211 Rectangle2D maxCharBoundsRec = metrics.getMaxCharBounds(g);
212 int newHeight = (int)maxCharBoundsRec.getHeight() + HEIGHT_MARGIN;
213 int newFrameHeight = newHeight +compositionWindow.getInsets().top
214 +compositionWindow.getInsets().bottom;
215 // If it's a passive client, set the width always to PASSIVE_WIDTH (480px)
216 InputMethodRequests req = handler.getClientInputMethodRequests();
217 int newWidth = (req==null) ? PASSIVE_WIDTH : (int)bounds.getWidth() + WIDTH_MARGIN;
218 int newFrameWidth = newWidth + compositionWindow.getInsets().left
219 + compositionWindow.getInsets().right;
220 setPreferredSize(new Dimension(newWidth, newHeight));
221 compositionWindow.setSize(new Dimension(newFrameWidth, newFrameHeight));
222
223 // show the composed text
224 paint(g);
225 }
226 finally {
227 g.dispose();
228 }
229 }
230 }
231
232 /**
233 * Sets the caret to be displayed in this composition area.
234 * The text is not changed.
235 */
236 void setCaret(TextHitInfo caret) {
237 this.caret = caret;
238 if (compositionWindow.isVisible()) {
239 Graphics g = getGraphics();
240 try {
241 paint(g);
242 } finally {
243 g.dispose();
244 }
245 }
246 }
247
248 /**
249 * Positions the composition window near (usually below) the
250 * insertion point in the client component if the client
251 * component is an active client (below-the-spot input).
252 */
253 void updateWindowLocation() {
254 InputMethodRequests req = handler.getClientInputMethodRequests();
255 if (req == null) {
256 // not an active client
257 return;
258 }
259
260 Point windowLocation = new Point();
261
262 Rectangle caretRect = req.getTextLocation(null);
263 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
264 Dimension windowSize = compositionWindow.getSize();
265 final int SPACING = 2;
266
267 if (caretRect.x + windowSize.width > screenSize.width) {
268 windowLocation.x = screenSize.width - windowSize.width;
269 } else {
270 windowLocation.x = caretRect.x;
271 }
272
273 if (caretRect.y + caretRect.height + SPACING + windowSize.height > screenSize.height) {
274 windowLocation.y = caretRect.y - SPACING - windowSize.height;
275 } else {
276 windowLocation.y = caretRect.y + caretRect.height + SPACING;
277 }
278
279 compositionWindow.setLocation(windowLocation);
280 }
281
282 // support for InputMethodRequests methods
283 Rectangle getTextLocation(TextHitInfo offset) {
284 Rectangle rectangle = getCaretRectangle(offset);
285 Point location = getLocationOnScreen();
286 rectangle.translate(location.x, location.y);
287 return rectangle;
288 }
289
290 TextHitInfo getLocationOffset(int x, int y) {
291 TextLayout layout = composedTextLayout;
292 if (layout == null) {
293 return null;
294 } else {
295 Point location = getLocationOnScreen();
296 x -= location.x + TEXT_ORIGIN_X;
297 y -= location.y + TEXT_ORIGIN_Y;
298 if (layout.getBounds().contains(x, y)) {
299 return layout.hitTestChar(x, y);
300 } else {
301 return null;
302 }
303 }
304 }
305
306 // Disables or enables decorations of the composition window
307 void setCompositionAreaUndecorated(boolean setUndecorated){
308 if (compositionWindow.isDisplayable()){
309 compositionWindow.removeNotify();
310 }
311 compositionWindow.setUndecorated(setUndecorated);
312 compositionWindow.pack();
313 }
314
315}