1 /*
2  * Copyright (c) 1997, 2021, 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.Component;
30 import java.awt.GraphicsEnvironment;
31 import java.awt.HeadlessException;
32 import java.awt.Rectangle;
33 import java.awt.Toolkit;
34 import java.awt.Window;
35 import java.awt.event.KeyEvent;
36 import java.awt.event.InputMethodEvent;
37 import java.awt.font.TextHitInfo;
38 import java.awt.im.InputMethodRequests;
39 import java.awt.im.spi.InputMethod;
40 import java.security.AccessController;
41 import java.text.AttributedCharacterIterator;
42 import java.text.AttributedCharacterIterator.Attribute;
43 import java.text.AttributedString;
44 import java.text.CharacterIterator;
45 import javax.swing.JFrame;
46 import sun.awt.InputMethodSupport;
47 import sun.security.action.GetPropertyAction;
48 
49 /**
50  * The InputMethodContext class provides methods that input methods
51  * can use to communicate with their client components.
52  * It is a subclass of InputContext, which provides methods for use by
53  * components.
54  *
55  * @author JavaSoft International
56  */
57 
58 public class InputMethodContext
59        extends sun.awt.im.InputContext
60        implements java.awt.im.spi.InputMethodContext {
61 
62     private boolean dispatchingCommittedText;
63 
64     // Creation of the context's composition area handler is
65     // delayed until we really need a composition area.
66     private CompositionAreaHandler compositionAreaHandler;
67     private Object compositionAreaHandlerLock = new Object();
68 
69     private static boolean belowTheSpotInputRequested;
70     private boolean inputMethodSupportsBelowTheSpot;
71 
72     static {
73         // check whether we should use below-the-spot input
74         // get property from command line
75         @SuppressWarnings("removal")
76         String inputStyle = AccessController.doPrivileged
77                 (new GetPropertyAction("java.awt.im.style", null));
78         // get property from awt.properties file
79         if (inputStyle == null) {
80             inputStyle = Toolkit.getProperty("java.awt.im.style", null);
81         }
82         belowTheSpotInputRequested = "below-the-spot".equals(inputStyle);
83     }
84 
85     /**
86      * Constructs an InputMethodContext.
87      */
InputMethodContext()88     public InputMethodContext() {
89         super();
90     }
91 
setInputMethodSupportsBelowTheSpot(boolean supported)92     void setInputMethodSupportsBelowTheSpot(boolean supported) {
93         inputMethodSupportsBelowTheSpot = supported;
94     }
95 
useBelowTheSpotInput()96    boolean useBelowTheSpotInput() {
97         return belowTheSpotInputRequested && inputMethodSupportsBelowTheSpot;
98     }
99 
haveActiveClient()100     private boolean haveActiveClient() {
101         Component client = getClientComponent();
102         return client != null
103                && client.getInputMethodRequests() != null;
104     }
105 
106     // implements java.awt.im.spi.InputMethodContext.dispatchInputMethodEvent
dispatchInputMethodEvent(int id, AttributedCharacterIterator text, int committedCharacterCount, TextHitInfo caret, TextHitInfo visiblePosition)107     public void dispatchInputMethodEvent(int id,
108                 AttributedCharacterIterator text, int committedCharacterCount,
109                 TextHitInfo caret, TextHitInfo visiblePosition) {
110         // We need to record the client component as the source so
111         // that we have correct information if we later have to break up this
112         // event into key events.
113         Component source;
114 
115         source = getClientComponent();
116         if (source != null) {
117             InputMethodEvent event = new InputMethodEvent(source,
118                     id, text, committedCharacterCount, caret, visiblePosition);
119 
120             if (haveActiveClient() && !useBelowTheSpotInput()) {
121                 source.dispatchEvent(event);
122             } else {
123                 getCompositionAreaHandler(true).processInputMethodEvent(event);
124             }
125         }
126     }
127 
128     /**
129      * Dispatches committed text to a client component.
130      * Called by composition window.
131      *
132      * @param client The component that the text should get dispatched to.
133      * @param text The iterator providing access to the committed
134      *        (and possible composed) text.
135      * @param committedCharacterCount The number of committed characters in the text.
136      */
dispatchCommittedText(Component client, AttributedCharacterIterator text, int committedCharacterCount)137     synchronized void dispatchCommittedText(Component client,
138                  AttributedCharacterIterator text,
139                  int committedCharacterCount) {
140         // note that the client is not always the current client component -
141         // some host input method adapters may dispatch input method events
142         // through the Java event queue, and we may have switched clients while
143         // the event was in the queue.
144         if (committedCharacterCount == 0
145                 || text.getEndIndex() <= text.getBeginIndex()) {
146             return;
147         }
148         long time = System.currentTimeMillis();
149         dispatchingCommittedText = true;
150         try {
151             InputMethodRequests req = client.getInputMethodRequests();
152             if (req != null) {
153                 // active client -> send text as InputMethodEvent
154                 int beginIndex = text.getBeginIndex();
155                 AttributedCharacterIterator toBeCommitted =
156                     (new AttributedString(text, beginIndex, beginIndex + committedCharacterCount)).getIterator();
157 
158                 InputMethodEvent inputEvent = new InputMethodEvent(
159                         client,
160                         InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
161                         toBeCommitted,
162                         committedCharacterCount,
163                         null, null);
164 
165                 client.dispatchEvent(inputEvent);
166             } else {
167                 // passive client -> send text as KeyEvents
168                 char keyChar = text.first();
169                 while (committedCharacterCount-- > 0 && keyChar != CharacterIterator.DONE) {
170                     KeyEvent keyEvent = new KeyEvent(client, KeyEvent.KEY_TYPED,
171                                                  time, 0, KeyEvent.VK_UNDEFINED, keyChar);
172                     client.dispatchEvent(keyEvent);
173                     keyChar = text.next();
174                 }
175             }
176         } finally {
177             dispatchingCommittedText = false;
178         }
179     }
180 
dispatchEvent(AWTEvent event)181     public void dispatchEvent(AWTEvent event) {
182         // some host input method adapters may dispatch input method events
183         // through the Java event queue. If the component that the event is
184         // intended for isn't an active client, or if we're using below-the-spot
185         // input, we need to dispatch this event
186         // to the input window. Note that that component is not necessarily the
187         // current client component, since we may have switched clients while
188         // the event was in the queue.
189         if (event instanceof InputMethodEvent) {
190             if (((Component) event.getSource()).getInputMethodRequests() == null
191                     || (useBelowTheSpotInput() && !dispatchingCommittedText)) {
192                 getCompositionAreaHandler(true).processInputMethodEvent((InputMethodEvent) event);
193             }
194         } else {
195             // make sure we don't dispatch our own key events back to the input method
196             if (!dispatchingCommittedText) {
197                 super.dispatchEvent(event);
198             }
199         }
200     }
201 
202     /**
203      * Gets this context's composition area handler, creating it if necessary.
204      * If requested, it grabs the composition area for use by this context.
205      * The composition area's text is not updated.
206      */
getCompositionAreaHandler(boolean grab)207     private CompositionAreaHandler getCompositionAreaHandler(boolean grab) {
208         synchronized(compositionAreaHandlerLock) {
209             if (compositionAreaHandler == null) {
210                 compositionAreaHandler = new CompositionAreaHandler(this);
211             }
212             compositionAreaHandler.setClientComponent(getClientComponent());
213             if (grab) {
214                 compositionAreaHandler.grabCompositionArea(false);
215             }
216 
217             return compositionAreaHandler;
218         }
219     }
220 
221     /**
222      * Grabs the composition area for use by this context.
223      * If doUpdate is true, updates the composition area with previously sent
224      * composed text.
225      */
grabCompositionArea(boolean doUpdate)226     void grabCompositionArea(boolean doUpdate) {
227         synchronized(compositionAreaHandlerLock) {
228             if (compositionAreaHandler != null) {
229                 compositionAreaHandler.grabCompositionArea(doUpdate);
230             } else {
231                 // if this context hasn't seen a need for a composition area yet,
232                 // just close it without creating the machinery
233                 CompositionAreaHandler.closeCompositionArea();
234             }
235         }
236     }
237 
238     /**
239      * Releases and closes the composition area if it is currently owned by
240      * this context's composition area handler.
241      */
releaseCompositionArea()242     void releaseCompositionArea() {
243         synchronized(compositionAreaHandlerLock) {
244             if (compositionAreaHandler != null) {
245                 compositionAreaHandler.releaseCompositionArea();
246             }
247         }
248     }
249 
250     /**
251      * Calls CompositionAreaHandler.isCompositionAreaVisible() to see
252      * whether the composition area is visible or not.
253      * Notice that this method is always called on the AWT event dispatch
254      * thread.
255      */
isCompositionAreaVisible()256     boolean isCompositionAreaVisible() {
257         if (compositionAreaHandler != null) {
258             return compositionAreaHandler.isCompositionAreaVisible();
259         }
260 
261         return false;
262     }
263     /**
264      * Calls CompositionAreaHandler.setCompositionAreaVisible to
265      * show or hide the composition area.
266      * As isCompositionAreaVisible method, it is always called
267      * on AWT event dispatch thread.
268      */
setCompositionAreaVisible(boolean visible)269     void setCompositionAreaVisible(boolean visible) {
270         if (compositionAreaHandler != null) {
271             compositionAreaHandler.setCompositionAreaVisible(visible);
272         }
273     }
274 
275     /**
276      * Calls the current client component's implementation of getTextLocation.
277      */
getTextLocation(TextHitInfo offset)278     public Rectangle getTextLocation(TextHitInfo offset) {
279         return getReq().getTextLocation(offset);
280     }
281 
282     /**
283      * Calls the current client component's implementation of getLocationOffset.
284      */
getLocationOffset(int x, int y)285     public TextHitInfo getLocationOffset(int x, int y) {
286         return getReq().getLocationOffset(x, y);
287     }
288 
289     /**
290      * Calls the current client component's implementation of getInsertPositionOffset.
291      */
getInsertPositionOffset()292     public int getInsertPositionOffset() {
293         return getReq().getInsertPositionOffset();
294     }
295 
296     /**
297      * Calls the current client component's implementation of getCommittedText.
298      */
getCommittedText(int beginIndex, int endIndex, Attribute[] attributes)299     public AttributedCharacterIterator getCommittedText(int beginIndex,
300                                                        int endIndex,
301                                                        Attribute[] attributes) {
302         return getReq().getCommittedText(beginIndex, endIndex, attributes);
303     }
304 
305     /**
306      * Calls the current client component's implementation of getCommittedTextLength.
307      */
getCommittedTextLength()308     public int getCommittedTextLength() {
309         return getReq().getCommittedTextLength();
310     }
311 
312 
313     /**
314      * Calls the current client component's implementation of cancelLatestCommittedText.
315      */
cancelLatestCommittedText(Attribute[] attributes)316     public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
317         return getReq().cancelLatestCommittedText(attributes);
318     }
319 
320     /**
321      * Calls the current client component's implementation of getSelectedText.
322      */
getSelectedText(Attribute[] attributes)323     public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
324         return getReq().getSelectedText(attributes);
325     }
326 
getReq()327     private InputMethodRequests getReq() {
328         if (haveActiveClient() && !useBelowTheSpotInput()) {
329             return getClientComponent().getInputMethodRequests();
330         } else {
331             return getCompositionAreaHandler(false);
332         }
333     }
334 
335     // implements java.awt.im.spi.InputMethodContext.createInputMethodWindow
createInputMethodWindow(String title, boolean attachToInputContext)336     public Window createInputMethodWindow(String title, boolean attachToInputContext) {
337         InputContext context = attachToInputContext ? this : null;
338         return createInputMethodWindow(title, context, false);
339     }
340 
341     // implements java.awt.im.spi.InputMethodContext.createInputMethodJFrame
createInputMethodJFrame(String title, boolean attachToInputContext)342     public JFrame createInputMethodJFrame(String title, boolean attachToInputContext) {
343         InputContext context = attachToInputContext ? this : null;
344         return (JFrame)createInputMethodWindow(title, context, true);
345     }
346 
createInputMethodWindow(String title, InputContext context, boolean isSwing)347     static Window createInputMethodWindow(String title, InputContext context, boolean isSwing) {
348         if (GraphicsEnvironment.isHeadless()) {
349             throw new HeadlessException();
350         }
351         if (isSwing) {
352             return new InputMethodJFrame(title, context);
353         } else {
354             Toolkit toolkit = Toolkit.getDefaultToolkit();
355             if (toolkit instanceof InputMethodSupport) {
356                 return ((InputMethodSupport)toolkit).createInputMethodWindow(
357                     title, context);
358             }
359         }
360         throw new InternalError("Input methods must be supported");
361     }
362 
363     /**
364      * @see java.awt.im.spi.InputMethodContext#enableClientWindowNotification
365      */
enableClientWindowNotification(InputMethod inputMethod, boolean enable)366     public void enableClientWindowNotification(InputMethod inputMethod, boolean enable) {
367         super.enableClientWindowNotification(inputMethod, enable);
368     }
369 
370   /**
371    * Disables or enables decorations for the composition window.
372    */
setCompositionAreaUndecorated(boolean undecorated)373    void setCompositionAreaUndecorated(boolean undecorated) {
374         if (compositionAreaHandler != null) {
375             compositionAreaHandler.setCompositionAreaUndecorated(undecorated);
376         }
377    }
378 }
379