/*
* WebApplicationHeader.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 com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.ui.*;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.command.*;
import org.rstudio.core.client.command.impl.WebMenuCallback;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.theme.res.ThemeStyles;
import org.rstudio.core.client.widget.HyperlinkLabel;
import org.rstudio.core.client.widget.MessageDialogLabel;
import org.rstudio.core.client.widget.ToolbarButton;
import org.rstudio.core.client.widget.events.GlassVisibilityEvent;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.events.LogoutRequestedEvent;
import org.rstudio.studio.client.application.ui.ApplicationHeader;
import org.rstudio.studio.client.application.ui.GlobalToolbar;
import org.rstudio.studio.client.application.ui.ProjectPopupMenu;
import org.rstudio.studio.client.application.ui.impl.header.HeaderPanel;
import org.rstudio.studio.client.application.ui.impl.header.MenubarPanel;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.dialog.WebDialogBuilderFactory;
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.Session;
import org.rstudio.studio.client.workbench.model.SessionInfo;
public class WebApplicationHeader extends Composite implements ApplicationHeader
{
public WebApplicationHeader()
{
RStudioGinjector.INSTANCE.injectMembers(this);
}
@Inject
public void initialize(
final Commands commands,
EventBus eventBus,
GlobalDisplay globalDisplay,
ThemeResources themeResources,
final Session session,
Provider<CodeSearch> pCodeSearch)
{
eventBus_ = eventBus;
globalDisplay_ = globalDisplay;
// Use the outer panel to just aggregate the menu bar/account area,
// with the logo. The logo can't be inside the HorizontalPanel because
// it needs to overflow out of the top of the panel, and it was much
// easier to do this with absolute positioning.
outerPanel_ = new FlowPanel();
outerPanel_.getElement().getStyle().setPosition(Position.RELATIVE);
// large logo
logoLarge_ = new Image(ThemeResources.INSTANCE.rstudio());
((ImageElement)logoLarge_.getElement().cast()).setAlt("RStudio");
Style style = logoLarge_.getElement().getStyle();
style.setPosition(Position.ABSOLUTE);
style.setTop(5, Unit.PX);
style.setLeft(18, Unit.PX);
// small logo
logoSmall_ = new Image(ThemeResources.INSTANCE.rstudio_small());
((ImageElement)logoSmall_.getElement().cast()).setAlt("RStudio");
style = logoSmall_.getElement().getStyle();
style.setPosition(Position.ABSOLUTE);
style.setTop(5, Unit.PX);
style.setLeft(18, Unit.PX);
// header container
headerBarPanel_ = new HorizontalPanel() ;
headerBarPanel_.setStylePrimaryName(themeResources.themeStyles().header());
headerBarPanel_.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
headerBarPanel_.setWidth("100%");
if (BrowseCap.INSTANCE.suppressBrowserForwardBack())
suppressBrowserForwardBack();
// override Cmd+W keybaord shortcut for Chrome
if (BrowseCap.isChrome())
{
int modifiers = (BrowseCap.hasMetaKey() ? KeyboardShortcut.META :
KeyboardShortcut.CTRL) |
KeyboardShortcut.ALT;
AppCommand closeSourceDoc = commands.closeSourceDoc();
closeSourceDoc.setShortcut(new KeyboardShortcut(modifiers, 'W'));
ShortcutManager.INSTANCE.register(
modifiers, 'W', closeSourceDoc, "", "");
}
// main menu
advertiseEditingShortcuts(globalDisplay, commands);
WebMenuCallback menuCallback = new WebMenuCallback();
commands.mainMenu(menuCallback);
mainMenu_ = menuCallback.getMenu();
mainMenu_.setAutoHideRedundantSeparators(false);
fixup(mainMenu_);
mainMenu_.addStyleName(themeResources.themeStyles().mainMenu());
AppMenuBar.addSubMenuVisibleChangedHandler(new SubMenuVisibleChangedHandler()
{
public void onSubMenuVisibleChanged(SubMenuVisibleChangedEvent event)
{
// When submenus of the main menu appear, glass over any iframes
// so that mouse clicks can make the menus disappear
if (event.isVisible())
eventBus_.fireEvent(new GlassVisibilityEvent(true));
else
eventBus_.fireEvent(new GlassVisibilityEvent(false));
}
});
headerBarPanel_.add(mainMenu_);
HTML spacer = new HTML();
headerBarPanel_.add(spacer);
headerBarPanel_.setCellWidth(spacer, "100%");
// commands panel (no widgets added until after session init)
headerBarCommandsPanel_ = new HorizontalPanel();
headerBarPanel_.add(headerBarCommandsPanel_);
headerBarPanel_.setCellHorizontalAlignment(headerBarCommandsPanel_,
HorizontalPanel.ALIGN_RIGHT);
eventBus.addHandler(SessionInitEvent.TYPE, new SessionInitHandler()
{
public void onSessionInit(SessionInitEvent sie)
{
SessionInfo sessionInfo = session.getSessionInfo();
// only show the user identity if we are in server mode
if (sessionInfo.getShowIdentity())
initCommandsPanel(sessionInfo);
// complete toolbar initialization
toolbar_.completeInitialization(sessionInfo);
// add project tools to main menu
projectMenuButton_ =
new ProjectPopupMenu(sessionInfo, commands).getToolbarButton();
projectMenuButton_.addStyleName(
ThemeStyles.INSTANCE.webHeaderBarCommandsProjectMenu());
headerBarPanel_.add(projectMenuButton_);
showProjectMenu(!toolbar_.isVisible());
}
});
// create toolbar
toolbar_ = new GlobalToolbar(commands,
eventBus,
pCodeSearch);
toolbar_.addStyleName(themeResources.themeStyles().webGlobalToolbar());
// initialize widget
initWidget(outerPanel_);
}
public void showToolbar(boolean showToolbar)
{
outerPanel_.clear();
if (showToolbar)
{
HeaderPanel headerPanel = new HeaderPanel(headerBarPanel_, toolbar_);
outerPanel_.add(headerPanel);
outerPanel_.add(logoLarge_);
mainMenu_.getElement().getStyle().setMarginLeft(18, Unit.PX);
preferredHeight_ = 65;
showProjectMenu(false);
}
else
{
MenubarPanel menubarPanel = new MenubarPanel(headerBarPanel_);
outerPanel_.add(menubarPanel);
outerPanel_.add(logoSmall_);
mainMenu_.getElement().getStyle().setMarginLeft(0, Unit.PX);
preferredHeight_ = 45;
showProjectMenu(true);
}
}
public boolean isToolbarVisible()
{
return !projectMenuButton_.isVisible();
}
public void focusGoToFunction()
{
toolbar_.focusGoToFunction();
}
private void showProjectMenu(boolean show)
{
projectMenuButton_.setVisible(show);
}
private native final void suppressBrowserForwardBack() /*-{
try {
var outerWindow = $wnd.parent;
if (outerWindow.addEventListener) {
var handler = function(evt) {
if ((evt.keyCode == 37 || evt.keyCode == 39) && (evt.metaKey && !evt.ctrlKey && !evt.shiftKey && !evt.altKey)) {
evt.preventDefault();
evt.stopPropagation();
}
};
outerWindow.addEventListener('keydown', handler, false);
$wnd.addEventListener('keydown', handler, false);
}
} catch(err) {}
}-*/;
private void advertiseEditingShortcuts(final GlobalDisplay display,
final Commands commands)
{
int mod = BrowseCap.hasMetaKey() ? KeyboardShortcut.META : KeyboardShortcut.CTRL;
commands.undoDummy().setShortcut(new KeyboardShortcut(mod, 'Z'));
commands.redoDummy().setShortcut(new KeyboardShortcut(mod| KeyboardShortcut.SHIFT, 'Z'));
commands.cutDummy().setShortcut(new KeyboardShortcut(mod, 'X'));
commands.copyDummy().setShortcut(new KeyboardShortcut(mod, 'C'));
commands.pasteDummy().setShortcut(new KeyboardShortcut(mod, 'V'));
CommandHandler useKeyboardNotification = new CommandHandler()
{
public void onCommand(AppCommand command)
{
MessageDialogLabel label = new MessageDialogLabel();
label.setHtml("Your browser does not allow access to your<br/>" +
"computer's clipboard. As a result you must<br/>" +
"use keyboard shortcuts for:" +
"<br/><br/><table cellpadding=0 cellspacing=0 border=0>" +
makeRow(commands.undoDummy()) +
makeRow(commands.redoDummy()) +
makeRow(commands.cutDummy()) +
makeRow(commands.copyDummy()) +
makeRow(commands.pasteDummy()) +
"</table>"
);
new WebDialogBuilderFactory().create(
GlobalDisplay.MSG_WARNING,
"Use Keyboard Shortcut",
label).showModal();
}
private String makeRow(AppCommand cmd)
{
String textAlign = BrowseCap.hasMetaKey()
? "text-align: right"
: "";
return "<tr><td>" + cmd.getMenuLabel(true) + "</td>" +
"<td style='padding-left: 12px; " + textAlign + "'>"
+ cmd.getShortcutPrettyHtml() + "</td></tr>";
}
};
commands.undoDummy().addHandler(useKeyboardNotification);
commands.redoDummy().addHandler(useKeyboardNotification);
commands.cutDummy().addHandler(useKeyboardNotification);
commands.copyDummy().addHandler(useKeyboardNotification);
commands.pasteDummy().addHandler(useKeyboardNotification);
}
public int getPreferredHeight()
{
return preferredHeight_;
}
/**
* Without this fixup, the main menu doesn't properly deselect its items
* when the mouse takes focus away.
*/
private void fixup(final AppMenuBar mainMenu)
{
mainMenu.addCloseHandler(new CloseHandler<PopupPanel>()
{
public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent)
{
// Only dismiss the selection if the panel that just closed belongs
// to the currently selected item. Otherwise, the selected item
// has already changed and we don't want to mess with it. (This is
// NOT an edge case, it is very common.)
MenuItem menuItem = mainMenu.getSelectedItem();
if (menuItem != null)
{
MenuBar subMenu = menuItem.getSubMenu();
if (subMenu != null &&
popupPanelCloseEvent.getTarget() != null &&
subMenu.equals(popupPanelCloseEvent.getTarget().getWidget()))
{
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
mainMenu.selectItem(null);
}
});
}
}
}
});
}
private void initCommandsPanel(final SessionInfo sessionInfo)
{
// add username
Label usernameLabel = new Label();
usernameLabel.setText(sessionInfo.getUserIdentity());
headerBarCommandsPanel_.add(usernameLabel);
headerBarCommandsPanel_.add(createCommandSeparator());
// signout link
Widget signoutLink = createCommandLink("Sign Out", new ClickHandler() {
public void onClick(ClickEvent event)
{
eventBus_.fireEvent(new LogoutRequestedEvent());
}
});
headerBarCommandsPanel_.add(signoutLink);
}
private Widget createCommandSeparator()
{
return new HTML(" | ");
}
private Widget createCommandLink(String caption, ClickHandler clickHandler)
{
HyperlinkLabel link = new HyperlinkLabel(caption, clickHandler);
return link;
}
public Widget asWidget()
{
return this;
}
private int preferredHeight_;
private FlowPanel outerPanel_;
private Image logoLarge_;
private Image logoSmall_;
private HorizontalPanel headerBarPanel_;
private HorizontalPanel headerBarCommandsPanel_;
private ToolbarButton projectMenuButton_;
private AppMenuBar mainMenu_;
private GlobalToolbar toolbar_;
private EventBus eventBus_;
private GlobalDisplay globalDisplay_;
}