1/*
2 * Copyright (c) 1999, 2013, 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
26package sun.awt.im;
27
28import java.awt.Component;
29import java.awt.Container;
30import java.awt.Rectangle;
31import java.awt.event.InputMethodEvent;
32import java.awt.event.InputMethodListener;
33import java.awt.font.TextAttribute;
34import java.awt.font.TextHitInfo;
35import java.awt.im.InputMethodRequests;
36import java.lang.ref.WeakReference;
37import java.text.AttributedCharacterIterator;
38import java.text.AttributedCharacterIterator.Attribute;
39import 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
50class 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}
355