1 /* 2 * Copyright (c) 2011, 2018, 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 com.sun.javafx.webkit; 27 28 import com.sun.javafx.scene.SceneHelper; 29 import java.lang.ref.WeakReference; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.concurrent.ExecutionException; 33 import java.util.concurrent.FutureTask; 34 import java.util.logging.Level; 35 import java.util.logging.Logger; 36 37 import com.sun.javafx.scene.input.ExtendedInputMethodRequests; 38 import com.sun.webkit.Invoker; 39 import javafx.geometry.Point2D; 40 import javafx.scene.input.InputMethodEvent; 41 import javafx.scene.input.InputMethodHighlight; 42 import javafx.scene.input.InputMethodRequests; 43 import javafx.scene.input.InputMethodTextRun; 44 import javafx.scene.web.WebView; 45 46 import com.sun.webkit.InputMethodClient; 47 import com.sun.webkit.WebPage; 48 import com.sun.webkit.event.WCInputMethodEvent; 49 import com.sun.webkit.graphics.WCPoint; 50 51 public final class InputMethodClientImpl 52 implements InputMethodClient, ExtendedInputMethodRequests 53 { 54 private static final Logger log = 55 Logger.getLogger(InputMethodClientImpl.class.getName()); 56 private final WeakReference<WebView> wvRef; 57 private final WebPage webPage; 58 59 // the state of the last setInputMethodState() call. 60 private boolean state; 61 62 public InputMethodClientImpl(WebView wv, WebPage webPage) { 63 this.wvRef = new WeakReference<WebView>(wv); 64 this.webPage = webPage; 65 if (webPage != null) { 66 webPage.setInputMethodClient(this); 67 } 68 } 69 70 public void activateInputMethods(final boolean doActivate) { 71 WebView wv = wvRef.get(); 72 if (wv != null && wv.getScene() != null) { 73 SceneHelper.enableInputMethodEvents(wv.getScene(), doActivate); 74 } 75 state = doActivate; 76 } 77 78 public boolean getInputMethodState() { 79 return state; 80 } 81 82 /** 83 * Converts the given InputMethodEvent to a WCInputMethodEvent. 84 */ 85 public static WCInputMethodEvent convertToWCInputMethodEvent(InputMethodEvent ie) { 86 List<Integer> underlines = new ArrayList<Integer>(); 87 StringBuilder composed = new StringBuilder(); 88 int pos = 0; 89 90 // Scan the given composedText to find input method highlight attribute runs. 91 for (InputMethodTextRun run : ie.getComposed()) { 92 String rawText = run.getText(); 93 94 // Convert highlight information of the attribute run into a 95 // CompositionUnderline. 96 InputMethodHighlight imh = run.getHighlight(); 97 underlines.add(pos); 98 underlines.add(pos + rawText.length()); 99 // WebKit CompostionUnderline supports only two kinds of highlighting 100 // attributes, thin and thick underlines. The SELECTED_CONVERTED 101 // and SELECTED_RAW attributes of JavaFX are mapped to the thick one. 102 underlines.add((imh == InputMethodHighlight.SELECTED_CONVERTED || 103 imh == InputMethodHighlight.SELECTED_RAW) ? 1 : 0); 104 pos += rawText.length(); 105 composed.append(rawText); 106 } 107 108 int size = underlines.size(); 109 // In case there's no highlight information, create an underline element 110 // for the entire text 111 if (size == 0) { 112 underlines.add(0); 113 underlines.add(pos); 114 underlines.add(0); // thin underline 115 size = underlines.size(); 116 } 117 int[] attributes = new int[size]; 118 for (int i = 0; i < size; i++) { 119 attributes[i] = underlines.get(i); 120 } 121 122 return new WCInputMethodEvent(ie.getCommitted(), composed.toString(), 123 attributes, ie.getCaretPosition()); 124 } 125 126 // InputMethodRequests implementation 127 public Point2D getTextLocation(int offset) { 128 FutureTask<Point2D> f = new FutureTask<>(() -> { 129 int[] loc = webPage.getClientTextLocation(offset); 130 WCPoint point = webPage.getPageClient().windowToScreen( 131 // We need lower left corner of the char bounds rectangle here 132 new WCPoint(loc[0], loc[1] + loc[3])); 133 return new Point2D(point.getIntX(), point.getIntY()); 134 }); 135 136 Invoker.getInvoker().invokeOnEventThread(f); 137 Point2D result = null; 138 try { 139 result = f.get(); 140 } catch (ExecutionException ex) { 141 log.log(Level.SEVERE, "InputMethodClientImpl.getTextLocation " + ex); 142 } catch (InterruptedException ex) { 143 log.log(Level.SEVERE, "InputMethodClientImpl.getTextLocation InterruptedException" + ex); 144 } 145 return result; 146 } 147 148 public int getLocationOffset(int x, int y) { 149 FutureTask<Integer> f = new FutureTask<>(() -> { 150 WCPoint point = webPage.getPageClient().windowToScreen(new WCPoint(0, 0)); 151 return webPage.getClientLocationOffset(x - point.getIntX(), y - point.getIntY()); 152 }); 153 154 Invoker.getInvoker().invokeOnEventThread(f); 155 int location = 0; 156 try { 157 location = f.get(); 158 } catch (ExecutionException ex) { 159 log.log(Level.SEVERE, "InputMethodClientImpl.getLocationOffset " + ex); 160 } catch (InterruptedException ex) { 161 log.log(Level.SEVERE, "InputMethodClientImpl.getTextLocation InterruptedException" + ex); 162 } 163 return location; 164 } 165 166 public void cancelLatestCommittedText() { 167 // "Undo commit" is not supported. 168 } 169 170 public String getSelectedText() { 171 return webPage.getClientSelectedText(); 172 } 173 174 @Override 175 public int getInsertPositionOffset() { 176 return webPage.getClientInsertPositionOffset(); 177 } 178 179 @Override 180 public String getCommittedText(int begin, int end) { 181 try { 182 return webPage.getClientCommittedText().substring(begin, end); 183 } catch (StringIndexOutOfBoundsException e) { 184 throw new IllegalArgumentException(e); 185 } 186 } 187 188 @Override 189 public int getCommittedTextLength() { 190 return webPage.getClientCommittedTextLength(); 191 } 192 }