View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.html;
16  
17  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_ADD;
18  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_BACK_SPACE;
19  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DECIMAL;
20  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DELETE;
21  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DIVIDE;
22  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_END;
23  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_EQUALS;
24  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_HOME;
25  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_LEFT;
26  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_MULTIPLY;
27  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_NUMPAD0;
28  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_NUMPAD9;
29  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_RIGHT;
30  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SEMICOLON;
31  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SEPARATOR;
32  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SPACE;
33  import static com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SUBTRACT;
34  
35  import java.awt.Toolkit;
36  import java.awt.datatransfer.Clipboard;
37  import java.awt.datatransfer.ClipboardOwner;
38  import java.awt.datatransfer.DataFlavor;
39  import java.awt.datatransfer.StringSelection;
40  import java.awt.datatransfer.Transferable;
41  import java.awt.datatransfer.UnsupportedFlavorException;
42  import java.io.IOException;
43  import java.io.Serializable;
44  import java.util.HashMap;
45  import java.util.Map;
46  
47  import com.gargoylesoftware.htmlunit.html.impl.SelectionDelegate;
48  
49  /**
50   * The process for {@link HtmlElement#doType(char, boolean, boolean)}.
51   *
52   * @author Marc Guillemot
53   * @author Ronald Brill
54   * @author Ahmed Ashour
55   */
56  class DoTypeProcessor implements Serializable, ClipboardOwner {
57  
58      private static Map<Integer, Character> SPECIAL_KEYS_MAP_ = new HashMap<>();
59  
60      /**
61       * Either {@link HtmlElement} or {@link DomText}.
62       */
63      private DomNode domNode_;
64  
65      static {
66          SPECIAL_KEYS_MAP_.put(DOM_VK_ADD, '+');
67          SPECIAL_KEYS_MAP_.put(DOM_VK_DECIMAL, '.');
68          SPECIAL_KEYS_MAP_.put(DOM_VK_DIVIDE, '/');
69          SPECIAL_KEYS_MAP_.put(DOM_VK_EQUALS, '=');
70          SPECIAL_KEYS_MAP_.put(DOM_VK_MULTIPLY, '*');
71          SPECIAL_KEYS_MAP_.put(DOM_VK_SEMICOLON, ';');
72          SPECIAL_KEYS_MAP_.put(DOM_VK_SEPARATOR, ',');
73          SPECIAL_KEYS_MAP_.put(DOM_VK_SPACE, ' ');
74          SPECIAL_KEYS_MAP_.put(DOM_VK_SUBTRACT, '-');
75  
76          for (int i = DOM_VK_NUMPAD0; i <= DOM_VK_NUMPAD9; i++) {
77              SPECIAL_KEYS_MAP_.put(i, (char) ('0' + (i - DOM_VK_NUMPAD0)));
78          }
79      }
80  
81      DoTypeProcessor(final DomNode domNode) {
82          domNode_ = domNode;
83      }
84  
85      void doType(final String currentValue, final SelectionDelegate selectionDelegate,
86              final char c, final HtmlElement element, final boolean lastType) {
87  
88          int selectionStart = selectionDelegate.getSelectionStart();
89          int selectionEnd = selectionDelegate.getSelectionEnd();
90  
91          final StringBuilder newValue = new StringBuilder(currentValue);
92          if (c == '\b') {
93              if (selectionStart > 0) {
94                  newValue.deleteCharAt(selectionStart - 1);
95                  selectionStart--;
96                  selectionEnd--;
97              }
98          }
99          else if (acceptChar(c)) {
100             final boolean ctrlKey = element.isCtrlPressed();
101             if (ctrlKey && (c == 'C' || c == 'c')) {
102                 final String content = newValue.substring(selectionStart, selectionEnd);
103                 setClipboardContent(content);
104             }
105             else if (ctrlKey && (c == 'V' || c == 'v')) {
106                 final String content = getClipboardContent();
107                 add(newValue, content, selectionStart, selectionEnd);
108                 selectionStart += content.length();
109                 selectionEnd = selectionStart;
110             }
111             else if (ctrlKey && (c == 'X' || c == 'x')) {
112                 final String content = newValue.substring(selectionStart, selectionEnd);
113                 setClipboardContent(content);
114                 newValue.delete(selectionStart, selectionEnd);
115                 selectionEnd = selectionStart;
116             }
117             else if (ctrlKey && (c == 'A' || c == 'a')) {
118                 selectionStart = 0;
119                 selectionEnd = newValue.length();
120             }
121             else {
122                 add(newValue, c, selectionStart, selectionEnd);
123                 selectionStart++;
124                 selectionEnd = selectionStart;
125             }
126         }
127 
128         typeDone(newValue.toString(), lastType);
129 
130         selectionDelegate.setSelectionStart(selectionStart);
131         selectionDelegate.setSelectionEnd(selectionEnd);
132     }
133 
134     private static void add(final StringBuilder newValue, final char c, final int selectionStart,
135             final int selectionEnd) {
136         if (selectionStart != newValue.length()) {
137             newValue.replace(selectionStart, selectionEnd, Character.toString(c));
138         }
139         else {
140             newValue.append(c);
141         }
142     }
143 
144     private static void add(final StringBuilder newValue, final String string, final int selectionStart,
145             final int selectionEnd) {
146         if (selectionStart != newValue.length()) {
147             newValue.replace(selectionStart, selectionEnd, string);
148         }
149         else {
150             newValue.append(string);
151         }
152     }
153 
154     private static String getClipboardContent() {
155         String result = "";
156         final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
157         final Transferable contents = clipboard.getContents(null);
158         if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
159             try {
160                 result = (String) contents.getTransferData(DataFlavor.stringFlavor);
161             }
162             catch (final UnsupportedFlavorException | IOException ex) {
163             }
164         }
165         return result;
166     }
167 
168     private void setClipboardContent(final String string) {
169         final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
170         final StringSelection stringSelection = new StringSelection(string);
171         clipboard.setContents(stringSelection, this);
172     }
173 
174     private void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
175         if (domNode_ instanceof DomText) {
176             ((DomText) domNode_).setData(newValue);
177         }
178         else {
179             ((HtmlElement) domNode_).typeDone(newValue, notifyAttributeChangeListeners);
180         }
181     }
182 
183     private boolean acceptChar(final char ch) {
184         if (domNode_ instanceof DomText) {
185             return ((DomText) domNode_).acceptChar(ch);
186         }
187         return ((HtmlElement) domNode_).acceptChar(ch);
188     }
189 
190     void doType(final String currentValue, final SelectionDelegate selectionDelegate,
191             final int keyCode, final HtmlElement element, final boolean lastType) {
192 
193         final StringBuilder newValue = new StringBuilder(currentValue);
194         int selectionStart = selectionDelegate.getSelectionStart();
195         int selectionEnd = selectionDelegate.getSelectionEnd();
196 
197         final Character ch = SPECIAL_KEYS_MAP_.get(keyCode);
198         if (ch != null) {
199             doType(currentValue, selectionDelegate, ch, element, lastType);
200             return;
201         }
202         switch (keyCode) {
203             case DOM_VK_BACK_SPACE:
204                 if (selectionStart > 0) {
205                     newValue.deleteCharAt(selectionStart - 1);
206                     selectionStart--;
207                 }
208                 break;
209 
210             case DOM_VK_LEFT:
211                 if (element.isCtrlPressed()) {
212                     while (selectionStart > 0 && newValue.charAt(selectionStart - 1) != ' ') {
213                         selectionStart--;
214                     }
215                 }
216                 else if (selectionStart > 0) {
217                     selectionStart--;
218                 }
219                 break;
220 
221             case DOM_VK_RIGHT:
222                 if (element.isCtrlPressed()) {
223                     if (selectionStart < newValue.length()) {
224                         selectionStart++;
225                     }
226                     while (selectionStart < newValue.length() && newValue.charAt(selectionStart - 1) != ' ') {
227                         selectionStart++;
228                     }
229                 }
230                 else if (element.isShiftPressed()) {
231                     selectionEnd++;
232                 }
233                 else if (selectionStart > 0) {
234                     selectionStart++;
235                 }
236                 break;
237 
238             case DOM_VK_HOME:
239                 selectionStart = 0;
240                 break;
241 
242             case DOM_VK_END:
243                 if (element.isShiftPressed()) {
244                     selectionEnd = newValue.length();
245                 }
246                 else {
247                     selectionStart = newValue.length();
248                 }
249                 break;
250 
251             case DOM_VK_DELETE:
252                 if (selectionEnd == selectionStart) {
253                     selectionEnd++;
254                 }
255                 newValue.delete(selectionStart, selectionEnd);
256                 selectionEnd = selectionStart;
257                 break;
258 
259             default:
260                 return;
261         }
262 
263         if (!element.isShiftPressed()) {
264             selectionEnd = selectionStart;
265         }
266 
267         typeDone(newValue.toString(), lastType);
268 
269         selectionDelegate.setSelectionStart(selectionStart);
270         selectionDelegate.setSelectionEnd(selectionEnd);
271     }
272 
273     /**
274      * {@inheritDoc}
275      */
276     @Override
277     public void lostOwnership(final Clipboard clipboard, final Transferable contents) {
278     }
279 
280 }