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