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