org.rstudio.studio.client.rmarkdown.ui.RmdTemplateOptionsWidget.java Source code

Java tutorial

Introduction

Here is the source code for org.rstudio.studio.client.rmarkdown.ui.RmdTemplateOptionsWidget.java

Source

/*
 * RmdTemplateOptionsWidget.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.rmarkdown.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.rstudio.core.client.JsArrayUtil;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.studio.client.common.FilePathUtils;
import org.rstudio.studio.client.rmarkdown.model.RmdFrontMatter;
import org.rstudio.studio.client.rmarkdown.model.RmdFrontMatterOutputOptions;
import org.rstudio.studio.client.rmarkdown.model.RmdTemplate;
import org.rstudio.studio.client.rmarkdown.model.RmdTemplateFormat;
import org.rstudio.studio.client.rmarkdown.model.RmdTemplateFormatOption;

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.core.client.JsArrayString;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TabLayoutPanel;
import com.google.gwt.user.client.ui.Widget;

public class RmdTemplateOptionsWidget extends Composite {

    private static RmdTemplateOptionsWidgetUiBinder uiBinder = GWT.create(RmdTemplateOptionsWidgetUiBinder.class);

    interface RmdTemplateOptionsWidgetUiBinder extends UiBinder<Widget, RmdTemplateOptionsWidget> {
    }

    interface OptionsStyle extends CssResource {
        String optionWidget();
    }

    public RmdTemplateOptionsWidget(boolean allowFormatChange) {
        initWidget(uiBinder.createAndBindUi(this));
        style.ensureInjected();
        allowFormatChange_ = allowFormatChange;
        if (allowFormatChange) {
            listFormats_.addChangeHandler(new ChangeHandler() {
                @Override
                public void onChange(ChangeEvent event) {
                    updateFormatOptions(getSelectedFormat());
                }
            });
        } else {
            listFormats_.setVisible(false);
            listFormats_.setEnabled(false);
            labelFormatNotes_.setVisible(false);
            labelFormatName_.setVisible(true);
        }
    }

    public void setTemplate(RmdTemplate template, boolean forCreate) {
        setTemplate(template, forCreate, null);
    }

    public void setTemplate(RmdTemplate template, boolean forCreate, RmdFrontMatter frontMatter) {
        template_ = template;
        formats_ = template.getFormats();
        options_ = template.getOptions();
        if (frontMatter != null)
            applyFrontMatter(frontMatter);
        listFormats_.clear();
        for (int i = 0; i < formats_.length(); i++) {
            listFormats_.addItem(formats_.get(i).getUiName(), formats_.get(i).getName());
        }
        updateFormatOptions(getSelectedFormat());
    }

    public void setDocument(FileSystemItem document) {
        document_ = document;
    }

    public String getSelectedFormat() {
        return listFormats_.getValue(listFormats_.getSelectedIndex());
    }

    // Returns a modified version of the front matter, with the current set
    // of options applied.
    public RmdFrontMatter getFrontMatter() {
        if (frontMatter_ == null)
            return null;
        frontMatter_.setOutputOption(getSelectedFormat(), RmdFormatOptionsHelper.optionsListToJson(optionWidgets_,
                document_, frontMatter_.getOutputOption(getSelectedFormat())));
        return frontMatter_;
    }

    public void setSelectedFormat(String format) {
        if (allowFormatChange_) {
            for (int i = 0; i < listFormats_.getItemCount(); i++) {
                if (listFormats_.getValue(i).equals(format)) {
                    listFormats_.setSelectedIndex(i);
                    updateFormatOptions(format);
                }
            }
        } else {
            RmdTemplateFormat selFormat = template_.getFormat(format);
            if (selFormat != null)
                labelFormatName_.setText("Shiny " + selFormat.getUiName());
        }
    }

    public JavaScriptObject getOptionsJSON() {
        return RmdFormatOptionsHelper.optionsListToJson(optionWidgets_, document_,
                frontMatter_ == null ? null : frontMatter_.getOutputOption(getSelectedFormat()));
    }

    private void updateFormatOptions(String format) {
        tabs_ = new HashMap<String, FlowPanel>();
        optionsTabs_.clear();
        for (int i = 0; i < formats_.length(); i++) {
            if (formats_.get(i).getName().equals(format)) {
                addFormatOptions(formats_.get(i));
                break;
            }
        }
    }

    private void addFormatOptions(RmdTemplateFormat format) {
        if (format.getNotes().length() > 0 && allowFormatChange_) {
            labelFormatNotes_.setText(format.getNotes());
            labelFormatNotes_.setVisible(true);
        } else {
            labelFormatNotes_.setVisible(false);
        }
        optionWidgets_ = new ArrayList<RmdFormatOption>();
        JsArrayString options = format.getOptions();
        for (int i = 0; i < options.length(); i++) {
            RmdFormatOption optionWidget;
            RmdTemplateFormatOption option = findOption(format.getName(), options.get(i));
            if (option == null)
                continue;

            String initialValue = option.getDefaultValue();

            // check to see whether a value for this format and option were
            // specified in the front matter
            String frontMatterValue = getFrontMatterDefault(format.getName(), option.getName());
            if (frontMatterValue != null)
                initialValue = frontMatterValue;

            optionWidget = createWidgetForOption(option, initialValue);
            if (optionWidget == null)
                continue;

            optionWidget.asWidget().addStyleName(style.optionWidget());

            FlowPanel panel = null;
            String category = option.getCategory();
            if (tabs_.containsKey(category)) {
                panel = tabs_.get(category);
            } else {
                ScrollPanel scrollPanel = new ScrollPanel();
                panel = new FlowPanel();
                scrollPanel.add(panel);
                optionsTabs_.add(scrollPanel, new Label(category));
                tabs_.put(category, panel);
            }

            panel.add(optionWidget);
            optionWidgets_.add(optionWidget);
        }

        // we need to center the tabs and overlay them on the top edge of the
        // content; to do this, it is necessary to nuke a couple of the inline
        // styles used by the default GWT tab panel. 
        Element tabOuter = (Element) optionsTabs_.getElement().getChild(1);
        tabOuter.getStyle().setOverflow(Overflow.VISIBLE);
        Element tabInner = (Element) tabOuter.getFirstChild();
        tabInner.getStyle().clearWidth();
    }

    private RmdFormatOption createWidgetForOption(RmdTemplateFormatOption option, String initialValue) {
        RmdFormatOption optionWidget = null;
        if (option.getType().equals(RmdTemplateFormatOption.TYPE_BOOLEAN)) {
            optionWidget = new RmdBooleanOption(option, initialValue);
        } else if (option.getType().equals(RmdTemplateFormatOption.TYPE_CHOICE)) {
            optionWidget = new RmdChoiceOption(option, initialValue);
        } else if (option.getType().equals(RmdTemplateFormatOption.TYPE_STRING)) {
            optionWidget = new RmdStringOption(option, initialValue);
        } else if (option.getType().equals(RmdTemplateFormatOption.TYPE_FLOAT)
                || option.getType().equals(RmdTemplateFormatOption.TYPE_INTEGER)) {
            optionWidget = new RmdFloatOption(option, initialValue);
        } else if (option.getType().equals(RmdTemplateFormatOption.TYPE_FILE)) {
            // if we have a document and a relative path, resolve the path
            // relative to the document
            if (document_ != null && !initialValue.equals("null") && FilePathUtils.pathIsRelative(initialValue)) {
                initialValue = document_.getParentPath().completePath(initialValue);
            }
            optionWidget = new RmdFileOption(option, initialValue);
        }
        return optionWidget;
    }

    private RmdTemplateFormatOption findOption(String formatName, String optionName) {
        RmdTemplateFormatOption result = null;
        for (int i = 0; i < options_.length(); i++) {
            RmdTemplateFormatOption option = options_.get(i);

            // Not the option we're looking for 
            if (!option.getName().equals(optionName))
                continue;

            String optionFormatName = option.getFormatName();
            if (optionFormatName.length() > 0) {
                // A format-specific option: if it's for this format we're done,
                // otherwise keep looking
                if (optionFormatName.equals(formatName))
                    return option;
                else
                    continue;
            }

            result = option;
        }
        return result;
    }

    private void applyFrontMatter(RmdFrontMatter frontMatter) {
        frontMatter_ = frontMatter;
        frontMatterCache_ = new HashMap<String, String>();
        ensureOptionsCache();
        JsArrayString formats = frontMatter.getFormatList();
        for (int i = 0; i < formats.length(); i++) {
            String format = formats.get(i);
            RmdFrontMatterOutputOptions options = frontMatter.getOutputOption(format);
            JsArrayString optionList = options.getOptionList();
            for (int j = 0; j < optionList.length(); j++) {
                String option = optionList.get(j);
                String value = options.getOptionValue(option);
                frontMatterCache_.put(format + ":" + option, value);
                if (optionCache_.containsKey(option)) {
                    // If the option is specifically labeled as transferable
                    // between formats, add a generic key to be applied to other
                    // formats
                    RmdTemplateFormatOption formatOption = optionCache_.get(option);
                    if (formatOption.isTransferable()) {
                        frontMatterCache_.put(option, value);
                    }
                }
            }
        }
    }

    private String getFrontMatterDefault(String formatName, String optionName) {
        // if we have no front matter, we have no default
        if (frontMatterCache_ == null)
            return null;

        // is this value defined in the front matter?
        String key = formatName + ":" + optionName;
        if (frontMatterCache_.containsKey(key))
            return frontMatterCache_.get(key);
        else {
            // is this value transferable from a format defined in the front
            // matter? (don't transfer options into formats explicitly defined
            // in the front matter)
            JsArrayString frontMatterFormats = frontMatter_.getFormatList();
            if ((!JsArrayUtil.jsArrayStringContains(frontMatterFormats, formatName))
                    && frontMatterCache_.containsKey(optionName)) {
                return frontMatterCache_.get(optionName);
            }
        }
        return null;
    }

    private void ensureOptionsCache() {
        if (optionCache_ != null)
            return;
        optionCache_ = new HashMap<String, RmdTemplateFormatOption>();
        for (int i = 0; i < options_.length(); i++) {
            RmdTemplateFormatOption option = options_.get(i);
            if (option.getFormatName().length() > 0)
                continue;
            optionCache_.put(option.getName(), option);
        }
    }

    private RmdTemplate template_;
    private JsArray<RmdTemplateFormat> formats_;
    private JsArray<RmdTemplateFormatOption> options_;
    private List<RmdFormatOption> optionWidgets_;
    private RmdFrontMatter frontMatter_;
    private FileSystemItem document_;
    private boolean allowFormatChange_;

    // Cache of options present in the template (ignores those options that 
    // are specifically marked for a format)
    private Map<String, RmdTemplateFormatOption> optionCache_;

    // Cache of values set in the front matter, e.g.:
    // "html_document:fig_width" => "7.5"
    // In the case of options that are marked as transferable, maps directly
    // from an option name to its default, e.g.
    // "toc" => "true"
    private Map<String, String> frontMatterCache_;

    private Map<String, FlowPanel> tabs_;

    @UiField
    ListBox listFormats_;
    @UiField
    Label labelFormatNotes_;
    @UiField
    Label labelFormatName_;
    @UiField
    TabLayoutPanel optionsTabs_;
    @UiField
    OptionsStyle style;
}