1 /* 2 * DomUtilsStandardImpl.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 package org.rstudio.core.client.dom.impl; 16 17 import com.google.gwt.dom.client.Document; 18 import com.google.gwt.dom.client.Element; 19 import com.google.gwt.dom.client.SpanElement; 20 import com.google.gwt.dom.client.Text; 21 import org.rstudio.core.client.Rectangle; 22 import org.rstudio.core.client.dom.DomUtils; 23 import org.rstudio.core.client.dom.ElementEx; 24 import org.rstudio.core.client.dom.NativeWindow; 25 26 public class DomUtilsStandardImpl implements DomUtilsImpl 27 { focus(Element element, boolean alwaysDriveSelection)28 public void focus(Element element, boolean alwaysDriveSelection) 29 { 30 ElementEx el = (ElementEx)element; 31 32 el.focus(); 33 if (alwaysDriveSelection 34 || (el.getContentEditable() && 35 (el.getInnerText() == null || el.getInnerText() == ""))) 36 { 37 Document doc = el.getOwnerDocument(); 38 Range range = Range.create(doc); 39 range.selectNodeContents(el); 40 Selection sel = Selection.get(NativeWindow.get(doc)); 41 sel.setRange(range); 42 } 43 44 NativeWindow.get().focus(); 45 } 46 collapseSelection(boolean toStart)47 public void collapseSelection(boolean toStart) 48 { 49 Selection sel = Selection.get(); 50 if (sel == null || sel.getRangeCount() <= 0) 51 return; 52 Range range = sel.getRangeAt(0); 53 range.collapse(toStart); 54 sel.removeAllRanges(); 55 sel.addRange(range); 56 } 57 isSelectionCollapsed()58 public boolean isSelectionCollapsed() 59 { 60 Selection sel = Selection.get(); 61 return sel != null 62 && sel.getRangeCount() == 1 63 && sel.getRangeAt(0).isCollapsed(); 64 } 65 isSelectionInElement(Element element)66 public boolean isSelectionInElement(Element element) 67 { 68 Range rng = getSelectionRange( 69 NativeWindow.get(element.getOwnerDocument()), false); 70 if (rng == null) 71 return false; 72 return DomUtils.contains(element, rng.getCommonAncestorContainer()); 73 } 74 selectionExists()75 public boolean selectionExists() 76 { 77 Selection sel = Selection.get(); 78 if (sel == null || sel.getRangeCount() == 0) 79 return false; 80 if (sel.getRangeCount() > 1) 81 return true; 82 return !sel.getRangeAt(0).isCollapsed(); 83 } 84 getSelectionRange(NativeWindow window, boolean clone)85 public Range getSelectionRange(NativeWindow window, boolean clone) 86 { 87 Selection sel = Selection.get(window); 88 if (sel.getRangeCount() != 1) 89 return null; 90 91 Range result = sel.getRangeAt(0); 92 if (clone) 93 result = result.cloneRange(); 94 return result; 95 } 96 getCursorBounds(Document doc)97 public Rectangle getCursorBounds(Document doc) 98 { 99 100 Selection sel = Selection.get(NativeWindow.get(doc)); 101 Range selRng = sel.getRangeAt(0); 102 if (selRng == null) 103 return null; 104 sel.removeAllRanges(); 105 SpanElement span = doc.createSpanElement(); 106 107 Range rng = selRng.cloneRange(); 108 rng.collapse(true); 109 rng.insertNode(span); 110 111 int x = span.getAbsoluteLeft(); 112 int y = span.getAbsoluteTop(); 113 int w = 0; 114 int h = span.getOffsetHeight(); 115 Rectangle result = new Rectangle(x, y, w, h); 116 117 ElementEx parent = (ElementEx)span.getParentElement(); 118 parent.removeChild(span); 119 parent.normalize(); 120 sel.setRange(selRng); 121 return result; 122 } 123 replaceSelection(Document document, String text)124 public String replaceSelection(Document document, String text) 125 { 126 if (!isSelectionInElement(document.getBody())) 127 throw new IllegalStateException("Selection is not active"); 128 129 Range rng = getSelectionRange(NativeWindow.get(document), true); 130 String orig = rng.toStringJs(); 131 rng.deleteContents(); 132 133 Text textNode = document.createTextNode(text); 134 rng.insertNode(textNode); 135 rng.selectNode(textNode); 136 137 Selection.get(NativeWindow.get(document)).setRange(rng); 138 139 return orig; 140 } 141 getSelectionText(Document document)142 public String getSelectionText(Document document) 143 { 144 Range range = getSelectionRange(NativeWindow.get(document), false); 145 if (range == null || range.isCollapsed()) 146 return null; 147 else 148 return range.toStringJs(); 149 } 150 getSelectionOffsets(Element container)151 public int[] getSelectionOffsets(Element container) 152 { 153 Range rng = getSelectionRange( 154 NativeWindow.get(container.getOwnerDocument()), 155 false); 156 157 if (rng == null) 158 return null; 159 160 int start = NodeRelativePosition.toOffset(container, 161 new NodeRelativePosition(rng.getStartContainer(), 162 rng.getStartOffset())); 163 int end = NodeRelativePosition.toOffset(container, 164 new NodeRelativePosition(rng.getEndContainer(), 165 rng.getEndOffset())); 166 if (start >= 0 && end >= 0) 167 return new int[] {start, end}; 168 else 169 return null; 170 } 171 setSelectionOffsets(Element container, int start, int end)172 public void setSelectionOffsets(Element container, int start, int end) 173 { 174 NodeRelativePosition startp = NodeRelativePosition.toPosition(container, start); 175 NodeRelativePosition endp = NodeRelativePosition.toPosition(container, end); 176 177 Document doc = container.getOwnerDocument(); 178 Range rng = Range.create(doc); 179 rng.setStart(startp.node, startp.offset); 180 rng.setEnd(endp.node, endp.offset); 181 Selection.get(NativeWindow.get(doc)).setRange(rng); 182 183 } 184 isSelectionAsynchronous()185 public boolean isSelectionAsynchronous() 186 { 187 return false; 188 } 189 190 @Override selectElement(Element el)191 public void selectElement(Element el) 192 { 193 Document doc = el.getOwnerDocument(); 194 Range rng = Range.create(doc); 195 rng.selectNode(el); 196 Selection.get(NativeWindow.get(doc)).setRange(rng); 197 } 198 } 199