/* * NewRMarkdownDialog.java * * Copyright (C) 2009-14 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.workbench.views.source.editors.text.ui; import java.util.ArrayList; import java.util.List; import org.rstudio.core.client.StringUtil; import org.rstudio.core.client.files.FileSystemItem; import org.rstudio.core.client.resources.ImageResource2x; import org.rstudio.core.client.widget.ModalDialog; import org.rstudio.core.client.widget.OperationWithInput; import org.rstudio.core.client.widget.WidgetListBox; import org.rstudio.studio.client.common.HelpLink; import org.rstudio.studio.client.rmarkdown.model.RMarkdownContext; import org.rstudio.studio.client.rmarkdown.model.RMarkdownServerOperations; import org.rstudio.studio.client.rmarkdown.model.RmdChosenTemplate; import org.rstudio.studio.client.rmarkdown.model.RmdFrontMatter; import org.rstudio.studio.client.rmarkdown.model.RmdTemplate; import org.rstudio.studio.client.rmarkdown.model.RmdTemplateData; import org.rstudio.studio.client.rmarkdown.model.RmdTemplateFormat; import org.rstudio.studio.client.rmarkdown.ui.RmdTemplateChooser; import org.rstudio.studio.client.workbench.WorkbenchContext; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiFactory; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RadioButton; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; public class NewRMarkdownDialog extends ModalDialog<NewRMarkdownDialog.Result> { public static class RmdNewDocument { public RmdNewDocument(String template, String author, String title, String format, boolean isShiny) { template_ = template; title_ = title; author_ = author; isShiny_ = isShiny; format_ = format; result_ = toJSO(author, title, format, isShiny); } public String getTemplate() { return template_; } public String getAuthor() { return author_; } public String getTitle() { return title_; } public boolean isShiny() { return isShiny_; } public String getFormat() { return format_; } public JavaScriptObject getJSOResult() { return result_; } private final JavaScriptObject toJSO(String author, String title, String format, boolean isShiny) { RmdFrontMatter result = RmdFrontMatter.create(); result.applyCreateOptions(author, title, format, isShiny); return result; } private final String template_; private final String author_; private final String title_; private final boolean isShiny_; private final String format_; private final JavaScriptObject result_; } public static class Result { public Result (RmdNewDocument newDocument, RmdChosenTemplate fromTemplate, boolean isNewDocument) { newDocument_ = newDocument; fromTemplate_ = fromTemplate; isNewDocument_ = isNewDocument; } public RmdNewDocument getNewDocument() { return newDocument_; } public RmdChosenTemplate getFromTemplate() { return fromTemplate_; } public boolean isNewDocument() { return isNewDocument_; } private final RmdNewDocument newDocument_; private final RmdChosenTemplate fromTemplate_; private final boolean isNewDocument_; } public interface Binder extends UiBinder<Widget, NewRMarkdownDialog> { } public interface NewRmdStyle extends CssResource { String outputFormat(); String outputFormatName(); String outputFormatChoice(); String outputFormatDetails(); String outputFormatIcon(); } public interface Resources extends ClientBundle { @Source("MarkdownPresentationIcon_2x.png") ImageResource presentationIcon2x(); @Source("MarkdownDocumentIcon_2x.png") ImageResource documentIcon2x(); @Source("MarkdownOptionsIcon_2x.png") ImageResource optionsIcon2x(); @Source("MarkdownTemplateIcon_2x.png") ImageResource templateIcon2x(); @Source("MarkdownShinyIcon_2x.png") ImageResource shinyIcon2x(); } public NewRMarkdownDialog( RMarkdownServerOperations server, RMarkdownContext context, WorkbenchContext workbench, String author, OperationWithInput<Result> operation) { super("New R Markdown", operation); server_ = server; context_ = context; templateChooser_ = new RmdTemplateChooser(server_); mainWidget_ = GWT.<Binder>create(Binder.class).createAndBindUi(this); formatOptions_ = new ArrayList<RadioButton>(); style.ensureInjected(); txtAuthor_.setText(author); txtTitle_.setText("Untitled"); listTemplates_.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { updateOptions(getSelectedTemplate()); txtTitle_.setFocus(true); } }); templates_ = RmdTemplateData.getTemplates(); for (int i = 0; i < templates_.length(); i++) { String templateName = templates_.get(i).getName(); TemplateMenuItem menuItem = new TemplateMenuItem(templateName); ImageResource img = null; // Special treatment for built-in templates with known names if (templateName.equals(RmdTemplateData.DOCUMENT_TEMPLATE)) { img = new ImageResource2x(resources.documentIcon2x()); } else if (templateName.equals(RmdTemplateData.PRESENTATION_TEMPLATE)) { img = new ImageResource2x(resources.presentationIcon2x()); } else { // don't advertise if no icon continue; } // Add an image if we have one if (img != null) { menuItem.addIcon(img); } listTemplates_.addItem(menuItem); } // Add the Shiny template TemplateMenuItem shinyItem = new TemplateMenuItem(TEMPLATE_SHINY); shinyItem.addIcon(new ImageResource2x(resources.shinyIcon2x())); listTemplates_.addItem(shinyItem); // Add the "From Template" item at the end of the list TemplateMenuItem templateItem = new TemplateMenuItem(TEMPLATE_CHOOSE_EXISTING); templateItem.addIcon(new ImageResource2x(resources.templateIcon2x())); listTemplates_.addItem(templateItem); // Save templates to the current project directory if available, and the // current working directory if not FileSystemItem dir = workbench.getActiveProjectDir(); if (dir == null) dir = workbench.getCurrentWorkingDir(); templateChooser_.setTargetDirectory(dir.getPath()); updateOptions(getSelectedTemplate()); } @UiFactory public HelpLink makeHelpCaption() { return new HelpLink("Using Shiny with R Markdown", "using_rmarkdown_shiny"); } @Override protected void onDialogShown() { // when dialog is finished booting, focus the title so it's ready to // accept input super.onDialogShown(); txtTitle_.setSelectionRange(0, txtTitle_.getText().length()); txtTitle_.setFocus(true); } @Override protected Result collectInput() { String formatName = ""; boolean isShiny = getSelectedTemplate().equals(TEMPLATE_SHINY); for (int i = 0; i < formatOptions_.size(); i++) { if (formatOptions_.get(i).getValue()) { // for Shiny documents, manually choose the underlying format to // represent the document if (isShiny) { String option = formatOptions_.get(i).getText(); if (option.equals(SHINY_DOC_NAME)) formatName = "html_document"; else if (option.equals(SHINY_PRESENTATION_NAME)) formatName = "ioslides_presentation"; } // for other documents, read the format from the template else { formatName = currentTemplate_.getFormats().get(i).getName(); } break; } } return new Result( new RmdNewDocument(getSelectedTemplate(), txtAuthor_.getText().trim(), txtTitle_.getText().trim(), formatName, isShiny), templateChooser_.getChosenTemplate(), !getSelectedTemplate().equals(TEMPLATE_CHOOSE_EXISTING)); } @Override protected boolean validate(Result input) { // the dialog isn't valid if the user's chosen to create a document from // a template but hasn't chosen a template. if (input.isNewDocument() || input.getFromTemplate().getTemplatePath() != null) return true; return false; } @Override protected Widget createMainWidget() { return mainWidget_; } private String getSelectedTemplate() { int idx = listTemplates_.getSelectedIndex(); TemplateMenuItem item = listTemplates_.getItemAtIdx(idx); if (item.getName().equals(TEMPLATE_CHOOSE_EXISTING) || item.getName().equals(TEMPLATE_SHINY)) return item.getName(); else return templates_.get(idx).getName(); } private void updateOptions(String selectedTemplate) { boolean existing = selectedTemplate.equals(TEMPLATE_CHOOSE_EXISTING); boolean shiny = selectedTemplate.equals(TEMPLATE_SHINY); // toggle visibility of UI elements based on which section of the dialog // we're in newTemplatePanel_.setVisible(!existing); existingTemplatePanel_.setVisible(existing); shinyInfoPanel_.setVisible(shiny); if (existing) { if (templateChooser_.getState() == RmdTemplateChooser.STATE_EMPTY) { populateTemplates(); } return; } templateFormatPanel_.clear(); formatOptions_.clear(); if (shiny) { templateFormatPanel_.add(createFormatOption(SHINY_DOC_NAME, "Create an HTML document with interactive Shiny components.")); templateFormatPanel_.add(createFormatOption(SHINY_PRESENTATION_NAME, "Create an IOSlides presentation with interactive Shiny components.")); } else { currentTemplate_ = RmdTemplate.getTemplate(templates_, selectedTemplate); if (currentTemplate_ == null) return; // Add each format to the dialog JsArray<RmdTemplateFormat> formats = currentTemplate_.getFormats(); for (int i = 0; i < formats.length(); i++) { Widget option = createFormatOption(formats.get(i)); // hide if no notes if (StringUtil.isNullOrEmpty(formats.get(i).getNotes())) option.setVisible(false); templateFormatPanel_.add(option); } } // select the first visible format by default for (int i = 0; i < formatOptions_.size(); i++) { if (formatOptions_.get(i).getParent().isVisible()) { formatOptions_.get(i).setValue(true); break; } } } private void populateTemplates() { templateChooser_.populateTemplates(); } private Widget createFormatOption(RmdTemplateFormat format) { return createFormatOption(format.getUiName(), format.getNotes()); } private Widget createFormatOption(String name, String description) { HTMLPanel formatWrapper = new HTMLPanel(""); formatWrapper.setStyleName(style.outputFormat()); SafeHtmlBuilder sb = new SafeHtmlBuilder(); sb.appendHtmlConstant("<span class=\"" + style.outputFormatName() + "\">"); sb.appendEscaped(name); sb.appendHtmlConstant("</span>"); RadioButton button = new RadioButton("DefaultOutputFormat", sb.toSafeHtml().asString(), true); button.addStyleName(style.outputFormatChoice()); formatOptions_.add(button); formatWrapper.add(button); Label label = new Label(description); label.setStyleName(style.outputFormatDetails()); formatWrapper.add(label); return formatWrapper; } @UiField TextBox txtAuthor_; @UiField TextBox txtTitle_; @UiField WidgetListBox<TemplateMenuItem> listTemplates_; @UiField NewRmdStyle style; @UiField Resources resources; @UiField HTMLPanel templateFormatPanel_; @UiField HTMLPanel newTemplatePanel_; @UiField HTMLPanel existingTemplatePanel_; @UiField(provided=true) RmdTemplateChooser templateChooser_; @UiField HTMLPanel shinyInfoPanel_; @UiField Label outputFormatLabel_; private final Widget mainWidget_; private List<RadioButton> formatOptions_; private JsArray<RmdTemplate> templates_; private RmdTemplate currentTemplate_; @SuppressWarnings("unused") private final RMarkdownContext context_; private final RMarkdownServerOperations server_; private final static String TEMPLATE_CHOOSE_EXISTING = "From Template"; private final static String TEMPLATE_SHINY = "Shiny"; private final static String SHINY_DOC_NAME = "Shiny Document"; private final static String SHINY_PRESENTATION_NAME = "Shiny Presentation"; }