/*
* DesktopApplicationHeader.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.application.ui.impl;
import java.util.ArrayList;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.command.impl.DesktopMenuCallback;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.theme.res.ThemeStyles;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.ApplicationQuit;
import org.rstudio.studio.client.application.ApplicationQuit.QuitContext;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.application.DesktopHooks;
import org.rstudio.studio.client.application.IgnoredUpdates;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.model.ApplicationServerOperations;
import org.rstudio.studio.client.application.model.UpdateCheckResult;
import org.rstudio.studio.client.application.ui.ApplicationHeader;
import org.rstudio.studio.client.application.ui.GlobalToolbar;
import org.rstudio.studio.client.application.ui.RStudioThemes;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.debugging.ErrorManager;
import org.rstudio.studio.client.events.EditEvent;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.SessionInitEvent;
import org.rstudio.studio.client.workbench.events.SessionInitHandler;
import org.rstudio.studio.client.workbench.model.ClientState;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.SessionInfo;
import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
import org.rstudio.studio.client.workbench.views.files.events.ShowFolderEvent;
import org.rstudio.studio.client.workbench.views.files.events.ShowFolderHandler;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorNative;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Selection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class DesktopApplicationHeader implements ApplicationHeader
{
public interface Binder
extends CommandBinder<Commands, DesktopApplicationHeader>
{
}
private static Binder binder_ = GWT.create(Binder.class);
public DesktopApplicationHeader()
{
RStudioGinjector.INSTANCE.injectMembers(this);
}
@Inject
public void initialize(final Commands commands,
EventBus events,
final Session session,
ApplicationServerOperations server,
Provider<DesktopHooks> pDesktopHooks,
Provider<CodeSearch> pCodeSearch,
Provider<UIPrefs> pUIPrefs,
ErrorManager errorManager,
GlobalDisplay globalDisplay,
ApplicationQuit appQuit)
{
session_ = session;
eventBus_= events;
pUIPrefs_ = pUIPrefs;
globalDisplay_ = globalDisplay;
ignoredUpdates_ = IgnoredUpdates.create();
server_ = server;
appQuit_ = appQuit;
binder_.bind(commands, this);
commands.mainMenu(new DesktopMenuCallback());
pDesktopHooks.get();
commands.uploadFile().remove();
commands.exportFiles().remove();
commands.updateCredentials().remove();
commands.checkForUpdates().setVisible(true);
commands.showLogFiles().setVisible(true);
commands.diagnosticsReport().setVisible(true);
commands.showFolder().setVisible(true);
events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
public void onSessionInit(SessionInitEvent sie)
{
final SessionInfo sessionInfo = session.getSessionInfo();
isFlatTheme_ = RStudioThemes.isFlat(pUIPrefs_.get());
toolbar_.completeInitialization(sessionInfo);
new JSObjectStateValue(
"updates",
"ignoredUpdates",
ClientState.PERSISTENT,
session_.getSessionInfo().getClientState(),
false)
{
@Override
protected void onInit(JsObject value)
{
if (value != null)
ignoredUpdates_ = value.cast();
}
@Override
protected JsObject getValue()
{
ignoredUpdatesDirty_ = false;
return ignoredUpdates_.cast();
}
@Override
protected boolean hasChanged()
{
return ignoredUpdatesDirty_;
}
};
Scheduler.get().scheduleFinally(new ScheduledCommand()
{
public void execute()
{
Desktop.getFrame().onWorkbenchInitialized(
sessionInfo.getScratchDir());
if (sessionInfo.getDisableCheckForUpdates())
commands.checkForUpdates().remove();
if (!sessionInfo.getDisableCheckForUpdates() &&
pUIPrefs_.get().checkForUpdates().getValue())
{
checkForUpdates(false);
}
}
});
}
});
events.addHandler(ShowFolderEvent.TYPE, new ShowFolderHandler()
{
public void onShowFolder(ShowFolderEvent event)
{
Desktop.getFrame().showFolder(event.getPath().getPath());
}
});
toolbar_ = new GlobalToolbar(commands,
events,
pCodeSearch);
ThemeStyles styles = ThemeResources.INSTANCE.themeStyles();
toolbar_.getWrapper().addStyleName(styles.desktopGlobalToolbarWrapper());
toolbar_.addStyleName(styles.desktopGlobalToolbar());
}
public void showToolbar(boolean showToolbar)
{
toolbar_.setVisible(showToolbar);
}
public boolean isToolbarVisible()
{
return toolbar_.isVisible();
}
public void focusGoToFunction()
{
toolbar_.focusGoToFunction();
}
private void fireEditEvent(final int type)
{
eventBus_.fireEvent(new EditEvent(true, type));
}
@Handler
void onUndoDummy()
{
Desktop.getFrame().undo(isFocusInAceInstance());
}
@Handler
void onRedoDummy()
{
Desktop.getFrame().redo(isFocusInAceInstance());
}
@Handler
void onCutDummy()
{
if (isSelectionEmpty()) return;
fireEditEvent(EditEvent.TYPE_CUT);
Desktop.getFrame().clipboardCut();
}
@Handler
void onCopyDummy()
{
if (isSelectionEmpty()) return;
fireEditEvent(EditEvent.TYPE_COPY);
Desktop.getFrame().clipboardCopy();
}
@Handler
void onPasteDummy()
{
fireEditEvent(EditEvent.TYPE_PASTE);
Desktop.getFrame().clipboardPaste();
}
@Handler
void onShowLogFiles()
{
Desktop.getFrame().showFolder(session_.getSessionInfo().getLogDir());
}
@Handler
void onDiagnosticsReport()
{
eventBus_.fireEvent(
new SendToConsoleEvent("rstudioDiagnosticsReport()", true));
new Timer() {
@Override
public void run()
{
Desktop.getFrame().showFolder("~/rstudio-diagnostics");
}
}.schedule(1000);
}
@Handler
void onCheckForUpdates()
{
checkForUpdates(true);
}
public int getPreferredHeight()
{
if (toolbar_.isVisible())
return isFlatTheme_ ? 29 : 32;
else
return 5;
}
public Widget asWidget()
{
return toolbar_;
}
private void checkForUpdates(final boolean manual)
{
server_.checkForUpdates(manual,
new ServerRequestCallback<UpdateCheckResult>()
{
@Override
public void onResponseReceived(UpdateCheckResult result)
{
respondToUpdateCheck(result, manual);
}
@Override
public void onError(ServerError error)
{
globalDisplay_.showErrorMessage("Error Checking for Updates",
"An error occurred while checking for updates: "
+ error.getMessage());
}
});
}
private void respondToUpdateCheck(final UpdateCheckResult result,
boolean manual)
{
boolean ignoredUpdate = false;
if (result.getUpdateVersion().length() > 0)
{
JsArrayString ignoredUpdates = ignoredUpdates_.getIgnoredUpdates();
for (int i = 0; i < ignoredUpdates.length(); i++)
{
if (ignoredUpdates.get(i).equals(result.getUpdateVersion()))
{
ignoredUpdate = true;
}
}
}
if (result.getUpdateVersion().length() > 0 &&
!ignoredUpdate)
{
ArrayList<String> buttonLabels = new ArrayList<String>();
ArrayList<Operation> buttonOperations = new ArrayList<Operation>();
buttonLabels.add("Quit and Download...");
buttonOperations.add(new Operation() {
@Override
public void execute()
{
appQuit_.prepareForQuit("Update RStudio", new QuitContext()
{
@Override
public void onReadyToQuit(boolean saveChanges)
{
Desktop.getFrame().browseUrl(result.getUpdateUrl());
appQuit_.performQuit(saveChanges);
}
});
}
});
buttonLabels.add("Remind Later");
buttonOperations.add(new Operation() {
@Override
public void execute()
{
// Don't do anything here; the prompt will re-appear the next
// time we do an update check
}
});
// Only provide the option to ignore the update if it's not urgent.
if (result.getUpdateUrgency() == 0)
{
buttonLabels.add("Ignore Update");
buttonOperations.add(new Operation() {
@Override
public void execute()
{
ignoredUpdates_.addIgnoredUpdate(result.getUpdateVersion());
ignoredUpdatesDirty_ = true;
}
});
}
globalDisplay_.showGenericDialog(GlobalDisplay.MSG_QUESTION,
"Update Available",
result.getUpdateMessage(),
buttonLabels,
buttonOperations, 0);
}
else if (manual)
{
globalDisplay_.showMessage(GlobalDisplay.MSG_INFO,
"No Update Available",
"You're using the newest version of RStudio.");
}
}
public static boolean isSelectionEmpty()
{
Element activeElement = DomUtils.getActiveElement();
AceEditorNative editor = AceEditorNative.getEditor(activeElement);
if (editor != null)
{
Selection selection = editor.getSession().getSelection();
return selection.isEmpty();
}
// NOTE: we currently use this for managing copy + paste
// behaviors, but copy + paste seems to do the right thing
// regardless of whether the user has highlighted some text
// or not _outside_ of an Ace instance, and we can't always
// detect if the document has a selection (e.g. if an iframe
// has focus)
return false;
}
private static boolean isFocusInAceInstance()
{
Element focusElem = DomUtils.getActiveElement();
if (focusElem != null)
return focusElem.hasClassName("ace_text-input");
return false;
}
private Session session_;
private EventBus eventBus_;
private GlobalToolbar toolbar_;
private GlobalDisplay globalDisplay_;
Provider<UIPrefs> pUIPrefs_;
private ApplicationServerOperations server_;
private IgnoredUpdates ignoredUpdates_;
private boolean ignoredUpdatesDirty_ = false;
private ApplicationQuit appQuit_;
private Boolean isFlatTheme_ = false;
}