/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ui.window;
import elemental.js.dom.JsElement;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import org.eclipse.che.commons.annotation.Nullable;
import org.vectomatic.dom.svg.ui.SVGResource;
/**
* A popup that automatically centers its content, even if the dimensions of the content change. The
* centering is done in CSS, so performance is very good. A semi-transparent "glass" panel appears
* behind the popup. The glass is not optional due to the way {@link Window} is implemented.
* <p/>
* <p>
* {@link Window} animates into and out of view using the shrink in/expand out animation.
* </p>
*/
public abstract class Window implements IsWidget {
protected static final Resources resources = GWT.create(Resources.class);
static {
resources.windowCss().ensureInjected();
}
private boolean blocked = false;
private boolean hideOnEscapeEnabled = true;
private boolean isShowing;
private View view;
protected Window() {
this(true);
}
protected Window(boolean showBottomPanel) {
view = new View(resources, showBottomPanel);
}
public void setWidget(Widget widget) {
view.addContentWidget(widget);
handleViewEvents();
}
public Widget getWidget() {
return view.getContent();
}
/**
* ensureDebugId on the current window container. ensureDebugId id + "-headerLabel" on the window control bar title
*
* @see UIObject#ensureDebugId(String)
*/
public void ensureDebugId(String id) {
view.contentContainer.ensureDebugId(id);
view.headerLabel.ensureDebugId(id + "-headerLabel");
}
public void hideCrossButton() {
view.closeButton.setVisible(false);
}
/**
* Blocks the window, prevents it closing.
*/
public void setBlocked(boolean blocked) {
this.blocked = blocked;
if (blocked) {
view.closeButton.getElement().getStyle().setProperty("opacity", "0.3");
view.closeButton.getElement().setAttribute("blocked", "");
} else {
view.closeButton.getElement().getStyle().clearProperty("opacity");
view.closeButton.getElement().removeAttribute("blocked");
}
}
/**
* Hides the {@link Window} popup. The popup will animate out of view.
*/
public void hide() {
if (blocked) {
return;
}
if (!isShowing) {
return;
}
isShowing = false;
// Animate the popup out of existence.
view.setShowing(false);
// Remove the popup when the animation completes.
new Timer() {
@Override
public void run() {
if (blocked) {
return;
}
// The popup may have been shown before this timer executes.
if (!isShowing) {
view.removeFromParent();
Style style = view.contentContainer.getElement().getStyle();
style.clearPosition();
style.clearLeft();
style.clearTop();
}
}
}.schedule(view.getAnimationDuration());
}
/**
* Checks if the {@link Window} is showing or animating into view.
*
* @return true if showing, false if hidden
*/
public boolean isShowing() {
return isShowing;
}
/**
* Sets whether or not the popup should hide when escape is pressed. The
* default behavior is to ignore the escape key.
*
* @param isEnabled
* true to close on escape, false not to
*/
public void setHideOnEscapeEnabled(boolean isEnabled) {
this.hideOnEscapeEnabled = isEnabled;
}
protected Button createButton(String title, String debugId, ClickHandler clickHandler) {
Button button = new Button();
button.setText(title);
button.ensureDebugId(debugId);
button.getElement().setId(debugId);
button.addStyleName(resources.windowCss().button());
button.addClickHandler(clickHandler);
//set default tab index
button.setTabIndex(0);
return button;
}
protected Button createPrimaryButton(String title, String debugId, ClickHandler clickHandler) {
Button button = createButton(title, debugId, clickHandler);
button.addStyleName(resources.windowCss().primaryButton());
//set default tab index
button.setTabIndex(0);
return button;
}
protected void addButtonToFooter(Button button) {
button.addStyleName(resources.windowCss().alignBtn());
getFooter().add(button);
}
protected void onEnterClicked() {
}
/** Set focus to current window. */
public void focus() {
view.setFocus();
}
/**
* Sets focus on the last focused child element if such exists.
*/
public void focusLastFocusedElement() {
view.focusLastFocusedElement();
}
/**
* Returns {@code true} if widget is in the focus and {@code false} - otherwise.
*/
public boolean isWidgetFocused(FocusWidget widget) {
return view.isElementFocused(widget.getElement());
}
/**
* See {@link #show(Focusable)}.
*/
public void show() {
show(null);
}
/**
* Displays the {@link Window} popup. The popup will animate into view.
*
* @param selectAndFocusElement
* an {@link Focusable} to select and focus on when the panel is
* shown. If null, no element will be given focus
*/
public void show(@Nullable final Focusable selectAndFocusElement) {
setBlocked(false);
if (isShowing) {
return;
}
isShowing = true;
// Attach the popup to the body.
final JsElement popup = view.popup.getElement().cast();
if (popup.getParentElement() == null) {
// Hide the popup so it can enter its initial state without flickering.
popup.getStyle().setVisibility("hidden");
RootLayoutPanel.get().add(view);
}
// The popup may have been hidden before this timer executes.
if (isShowing) {
popup.getStyle().removeProperty("visibility");
// Start the animation after the element is attached.
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
// The popup may have been hidden before this timer executes.
view.setShowing(true);
if (selectAndFocusElement != null) {
selectAndFocusElement.setFocus(true);
}
}
});
}
}
private void handleViewEvents() {
view.setDelegate(new ViewEvents() {
@Override
public void onEscapeKey() {
if (hideOnEscapeEnabled && !blocked) {
Window.this.onClose();
}
}
@Override
public void onClose() {
if (!blocked) {
Window.this.onClose();
}
}
@Override
public void onEnterKey() {
onEnterClicked();
}
});
}
/**
* Is called when user closes the Window.
*/
protected void onClose() {
hide();
}
@Override
public Widget asWidget() {
return com.google.gwt.user.client.ui.HTML.wrap(view.getElement());
}
public void setTitle(String title) {
view.headerLabel.setText(title);
}
public HTMLPanel getFooter() {
return view.footer;
}
/**
* The resources used by this UI component.
*/
public interface Resources extends ClientBundle {
@Source({"org/eclipse/che/ide/ui/constants.css", "Window.css", "org/eclipse/che/ide/api/ui/style.css"})
Css windowCss();
@Source("close-icon.svg")
SVGResource closeButton();
}
/**
* The Css Style names used by this panel.
*/
public interface Css extends CssResource {
/**
* Returns duration of the popup animation in milliseconds.
*/
int animationDuration();
String content();
String contentVisible();
String center();
String glassVisible();
String popup();
String positioner();
String header();
String headerTitleWrapper();
String headerTitleLabel();
String footer();
String separator();
String alignBtn();
String closeButton();
String primaryButton();
String button();
String image();
}
/**
* The events sources by the View.
*/
public interface ViewEvents {
void onEscapeKey();
void onClose();
void onEnterKey();
}
}