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 }