org.rstudio.studio.client.workbench.views.presentation.Presentation.java Source code

Java tutorial

Introduction

Here is the source code for org.rstudio.studio.client.workbench.views.presentation.Presentation.java

Source

/*
 * Presentation.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.presentation;

import java.util.Iterator;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.inject.Inject;

import org.rstudio.core.client.Barrier;
import org.rstudio.core.client.MessageDisplay;
import org.rstudio.core.client.Size;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.TimeBufferedCommand;
import org.rstudio.core.client.Barrier.Token;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.events.BarrierReleasedEvent;
import org.rstudio.core.client.events.BarrierReleasedHandler;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.widget.MessageDialog;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.core.client.widget.ProgressOperation;
import org.rstudio.core.client.widget.ProgressOperationWithInput;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.events.ReloadEvent;
import org.rstudio.studio.client.common.FileDialogs;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.GlobalProgressDelayer;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.common.presentation.SlideNavigationMenu;
import org.rstudio.studio.client.common.presentation.SlideNavigationPresenter;
import org.rstudio.studio.client.common.presentation.events.SlideIndexChangedEvent;
import org.rstudio.studio.client.common.presentation.events.SlideNavigationChangedEvent;
import org.rstudio.studio.client.common.presentation.model.SlideNavigation;
import org.rstudio.studio.client.common.rpubs.ui.RPubsUploadDialog;

import org.rstudio.studio.client.server.Void;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.server.VoidServerRequestCallback;
import org.rstudio.studio.client.workbench.WorkbenchView;

import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.views.BasePresenter;
import org.rstudio.studio.client.workbench.views.edit.ui.EditDialog;
import org.rstudio.studio.client.workbench.views.presentation.events.PresentationPaneRequestCompletedEvent;
import org.rstudio.studio.client.workbench.views.presentation.events.ShowPresentationPaneEvent;
import org.rstudio.studio.client.workbench.views.presentation.events.SourceFileSaveCompletedEvent;
import org.rstudio.studio.client.workbench.views.presentation.model.PresentationRPubsSource;
import org.rstudio.studio.client.workbench.views.presentation.model.PresentationServerOperations;
import org.rstudio.studio.client.workbench.views.presentation.model.PresentationState;
import org.rstudio.studio.client.workbench.views.source.events.EditPresentationSourceEvent;

public class Presentation extends BasePresenter implements SlideNavigationPresenter.Display {
    public interface Binder extends CommandBinder<Commands, Presentation> {
    }

    public interface Display extends WorkbenchView {
        void load(String url);

        void zoom(String title, String url, Command onClosed);

        void clear();

        boolean hasSlides();

        void home();

        void navigate(int index);

        void next();

        void prev();

        SlideNavigationMenu getNavigationMenu();

        void pauseMedia();

        String getPresentationTitle();

        void showBusy();

        void hideBusy();
    }

    @Inject
    public Presentation(Display display, PresentationServerOperations server, GlobalDisplay globalDisplay,
            FileDialogs fileDialogs, RemoteFileSystemContext fileSystemContext, EventBus eventBus,
            FileTypeRegistry fileTypeRegistry, Session session, Binder binder, Commands commands,
            PresentationDispatcher dispatcher) {
        super(display);
        view_ = display;
        server_ = server;
        globalDisplay_ = globalDisplay;
        fileDialogs_ = fileDialogs;
        fileSystemContext_ = fileSystemContext;
        eventBus_ = eventBus;
        commands_ = commands;
        fileTypeRegistry_ = fileTypeRegistry;
        session_ = session;
        dispatcher_ = dispatcher;
        dispatcher_.setContext(new PresentationDispatcher.Context() {
            @Override
            public void pauseMedia() {
                view_.pauseMedia();
            }

            @Override
            public String getPresentationFilePath() {
                return currentState_.getFilePath();
            }
        });
        navigationPresenter_ = new SlideNavigationPresenter(this);

        binder.bind(commands, this);

        // auto-refresh for presentation files saved
        eventBus.addHandler(SourceFileSaveCompletedEvent.TYPE, new SourceFileSaveCompletedEvent.Handler() {
            @Override
            public void onSourceFileSaveCompleted(SourceFileSaveCompletedEvent event) {
                if (currentState_ != null) {
                    FileSystemItem file = event.getSourceFile();
                    if (file.getPath().equals(currentState_.getFilePath())) {
                        int index = detectSlideIndex(event.getContents(), event.getCursorPos().getRow());
                        if (index != -1)
                            currentState_.setSlideIndex(index);

                        refreshPresentation();
                    } else if (file.getParentPathString().equals(getCurrentPresDir())
                            && file.getExtension().toLowerCase().equals(".css")) {
                        refreshPresentation();
                    }
                }
            }
        });

        eventBus.addHandler(PresentationPaneRequestCompletedEvent.TYPE,
                new PresentationPaneRequestCompletedEvent.Handler() {
                    @Override
                    public void onPresentationRequestCompleted(PresentationPaneRequestCompletedEvent event) {
                        view_.hideBusy();
                    }
                });

        initPresentationCallbacks();
    }

    public void initialize(PresentationState state) {
        if ((state.getSlideIndex() == 0) || state.isTutorial())
            view_.bringToFront();

        init(state);
    }

    public void onShowPresentationPane(ShowPresentationPaneEvent event) {
        globalDisplay_.showProgress("Opening Presentation...");
        reloadWorkbench();
    }

    @Override
    public void editCurrentSlide() {
        eventBus_.fireEvent(new EditPresentationSourceEvent(FileSystemItem.createFile(currentState_.getFilePath()),
                currentState_.getSlideIndex()));
    }

    @Handler
    void onPresentationNext() {
        view_.next();
    }

    @Handler
    void onPresentationPrev() {
        view_.prev();
    }

    @Handler
    void onPresentationFullscreen() {
        // clear the internal iframe so there is no conflict over handling
        // presentation events (we'll restore it on zoom close)
        view_.clear();

        // show the zoomed version of the presentation. after it closes
        // restore the inline version
        view_.zoom(session_.getSessionInfo().getPresentationName(), buildPresentationUrl("zoom"), new Command() {
            @Override
            public void execute() {
                view_.load(buildPresentationUrl());
            }
        });
    }

    @Handler
    void onPresentationViewInBrowser() {
        if (Desktop.isDesktop()) {
            server_.createDesktopViewInBrowserPresentation(new SimpleRequestCallback<String>() {
                @Override
                public void onResponseReceived(String path) {
                    Desktop.getFrame().showFile(path);
                }
            });
        } else {
            globalDisplay_.openWindow(server_.getApplicationURL("presentation/view"));
        }
    }

    @Handler
    void onPresentationSaveAsStandalone() {
        // determine the default file name
        if (saveAsStandaloneDefaultPath_ == null) {
            FileSystemItem presFilePath = FileSystemItem.createFile(currentState_.getFilePath());
            saveAsStandaloneDefaultPath_ = FileSystemItem
                    .createFile(presFilePath.getParentPath().completePath(presFilePath.getStem() + ".html"));
        }

        fileDialogs_.saveFile("Save Presentation As", fileSystemContext_, saveAsStandaloneDefaultPath_, ".html",
                false, new ProgressOperationWithInput<FileSystemItem>() {

                    @Override
                    public void execute(final FileSystemItem targetFile, ProgressIndicator indicator) {
                        if (targetFile == null) {
                            indicator.onCompleted();
                            return;
                        }

                        indicator.onProgress("Saving Presentation...");

                        server_.createStandalonePresentation(targetFile.getPath(),
                                new VoidServerRequestCallback(indicator) {
                                    @Override
                                    public void onSuccess() {
                                        saveAsStandaloneDefaultPath_ = targetFile;
                                    }
                                });
                    }
                });
    }

    private void saveAsStandalone(String targetFile, final ProgressIndicator indicator, final Command onSuccess) {
        server_.createStandalonePresentation(targetFile, new VoidServerRequestCallback(indicator) {
            @Override
            public void onSuccess() {
                onSuccess.execute();
            }
        });
    }

    @Handler
    void onPresentationPublishToRpubs() {
        server_.createPresentationRPubsSource(new SimpleRequestCallback<PresentationRPubsSource>() {

            @Override
            public void onResponseReceived(PresentationRPubsSource source) {
                RPubsUploadDialog dlg = new RPubsUploadDialog("Presentation", view_.getPresentationTitle(),
                        source.getSourceFilePath(), source.isPublished());
                dlg.showModal();
            }

            @Override
            public void onError(ServerError error) {
                globalDisplay_.showErrorMessage("Error Saving Presentation", getErrorMessage(error));
            }
        });
    }

    @Handler
    void onClearPresentationCache() {
        globalDisplay_.showYesNoMessage(MessageDialog.INFO, "Clear Knitr Cache",
                "Clearing the Knitr cache will discard previously cached "
                        + "output and re-run all of the R code chunks within the " + "presentation.\n\n"
                        + "Are you sure you want to clear the cache now?",
                false, new ProgressOperation() {

                    @Override
                    public void execute(final ProgressIndicator indicator) {
                        indicator.onProgress("Clearing Knitr Cache...");
                        server_.clearPresentationCache(new ServerRequestCallback<Void>() {
                            @Override
                            public void onResponseReceived(Void response) {
                                indicator.onCompleted();
                                refreshPresentation();
                            }

                            @Override
                            public void onError(ServerError error) {
                                indicator.onCompleted();
                                globalDisplay_.showErrorMessage("Error Clearing Cache", getErrorMessage(error));
                            }
                        });
                    }

                }, new ProgressOperation() {

                    @Override
                    public void execute(ProgressIndicator indicator) {
                        indicator.onCompleted();
                    }
                }, true);
    }

    @Handler
    void onRefreshPresentation() {
        if (Event.getCurrentEvent().getShiftKey())
            currentState_.setSlideIndex(0);

        refreshPresentation();
    }

    private void refreshPresentation() {
        view_.showBusy();
        view_.load(buildPresentationUrl());
    }

    @Handler
    void onTutorialFeedback() {
        EditDialog editDialog = new EditDialog("Provide Feedback", "Submit", "", false, true, new Size(450, 300),
                new ProgressOperationWithInput<String>() {
                    @Override
                    public void execute(String input, ProgressIndicator indicator) {
                        if (input == null) {
                            indicator.onCompleted();
                            return;
                        }

                        indicator.onProgress("Saving feedback...");

                        server_.tutorialFeedback(input, new VoidServerRequestCallback(indicator));

                    }
                });

        editDialog.showModal();

    }

    @Override
    public void onSelected() {
        super.onSelected();

        // after doing a pane reconfig the frame gets wiped (no idea why)
        // workaround this by doing a check for an active state with
        // no slides currently displayed
        if (currentState_ != null && currentState_.isActive() && !view_.hasSlides()) {
            init(currentState_);
        }
    }

    public void confirmClose(Command onConfirmed) {
        // don't allow close if this is a tutorial
        if (currentState_.isTutorial()) {
            globalDisplay_.showMessage(MessageDisplay.MSG_WARNING, "Unable to Close", "Tutorials cannot be closed");
            return;
        }

        final ProgressIndicator progress = new GlobalProgressDelayer(globalDisplay_, 0, "Closing Presentation...")
                .getIndicator();

        server_.closePresentationPane(new ServerRequestCallback<Void>() {
            @Override
            public void onResponseReceived(Void resp) {
                reloadWorkbench();
            }

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

            }
        });
    }

    @Override
    public void navigate(int index) {
        view_.navigate(index);

    }

    @Override
    public SlideNavigationMenu getNavigationMenu() {
        return view_.getNavigationMenu();
    }

    @Override
    public HandlerRegistration addSlideNavigationChangedHandler(SlideNavigationChangedEvent.Handler handler) {
        return handlerManager_.addHandler(SlideNavigationChangedEvent.TYPE, handler);
    }

    @Override
    public HandlerRegistration addSlideIndexChangedHandler(SlideIndexChangedEvent.Handler handler) {
        return handlerManager_.addHandler(SlideIndexChangedEvent.TYPE, handler);
    }

    private void reloadWorkbench() {
        Barrier barrier = new Barrier();
        barrier.addBarrierReleasedHandler(new BarrierReleasedHandler() {

            @Override
            public void onBarrierReleased(BarrierReleasedEvent event) {
                eventBus_.fireEvent(new ReloadEvent());
            }
        });

        Token token = barrier.acquire();
        try {
            eventBus_.fireEvent(new LastChanceSaveEvent(barrier));
        } finally {
            token.release();
        }
    }

    private void init(PresentationState state) {
        currentState_ = state;
        view_.load(buildPresentationUrl());
    }

    private String buildPresentationUrl() {
        return buildPresentationUrl(null);
    }

    private String buildPresentationUrl(String extraPath) {
        String url = server_.getApplicationURL("presentation/");
        if (extraPath != null)
            url = url + extraPath;
        url = url + "#/" + currentState_.getSlideIndex();
        return url;
    }

    private boolean isPresentationActive() {
        return (currentState_ != null) && (currentState_.isActive()) && view_.hasSlides();
    }

    private String getCurrentPresDir() {
        if (currentState_ == null)
            return "";

        FileSystemItem presFilePath = FileSystemItem.createFile(currentState_.getFilePath());
        return presFilePath.getParentPathString();
    }

    private void onPresentationSlideChanged(final int index, final JavaScriptObject jsCmds) {
        // note the slide index and save it
        currentState_.setSlideIndex(index);
        indexPersister_.setIndex(index);

        handlerManager_.fireEvent(new SlideIndexChangedEvent(index));

        // execute commands if we stay on the slide for > 500ms
        new Timer() {
            @Override
            public void run() {
                // execute commands if we're still on the same slide
                if (index == currentState_.getSlideIndex()) {
                    JsArray<JavaScriptObject> cmds = jsCmds.cast();
                    for (int i = 0; i < cmds.length(); i++)
                        dispatchCommand(cmds.get(i));
                }
            }
        }.schedule(500);
    }

    private void dispatchCommand(JavaScriptObject jsCommand) {
        dispatcher_.dispatchCommand(jsCommand);
    }

    private void initPresentationNavigator(JavaScriptObject jsNavigator) {
        // record current slides
        SlideNavigation navigation = jsNavigator.cast();
        handlerManager_.fireEvent(new SlideNavigationChangedEvent(navigation));
    }

    private void recordPresentationQuizAnswer(int slideIndex, int answer, boolean correct) {
        server_.tutorialQuizResponse(slideIndex, answer, correct, new VoidServerRequestCallback());
    }

    private final native void initPresentationCallbacks() /*-{
                                                          var thiz = this;
                                                          $wnd.presentationSlideChanged = $entry(function(index, cmds) {
                                                          thiz.@org.rstudio.studio.client.workbench.views.presentation.Presentation::onPresentationSlideChanged(ILcom/google/gwt/core/client/JavaScriptObject;)(index, cmds);
                                                          });
                                                          $wnd.dispatchPresentationCommand = $entry(function(cmd) {
                                                          thiz.@org.rstudio.studio.client.workbench.views.presentation.Presentation::dispatchCommand(Lcom/google/gwt/core/client/JavaScriptObject;)(cmd);
                                                          });
                                                          $wnd.initPresentationNavigator = $entry(function(slides) {
                                                          thiz.@org.rstudio.studio.client.workbench.views.presentation.Presentation::initPresentationNavigator(Lcom/google/gwt/core/client/JavaScriptObject;)(slides);
                                                          });
                                                          $wnd.recordPresentationQuizAnswer = $entry(function(index, answer, correct) {
                                                          thiz.@org.rstudio.studio.client.workbench.views.presentation.Presentation::recordPresentationQuizAnswer(IIZ)(index, answer, correct);
                                                          });
                                                          }-*/;

    private class IndexPersister extends TimeBufferedCommand {
        public IndexPersister() {
            super(500);
        }

        public void setIndex(int index) {
            index_ = index;
            nudge();
        }

        @Override
        protected void performAction(boolean shouldSchedulePassive) {
            server_.setPresentationSlideIndex(index_, new VoidServerRequestCallback());
        }

        private int index_ = 0;
    };

    private IndexPersister indexPersister_ = new IndexPersister();

    private static int detectSlideIndex(String contents, int cursorLine) {
        int currentLine = 0;
        int slideIndex = -1;
        String slideRegex = "^\\={3,}\\s*$";

        Iterator<String> it = StringUtil.getLineIterator(contents).iterator();
        while (it.hasNext()) {
            String line = it.next();
            if (line.matches(slideRegex))
                slideIndex++;

            if (currentLine++ >= cursorLine) {
                // bump the slide index if the next line is a header
                if (it.hasNext() && it.next().matches(slideRegex))
                    slideIndex++;

                return slideIndex;
            }
        }

        return -1;
    }

    private String getErrorMessage(ServerError error) {
        String message = error.getUserMessage();
        JSONString userMessage = error.getClientInfo().isString();
        if (userMessage != null)
            message = userMessage.stringValue();
        return message;
    }

    private final Display view_;
    private final PresentationServerOperations server_;
    private final GlobalDisplay globalDisplay_;
    private final EventBus eventBus_;
    private final Commands commands_;
    private final FileTypeRegistry fileTypeRegistry_;
    private final FileDialogs fileDialogs_;
    private final RemoteFileSystemContext fileSystemContext_;
    private final Session session_;
    private final PresentationDispatcher dispatcher_;
    private final SlideNavigationPresenter navigationPresenter_;
    private PresentationState currentState_ = null;

    private boolean usingRmd_ = false;

    private FileSystemItem saveAsStandaloneDefaultPath_ = null;

    private HandlerManager handlerManager_ = new HandlerManager(this);
}