org.rstudio.studio.client.workbench.views.plots.Plots.java Source code

Java tutorial

Introduction

Here is the source code for org.rstudio.studio.client.workbench.views.plots.Plots.java

Source

/*
 * Plots.java
 *
 * Copyright (C) 2009-12 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.plots;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.HasResizeHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Panel;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.CommandWithArg;
import org.rstudio.core.client.Point;
import org.rstudio.core.client.Size;
import org.rstudio.core.client.dom.WindowEx;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.widget.HasCustomizableToolbar;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.core.client.widget.ProgressOperation;
import org.rstudio.studio.client.application.events.DeferredInitCompletedEvent;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.dependencies.DependencyManager;
import org.rstudio.studio.client.common.rpubs.RPubsHtmlGenerator;
import org.rstudio.studio.client.common.rpubs.ui.RPubsUploadDialog;
import org.rstudio.studio.client.common.zoom.ZoomUtils;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.server.Void;
import org.rstudio.studio.client.server.VoidServerRequestCallback;
import org.rstudio.studio.client.workbench.WorkbenchContext;
import org.rstudio.studio.client.workbench.WorkbenchView;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.exportplot.ExportPlotUtils;
import org.rstudio.studio.client.workbench.exportplot.model.ExportPlotOptions;
import org.rstudio.studio.client.workbench.exportplot.model.SavePlotAsImageContext;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.views.BasePresenter;
import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
import org.rstudio.studio.client.workbench.views.plots.events.LocatorEvent;
import org.rstudio.studio.client.workbench.views.plots.events.LocatorHandler;
import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedEvent;
import org.rstudio.studio.client.workbench.views.plots.events.PlotsChangedHandler;
import org.rstudio.studio.client.workbench.views.plots.events.PlotsZoomSizeChangedEvent;
import org.rstudio.studio.client.workbench.views.plots.model.PlotsServerOperations;
import org.rstudio.studio.client.workbench.views.plots.model.PlotsState;
import org.rstudio.studio.client.workbench.views.plots.model.SavePlotAsPdfOptions;
import org.rstudio.studio.client.workbench.views.plots.ui.export.ExportPlot;
import org.rstudio.studio.client.workbench.views.plots.ui.manipulator.ManipulatorChangedHandler;
import org.rstudio.studio.client.workbench.views.plots.ui.manipulator.ManipulatorManager;

public class Plots extends BasePresenter implements PlotsChangedHandler, LocatorHandler, ConsolePromptHandler,
        DeferredInitCompletedEvent.Handler, PlotsZoomSizeChangedEvent.Handler {
    public interface Parent extends HasWidgets, HasCustomizableToolbar {
    }

    public interface Display extends WorkbenchView, HasResizeHandlers {
        void showEmptyPlot();

        void showPlot(String plotUrl);

        String getPlotUrl();

        void refresh();

        Panel getPlotsSurface();

        Parent getPlotsParent();

        Size getPlotFrameSize();
    }

    @Inject
    public Plots(final Display view, GlobalDisplay globalDisplay, WorkbenchContext workbenchContext,
            Provider<UIPrefs> uiPrefs, Commands commands, EventBus events, DependencyManager dependencyManager,
            final PlotsServerOperations server, Session session) {
        super(view);
        view_ = view;
        globalDisplay_ = globalDisplay;
        workbenchContext_ = workbenchContext;
        uiPrefs_ = uiPrefs;
        server_ = server;
        session_ = session;
        dependencyManager_ = dependencyManager;
        exportPlot_ = GWT.create(ExportPlot.class);
        zoomWindow_ = null;
        zoomWindowDefaultSize_ = null;

        locator_ = new Locator(view.getPlotsParent());
        locator_.addSelectionHandler(new SelectionHandler<Point>() {
            public void onSelection(SelectionEvent<Point> e) {
                org.rstudio.studio.client.workbench.views.plots.model.Point p = null;
                if (e.getSelectedItem() != null)
                    p = org.rstudio.studio.client.workbench.views.plots.model.Point
                            .create(e.getSelectedItem().getX(), e.getSelectedItem().getY());
                server.locatorCompleted(p, new SimpleRequestCallback<Void>());
            }
        });

        // manipulator
        manipulatorManager_ = new ManipulatorManager(view_.getPlotsSurface(), commands,

                new ManipulatorChangedHandler() {
                    @Override
                    public void onManipulatorChanged(JSONObject values) {
                        server_.setManipulatorValues(values, new ManipulatorRequestCallback());
                    }
                },

                new ClickHandler() {
                    @Override
                    public void onClick(ClickEvent event) {
                        server_.manipulatorPlotClicked(new Double(event.getX()).intValue(),
                                new Double(event.getY()).intValue(), new ManipulatorRequestCallback());

                    }
                });

        events.addHandler(DeferredInitCompletedEvent.TYPE, this);
        events.addHandler(PlotsZoomSizeChangedEvent.TYPE, this);
    }

    public void onPlotsChanged(PlotsChangedEvent event) {
        // get the event
        PlotsState plotsState = event.getPlotsState();

        // clear progress 
        view_.setProgress(false);
        manipulatorManager_.setProgress(false);

        // if this is the empty plot then clear the display
        // NOTE: we currently return a zero byte PNG as our "empty.png" from
        // the server. this is shown as a blank pane by Webkit, however
        // firefox shows the full URI of the empty.png rather than a blank
        // pane. therefore, we put in this workaround. 
        if (plotsState.getFilename().startsWith("empty.")) {
            view_.showEmptyPlot();
        } else {
            String url = server_.getGraphicsUrl(plotsState.getFilename());
            view_.showPlot(url);
        }

        // activate the plots tab if requested
        if (plotsState.getActivatePlots())
            view_.bringToFront();

        // update plot size
        plotSize_ = new Size(plotsState.getWidth(), plotsState.getHeight());

        // manipulator
        manipulatorManager_.setManipulator(plotsState.getManipulator(), plotsState.getShowManipulator());

        // locator
        if (locator_.isActive())
            locate();

        // reload zoom window if we have one
        if (Desktop.isDesktop())
            Desktop.getFrame().reloadZoomWindow();
        else if ((zoomWindow_ != null) && !zoomWindow_.isClosed())
            zoomWindow_.reload();
    }

    void onNextPlot() {
        view_.bringToFront();
        setChangePlotProgress();
        server_.nextPlot(new PlotRequestCallback());
    }

    void onPreviousPlot() {
        view_.bringToFront();
        setChangePlotProgress();
        server_.previousPlot(new PlotRequestCallback());
    }

    void onRemovePlot() {
        // delete plot gesture indicates we are done with locator
        safeClearLocator();

        // confirm
        globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,

                "Remove Plot",

                "Are you sure you want to remove the current plot?",

                new ProgressOperation() {
                    public void execute(final ProgressIndicator indicator) {
                        indicator.onProgress("Removing plot...");
                        server_.removePlot(new VoidServerRequestCallback(indicator));
                    }
                },

                true

        );

        view_.bringToFront();
    }

    void onClearPlots() {
        // clear plots gesture indicates we are done with locator
        safeClearLocator();

        // confirm
        globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,

                "Clear Plots",

                "Are you sure you want to clear all of the plots in the history?",

                new ProgressOperation() {
                    public void execute(final ProgressIndicator indicator) {
                        indicator.onProgress("Clearing plots...");
                        server_.clearPlots(new VoidServerRequestCallback(indicator));
                    }
                },

                true

        );
    }

    void onSavePlotAsImage() {
        view_.bringToFront();

        final ProgressIndicator indicator = globalDisplay_.getProgressIndicator("Error");
        indicator.onProgress("Preparing to export plot...");

        // get the default directory
        FileSystemItem defaultDir = ExportPlotUtils
                .getDefaultSaveDirectory(workbenchContext_.getCurrentWorkingDir());

        // get context
        server_.getSavePlotContext(defaultDir.getPath(), new SimpleRequestCallback<SavePlotAsImageContext>() {

            @Override
            public void onResponseReceived(SavePlotAsImageContext context) {
                indicator.onCompleted();

                exportPlot_.savePlotAsImage(globalDisplay_, server_, context,
                        ExportPlotOptions.adaptToSize(uiPrefs_.get().exportPlotOptions().getValue(), getPlotSize()),
                        saveExportOptionsOperation_);
            }

            @Override
            public void onError(ServerError error) {
                indicator.onError(error.getUserMessage());
            }
        });
    }

    void onSavePlotAsPdf() {
        view_.bringToFront();

        final ProgressIndicator indicator = globalDisplay_.getProgressIndicator("Error");
        indicator.onProgress("Preparing to export plot...");

        // get the default directory
        final FileSystemItem defaultDir = ExportPlotUtils
                .getDefaultSaveDirectory(workbenchContext_.getCurrentWorkingDir());

        // get context
        server_.getUniqueSavePlotStem(defaultDir.getPath(), new SimpleRequestCallback<String>() {

            @Override
            public void onResponseReceived(String stem) {
                indicator.onCompleted();

                Size size = getPlotSize();
                final SavePlotAsPdfOptions currentOptions = SavePlotAsPdfOptions.adaptToSize(
                        uiPrefs_.get().savePlotAsPdfOptions().getValue(), pixelsToInches(size.width),
                        pixelsToInches(size.height));

                exportPlot_.savePlotAsPdf(globalDisplay_, server_, session_.getSessionInfo(), defaultDir, stem,
                        currentOptions, new OperationWithInput<SavePlotAsPdfOptions>() {
                            @Override
                            public void execute(SavePlotAsPdfOptions options) {
                                if (!SavePlotAsPdfOptions.areEqual(options, currentOptions)) {
                                    UIPrefs prefs = uiPrefs_.get();
                                    prefs.savePlotAsPdfOptions().setGlobalValue(options);
                                    prefs.writeUIPrefs();
                                }
                            }
                        });
            }

            @Override
            public void onError(ServerError error) {
                indicator.onError(error.getUserMessage());
            }
        });
    }

    void onCopyPlotToClipboard() {
        view_.bringToFront();

        exportPlot_.copyPlotToClipboard(server_,
                ExportPlotOptions.adaptToSize(uiPrefs_.get().exportPlotOptions().getValue(), getPlotSize()),
                saveExportOptionsOperation_);
    }

    void onPublishPlotToRPubs() {
        dependencyManager_.withRMarkdown("Publishing to RPubs", new Command() {
            @Override
            public void execute() {
                // determine the size (re-use the zoom window logic for this)
                final Size size = ZoomUtils.getZoomedSize(view_.getPlotFrameSize(), new Size(400, 350),
                        new Size(750, 600));

                // show the dialog
                RPubsUploadDialog dlg = new RPubsUploadDialog("Plots", "Plot", new RPubsHtmlGenerator() {

                    @Override
                    public void generateRPubsHtml(String title, String comment,
                            final CommandWithArg<String> onCompleted) {
                        server_.plotsCreateRPubsHtml(title, comment, size.width, size.height,
                                new SimpleRequestCallback<String>() {

                                    @Override
                                    public void onResponseReceived(String rpubsHtmlFile) {
                                        onCompleted.execute(rpubsHtmlFile);
                                    }
                                });
                    }
                }, false);
                dlg.showModal();
            }
        });
    }

    private double pixelsToInches(int pixels) {
        return (double) pixels / 96.0;
    }

    private OperationWithInput<ExportPlotOptions> saveExportOptionsOperation_ = new OperationWithInput<ExportPlotOptions>() {
        public void execute(ExportPlotOptions options) {
            UIPrefs uiPrefs = uiPrefs_.get();
            if (!ExportPlotOptions.areEqual(options, uiPrefs.exportPlotOptions().getValue())) {
                uiPrefs.exportPlotOptions().setGlobalValue(options);
                uiPrefs.writeUIPrefs();
            }
        }
    };

    void onZoomPlot() {
        Size windowSize = ZoomUtils.getZoomWindowSize(view_.getPlotFrameSize(), zoomWindowDefaultSize_);

        // determine whether we should scale (see comment in ImageFrame.onLoad
        // for why we wouldn't want to scale)
        int scale = 1;
        if (Desktop.isDesktop() && BrowseCap.isMacintosh())
            scale = 0;

        // compose url string
        String url = server_.getGraphicsUrl("plot_zoom?" + "width=" + windowSize.width + "&" + "height="
                + windowSize.height + "&" + "scale=" + scale);

        // open the window
        ZoomUtils.openZoomWindow("_rstudio_zoom", url, windowSize, new OperationWithInput<WindowEx>() {
            @Override
            public void execute(WindowEx input) {
                zoomWindow_ = input;
            }
        });
    }

    void onRefreshPlot() {
        view_.bringToFront();
        view_.setProgress(true);
        server_.refreshPlot(new PlotRequestCallback());
    }

    @Override
    public void onDeferredInitCompleted(DeferredInitCompletedEvent event) {
        server_.refreshPlot(new PlotRequestCallback(false));
    }

    void onShowManipulator() {
        manipulatorManager_.showManipulator();
    }

    public Display getView() {
        return view_;
    }

    private void safeClearLocator() {
        if (locator_.isActive()) {
            server_.locatorCompleted(null, new SimpleRequestCallback<Void>() {
                @Override
                public void onError(ServerError error) {
                    // ignore errors (this method is meant to be used "quietly"
                    // so that if the server has a problem with clearing
                    // locator state (e.g. because it has already exited the
                    // locator state) we don't bother the user with it. worst
                    // case if this fails then the user will see that the console
                    // is still pending the locator command and the Done and Esc
                    // gestures will still be available to clear the Locator
                }
            });
        }
    }

    private void setChangePlotProgress() {
        if (!Desktop.isDesktop())
            view_.setProgress(true);
    }

    private class PlotRequestCallback extends ServerRequestCallback<Void> {
        public PlotRequestCallback() {
            this(true);
        }

        public PlotRequestCallback(boolean showErrors) {
            showErrors_ = showErrors;
        }

        @Override
        public void onResponseReceived(Void response) {
            // we don't clear the progress until the GraphicsOutput
            // event is received (enables us to wait for rendering
            // to complete before clearing progress)
        }

        @Override
        public void onError(ServerError error) {
            view_.setProgress(false);

            if (showErrors_) {
                globalDisplay_.showErrorMessage("Server Error", error.getUserMessage());
            }
        }

        private final boolean showErrors_;
    }

    public void onLocator(LocatorEvent event) {
        view_.bringToFront();
        locate();
    }

    private void locate() {
        locator_.locate(view_.getPlotUrl(), getPlotSize());
    }

    public void onConsolePrompt(ConsolePromptEvent event) {
        locator_.clearDisplay();
    }

    @Override
    public void onPlotsZoomSizeChanged(PlotsZoomSizeChangedEvent event) {
        zoomWindowDefaultSize_ = new Size(event.getWidth(), event.getHeight());
    }

    private Size getPlotSize() {
        // NOTE: the reason we capture the plotSize_ from the PlotChangedEvent
        // is that the server can actually change the size of the plot
        // (e.g. for CairoSVG the width and height must be multiples of 4)
        // in order for locator to work properly we need to use this size 
        // rather than size of our current plot frame

        if (plotSize_ != null) // first try to use the last size reported
            return plotSize_;
        else // then fallback to frame size
            return view_.getPlotFrameSize();
    }

    private class ManipulatorRequestCallback extends ServerRequestCallback<Void> {
        public ManipulatorRequestCallback() {
            manipulatorManager_.setProgress(true);
        }

        @Override
        public void onResponseReceived(Void response) {
            // we don't clear the progress until the GraphicsOutput
            // event is received (enables us to wait for rendering
            // to complete before clearing progress)
        }

        @Override
        public void onError(ServerError error) {
            manipulatorManager_.setProgress(false);
            globalDisplay_.showErrorMessage("Server Error", error.getUserMessage());

        }

    }

    private final Display view_;
    private final GlobalDisplay globalDisplay_;
    private final PlotsServerOperations server_;
    private final WorkbenchContext workbenchContext_;
    private final DependencyManager dependencyManager_;
    private final Session session_;
    private final Provider<UIPrefs> uiPrefs_;
    private final Locator locator_;
    private final ManipulatorManager manipulatorManager_;
    private WindowEx zoomWindow_;
    private Size zoomWindowDefaultSize_;

    // export plot impl
    private final ExportPlot exportPlot_;

    // size of most recently rendered plot
    Size plotSize_ = null;
}