Java tutorial
/* * 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; }