1 /*
   2  * Copyright (c) 1999, 2006, 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.Component;
  29 import java.awt.Container;
  30 import java.awt.Rectangle;
  31 import java.awt.event.InputMethodEvent;
  32 import java.awt.event.InputMethodListener;
  33 import java.awt.font.TextAttribute;
  34 import java.awt.font.TextHitInfo;
  35 import java.awt.im.InputMethodRequests;
  36 import java.text.AttributedCharacterIterator;
  37 import java.text.AttributedCharacterIterator.Attribute;
  38 import java.text.AttributedString;
  39 
  40 /**
  41  * A composition area handler handles events and input method requests for
  42  * the composition area. Typically each input method context has its own
  43  * composition area handler if it supports passive clients or below-the-spot
  44  * input, but all handlers share a single composition area.
  45  *
  46  * @author JavaSoft International
  47  */
  48 
  49 class CompositionAreaHandler implements InputMethodListener,
  50                                                  InputMethodRequests {
  51 
  52     private static CompositionArea compositionArea;
  53     private static Object compositionAreaLock = new Object();
  54     private static CompositionAreaHandler compositionAreaOwner; // synchronized through compositionArea
  55 
  56     private AttributedCharacterIterator composedText;
  57     private TextHitInfo caret = null;
  58     private Component clientComponent = null;
  59     private InputMethodContext inputMethodContext;
  60 
  61     /**
  62      * Constructs the composition area handler.
  63      */
  64     CompositionAreaHandler(InputMethodContext context) {
  65         inputMethodContext = context;
  66     }
  67 
  68     /**
  69      * Creates the composition area.
  70      */
  71     private void createCompositionArea() {
  72         synchronized(compositionAreaLock) {
  73             compositionArea = new CompositionArea();
  74             if (compositionAreaOwner != null) {
  75                 compositionArea.setHandlerInfo(compositionAreaOwner, inputMethodContext);
  76             }
  77             // If the client component is an active client using below-the-spot style, then
  78             // make the composition window undecorated without a title bar.
  79             if(clientComponent!=null){
  80                 InputMethodRequests req = clientComponent.getInputMethodRequests();
  81                 if (req != null && inputMethodContext.useBelowTheSpotInput()) {
  82                     setCompositionAreaUndecorated(true);
  83                 }
  84             }
  85         }
  86     }
  87 
  88     void setClientComponent(Component clientComponent) {
  89         this.clientComponent = clientComponent;
  90     }
  91 
  92     /**
  93      * Grabs the composition area, makes this handler its owner, and installs
  94      * the handler and its input context into the composition area for event
  95      * and input method request handling.
  96      * If doUpdate is true, updates the composition area with previously sent
  97      * composed text.
  98      */
  99 
 100     void grabCompositionArea(boolean doUpdate) {
 101         synchronized (compositionAreaLock) {
 102             if (compositionAreaOwner != this) {
 103                 compositionAreaOwner = this;
 104                 if (compositionArea != null) {
 105                     compositionArea.setHandlerInfo(this, inputMethodContext);
 106                 }
 107                 if (doUpdate) {
 108                     // Create the composition area if necessary
 109                     if ((composedText != null) && (compositionArea == null)) {
 110                         createCompositionArea();
 111                     }
 112                     if (compositionArea != null) {
 113                         compositionArea.setText(composedText, caret);
 114                     }
 115                 }
 116             }
 117         }
 118     }
 119 
 120     /**
 121      * Releases and closes the composition area if it is currently owned by
 122      * this composition area handler.
 123      */
 124     void releaseCompositionArea() {
 125         synchronized (compositionAreaLock) {
 126             if (compositionAreaOwner == this) {
 127                 compositionAreaOwner = null;
 128                 if (compositionArea != null) {
 129                     compositionArea.setHandlerInfo(null, null);
 130                     compositionArea.setText(null, null);
 131                 }
 132             }
 133         }
 134     }
 135 
 136     /**
 137      * Releases and closes the composition area if it has been created,
 138      * independent of the current owner.
 139      */
 140     static void closeCompositionArea() {
 141         if (compositionArea != null) {
 142             synchronized (compositionAreaLock) {
 143                 compositionAreaOwner = null;
 144                 compositionArea.setHandlerInfo(null, null);
 145                 compositionArea.setText(null, null);
 146             }
 147         }
 148     }
 149 
 150     /**
 151      * Returns whether the composition area is currently visible
 152      */
 153     boolean isCompositionAreaVisible() {
 154         if (compositionArea != null) {
 155             return compositionArea.isCompositionAreaVisible();
 156         }
 157 
 158         return false;
 159     }
 160 
 161 
 162     /**
 163      * Shows or hides the composition Area
 164      */
 165     void setCompositionAreaVisible(boolean visible) {
 166         if (compositionArea != null) {
 167             compositionArea.setCompositionAreaVisible(visible);
 168         }
 169     }
 170 
 171     void processInputMethodEvent(InputMethodEvent event) {
 172         if (event.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
 173             inputMethodTextChanged(event);
 174         } else {
 175             caretPositionChanged(event);
 176         }
 177     }
 178 
 179     /**
 180      * set the compositionArea frame decoration
 181      */
 182     void setCompositionAreaUndecorated(boolean undecorated) {
 183         if (compositionArea != null) {
 184             compositionArea.setCompositionAreaUndecorated(undecorated);
 185         }
 186     }
 187 
 188     //
 189     // InputMethodListener methods
 190     //
 191 
 192     private static final Attribute[] IM_ATTRIBUTES =
 193             { TextAttribute.INPUT_METHOD_HIGHLIGHT };
 194 
 195     public void inputMethodTextChanged(InputMethodEvent event) {
 196         AttributedCharacterIterator text = event.getText();
 197         int committedCharacterCount = event.getCommittedCharacterCount();
 198 
 199         // extract composed text and prepare it for display
 200         composedText = null;
 201         caret = null;
 202         if (text != null
 203                 && committedCharacterCount < text.getEndIndex() - text.getBeginIndex()) {
 204 
 205             // Create the composition area if necessary
 206             if (compositionArea == null) {
 207                  createCompositionArea();
 208             }
 209 
 210             // copy the composed text
 211             AttributedString composedTextString;
 212             composedTextString = new AttributedString(text,
 213                     text.getBeginIndex() + committedCharacterCount, // skip over committed text
 214                     text.getEndIndex(), IM_ATTRIBUTES);
 215             composedTextString.addAttribute(TextAttribute.FONT, compositionArea.getFont());
 216             composedText = composedTextString.getIterator();
 217             caret = event.getCaret();
 218         }
 219 
 220         if (compositionArea != null) {
 221             compositionArea.setText(composedText, caret);
 222         }
 223 
 224         // send any committed text to the text component
 225         if (committedCharacterCount > 0) {
 226             inputMethodContext.dispatchCommittedText(((Component) event.getSource()),
 227                                                      text, committedCharacterCount);
 228 
 229             // this may have changed the text location, so reposition the window
 230             if (isCompositionAreaVisible()) {
 231                 compositionArea.updateWindowLocation();
 232             }
 233         }
 234 
 235         // event has been handled, so consume it
 236         event.consume();
 237     }
 238 
 239     public void caretPositionChanged(InputMethodEvent event) {
 240         if (compositionArea != null) {
 241             compositionArea.setCaret(event.getCaret());
 242         }
 243 
 244         // event has been handled, so consume it
 245         event.consume();
 246     }
 247 
 248     //
 249     // InputMethodRequests methods
 250     //
 251 
 252     /**
 253      * Returns the input method request handler of the client component.
 254      * When using the composition window for an active client (below-the-spot
 255      * input), input method requests that do not relate to the display of
 256      * the composed text are forwarded to the client component.
 257      */
 258     InputMethodRequests getClientInputMethodRequests() {
 259         if (clientComponent != null) {
 260             return clientComponent.getInputMethodRequests();
 261         }
 262 
 263         return null;
 264     }
 265 
 266     public Rectangle getTextLocation(TextHitInfo offset) {
 267         synchronized (compositionAreaLock) {
 268             if (compositionAreaOwner == this && isCompositionAreaVisible()) {
 269                 return compositionArea.getTextLocation(offset);
 270             } else if (composedText != null) {
 271                 // there's composed text, but it's not displayed, so fake a rectangle
 272                 return new Rectangle(0, 0, 0, 10);
 273             } else {
 274                 InputMethodRequests requests = getClientInputMethodRequests();
 275                 if (requests != null) {
 276                     return requests.getTextLocation(offset);
 277                 } else {
 278                     // passive client, no composed text, so fake a rectangle
 279                     return new Rectangle(0, 0, 0, 10);
 280                 }
 281             }
 282         }
 283     }
 284 
 285     public TextHitInfo getLocationOffset(int x, int y) {
 286         synchronized (compositionAreaLock) {
 287             if (compositionAreaOwner == this && isCompositionAreaVisible()) {
 288                 return compositionArea.getLocationOffset(x, y);
 289             } else {
 290                 return null;
 291             }
 292         }
 293     }
 294 
 295     public int getInsertPositionOffset() {
 296         InputMethodRequests req = getClientInputMethodRequests();
 297         if (req != null) {
 298             return req.getInsertPositionOffset();
 299         }
 300 
 301         // we don't have access to the client component's text.
 302         return 0;
 303     }
 304 
 305     private static final AttributedCharacterIterator EMPTY_TEXT =
 306             (new AttributedString("")).getIterator();
 307 
 308     public AttributedCharacterIterator getCommittedText(int beginIndex,
 309                                                        int endIndex,
 310                                                        Attribute[] attributes) {
 311         InputMethodRequests req = getClientInputMethodRequests();
 312         if(req != null) {
 313             return req.getCommittedText(beginIndex, endIndex, attributes);
 314         }
 315 
 316         // we don't have access to the client component's text.
 317         return EMPTY_TEXT;
 318     }
 319 
 320     public int getCommittedTextLength() {
 321         InputMethodRequests req = getClientInputMethodRequests();
 322         if(req != null) {
 323             return req.getCommittedTextLength();
 324         }
 325 
 326         // we don't have access to the client component's text.
 327         return 0;
 328     }
 329 
 330 
 331     public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
 332         InputMethodRequests req = getClientInputMethodRequests();
 333         if(req != null) {
 334             return req.cancelLatestCommittedText(attributes);
 335         }
 336 
 337         // we don't have access to the client component's text.
 338         return null;
 339     }
 340 
 341     public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
 342         InputMethodRequests req = getClientInputMethodRequests();
 343         if(req != null) {
 344             return req.getSelectedText(attributes);
 345         }
 346 
 347         // we don't have access to the client component's text.
 348         return EMPTY_TEXT;
 349     }
 350 
 351 }