1 /* 2 * AceEditorPreview.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.studio.client.workbench.prefs.views; 16 17 import com.google.gwt.core.client.GWT; 18 import com.google.gwt.dom.client.*; 19 import com.google.gwt.dom.client.Style.BorderStyle; 20 import com.google.gwt.dom.client.Style.Unit; 21 import com.google.gwt.resources.client.ClientBundle; 22 import com.google.gwt.resources.client.TextResource; 23 24 import org.rstudio.core.client.ExternalJavaScriptLoader; 25 import org.rstudio.core.client.ExternalJavaScriptLoader.Callback; 26 import org.rstudio.core.client.theme.ThemeFonts; 27 import org.rstudio.core.client.widget.DynamicIFrame; 28 import org.rstudio.core.client.widget.FontSizer; 29 import org.rstudio.studio.client.application.Desktop; 30 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceResources; 31 32 public class AceEditorPreview extends DynamicIFrame 33 { AceEditorPreview(String code)34 public AceEditorPreview(String code) 35 { 36 super("Editor Theme Preview"); 37 code_ = code; 38 Style style = getStyleElement().getStyle(); 39 style.setBorderColor("#CCC"); 40 style.setBorderWidth(1, Unit.PX); 41 style.setBorderStyle(BorderStyle.SOLID); 42 } 43 44 @Override onFrameLoaded()45 protected void onFrameLoaded() 46 { 47 isFrameLoaded_ = true; 48 final Document doc = getDocument(); 49 50 // NOTE: There is an interesting 'feature' in Firefox whereby an 51 // initialized IFrame will report that it has successfully initialized the 52 // window / underlying document (readyState is 'complete') but, in fact, 53 // there is still some initialization left to occur and any changes made 54 // before complete initialization will cause it to be swept out from under 55 // our feet. To work around this, we double-check that the document we are 56 // working with is the _same document_ after each JavaScript load 57 // iteration. 58 new ExternalJavaScriptLoader(getDocument(), AceResources.INSTANCE.acejs().getSafeUri().asString()) 59 .addCallback(new Callback() 60 { 61 public void onLoaded() 62 { 63 if (getDocument() != doc) 64 { 65 onFrameLoaded(); 66 return; 67 } 68 69 new ExternalJavaScriptLoader(getDocument(), AceResources.INSTANCE.acesupportjs().getSafeUri().asString()) 70 .addCallback(new Callback() 71 { 72 public void onLoaded() 73 { 74 75 if (getDocument() != doc) 76 { 77 onFrameLoaded(); 78 return; 79 } 80 81 final Document doc = getDocument(); 82 final BodyElement body = doc.getBody(); 83 84 if (themeUrl_ != null) 85 setTheme(themeUrl_); 86 if (fontSize_ != null) 87 setFontSize(fontSize_); 88 if (zoomLevel_ != null) 89 setZoomLevel(zoomLevel_); 90 91 doc.getHead().getParentElement().setLang("en"); // accessibility requirement 92 93 body.getStyle().setMargin(0, Unit.PX); 94 body.getStyle().setBackgroundColor("white"); 95 96 StyleElement style = doc.createStyleElement(); 97 style.setType("text/css"); 98 style.setInnerText( 99 ".ace_editor {\n" + 100 "border: none !important;\n" + 101 "}"); 102 if (Desktop.isDesktop()) 103 setFont(ThemeFonts.getFixedWidthFont(), false); 104 else if (webFont_ != null) 105 setFont(webFont_, true); 106 body.appendChild(style); 107 108 DivElement div = doc.createDivElement(); 109 div.setId("editor"); 110 div.getStyle().setWidth(100, Unit.PCT); 111 div.getStyle().setHeight(100, Unit.PCT); 112 div.setInnerText(code_); 113 body.appendChild(div); 114 115 FontSizer.injectStylesIntoDocument(doc); 116 FontSizer.applyNormalFontSize(div); 117 118 body.appendChild(doc.createScriptElement(RES.loader().getText())); 119 } 120 }); 121 } 122 }); 123 } 124 setTheme(String themeUrl)125 public void setTheme(String themeUrl) 126 { 127 themeUrl_ = themeUrl; 128 if (!isFrameLoaded_) 129 return; 130 131 if (currentStyleLink_ != null) 132 currentStyleLink_.removeFromParent(); 133 134 Document doc = getDocument(); 135 currentStyleLink_ = doc.createLinkElement(); 136 currentStyleLink_.setRel("stylesheet"); 137 currentStyleLink_.setType("text/css"); 138 currentStyleLink_.setHref(themeUrl); 139 doc.getBody().appendChild(currentStyleLink_); 140 } 141 setFontSize(double fontSize)142 public void setFontSize(double fontSize) 143 { 144 fontSize_ = fontSize; 145 if (!isFrameLoaded_) 146 return; 147 148 if (zoomLevel_ == null) 149 FontSizer.setNormalFontSize(getDocument(), fontSize_); 150 else 151 FontSizer.setNormalFontSize(getDocument(), fontSize_ * zoomLevel_); 152 } 153 setFont(String font, boolean webFont)154 public void setFont(String font, boolean webFont) 155 { 156 final String STYLE_EL_ID = "__rstudio_font_family"; 157 final String LINK_EL_ID = "__rstudio_font_link"; 158 Document document = getDocument(); 159 160 Element oldStyle = document.getElementById(STYLE_EL_ID); 161 Element oldLink = document.getElementById(LINK_EL_ID); 162 163 if (webFont) 164 { 165 LinkElement link = document.createLinkElement(); 166 link.setRel("stylesheet"); 167 link.setHref("fonts/css/" + font + ".css"); 168 link.setId(LINK_EL_ID); 169 document.getHead().appendChild(link); 170 webFont_ = font; 171 } 172 173 StyleElement style = document.createStyleElement(); 174 style.setAttribute("type", "text/css"); 175 style.setInnerText(".ace_editor, .ace_text-layer {\n" + 176 "font-family: \"" + font + "\" !important;\n" + 177 "}"); 178 179 document.getBody().appendChild(style); 180 181 if (oldStyle != null) 182 oldStyle.removeFromParent(); 183 if (oldLink != null) 184 oldLink.removeFromParent(); 185 186 style.setId(STYLE_EL_ID); 187 } 188 setZoomLevel(double zoomLevel)189 public void setZoomLevel(double zoomLevel) 190 { 191 zoomLevel_ = zoomLevel; 192 if (!isFrameLoaded_) 193 return; 194 195 if (fontSize_ != null) 196 setFontSize(fontSize_); 197 } 198 199 private LinkElement currentStyleLink_; 200 private boolean isFrameLoaded_; 201 private String themeUrl_; 202 private String webFont_; 203 private Double fontSize_; 204 private Double zoomLevel_; 205 private final String code_; 206 207 public interface Resources extends ClientBundle 208 { 209 @Source("AceEditorPreview.js") loader()210 TextResource loader(); 211 } 212 213 private static Resources RES = GWT.create(Resources.class); 214 215 } 216