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 final static int TEXT_ORIGIN_X = 5; 68 private final static int TEXT_ORIGIN_Y = 15; 69 private final static int PASSIVE_WIDTH = 480; 70 private final static int WIDTH_MARGIN=10; 71 private final static int HEIGHT_MARGIN=3; 72 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 */ 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 */ 113 public InputMethodRequests getInputMethodRequests() { 114 return handler; 115 } 116 117 // returns a 0-width rectangle 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 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 152 void setCompositionAreaVisible(boolean visible) { 153 compositionWindow.setVisible(visible); 154 } 155 156 // returns true if composition area is visible 157 boolean isCompositionAreaVisible() { 158 return compositionWindow.isVisible(); 159 } 160 161 // workaround for the Solaris focus lost problem 162 class FrameWindowAdapter extends WindowAdapter { 163 public void windowActivated(WindowEvent e) { 164 requestFocus(); 165 } 166 } 167 168 // InputMethodListener methods - just forward to the current handler 169 public void inputMethodTextChanged(InputMethodEvent event) { 170 handler.inputMethodTextChanged(event); 171 } 172 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 */ 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 */ 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 */ 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 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 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 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 }