/******************************************************************************* * 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.events.KeyboardEvent; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiTemplate; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import org.eclipse.che.ide.util.UIUtil; import java.util.List; /** * The view that renders the {@link Window}. The View consists of a glass * panel that fades out the background, and a DOM structure that positions the * contents in the exact center of the screen. */ class View extends Composite { private static MyBinder uiBinder = GWT.create(MyBinder.class); final Window.Resources res; @UiField(provided = true) final Window.Css css; @UiField FocusPanel focusPanel; @UiField FlowPanel contentContainer; @UiField FlowPanel popup; @UiField FlowPanel header; @UiField FlowPanel content; @UiField Label headerLabel; HTMLPanel footer; @UiField FlowPanel closeButton; private int windowWidth; private int clientLeft; private int clientTop; private Window.ViewEvents delegate; private boolean dragging; private int dragStartX; private int dragStartY; private String transition; private FocusWidget lastFocused; private BlurHandler blurHandler = new BlurHandler() { @Override public void onBlur(BlurEvent event) { if (event.getSource() instanceof FocusWidget) { lastFocused = (FocusWidget)event.getSource(); } } }; View(Window.Resources res, boolean showBottomPanel) { this.res = res; this.css = res.windowCss(); windowWidth = com.google.gwt.user.client.Window.getClientWidth(); clientLeft = Document.get().getBodyOffsetLeft(); clientTop = Document.get().getBodyOffsetTop(); initWidget(uiBinder.createAndBindUi(this)); footer = new HTMLPanel(""); if (showBottomPanel) { footer.setStyleName(res.windowCss().footer()); contentContainer.add(footer); } handleEvents(); FocusPanel dummyFocusElement = new FocusPanel(); dummyFocusElement.setTabIndex(0); dummyFocusElement.addFocusHandler(new FocusHandler() { @Override public void onFocus(FocusEvent event) { setFocus(); } }); contentContainer.add(dummyFocusElement); } /** * Returns the duration of the popup animation in milliseconds. The return * value should equal the value of {@link Window.Css#animationDuration()}. */ protected int getAnimationDuration() { return css.animationDuration(); } /** * Updates the View to reflect the showing state of the popup. * * @param showing * true if showing, false if not. */ protected void setShowing(boolean showing) { //set for each focusable widget blur handler to have ability to store last focused element for (FocusWidget focusWidget : UIUtil.getFocusableChildren(content)) { focusWidget.addBlurHandler(blurHandler); } if (showing) { contentContainer.addStyleName(css.contentVisible()); } else { contentContainer.removeStyleName(css.contentVisible()); } } private void handleEvents() { KeyDownHandler handler = new KeyDownHandler() { @Override public void onKeyDown(KeyDownEvent event) { if (KeyboardEvent.KeyCode.ESC == event.getNativeEvent().getKeyCode()) { event.stopPropagation(); event.preventDefault(); if (delegate != null) { delegate.onEscapeKey(); } } else if (KeyboardEvent.KeyCode.ENTER == event.getNativeEvent().getKeyCode()) { event.stopPropagation(); event.preventDefault(); if (delegate != null) { delegate.onEnterKey(); } } } }; focusPanel.addDomHandler(handler, KeyDownEvent.getType()); closeButton.addDomHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { if (delegate != null) { delegate.onClose(); } event.stopPropagation(); } }, ClickEvent.getType()); /* Don't start moving the window when clicking close button */ closeButton.addDomHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { event.preventDefault(); event.stopPropagation(); } }, MouseDownEvent.getType()); MouseHandler mouseHandler = new MouseHandler(); header.addDomHandler(mouseHandler, MouseDownEvent.getType()); header.addDomHandler(mouseHandler, MouseUpEvent.getType()); header.addDomHandler(mouseHandler, MouseMoveEvent.getType()); } public void setDelegate(Window.ViewEvents delegate) { this.delegate = delegate; } public void addContentWidget(Widget content) { this.content.add(content); } public Widget getContent() { return this.content; } private void endDragging(MouseUpEvent event) { dragging = false; DOM.releaseCapture(header.getElement()); elemental.dom.Element element = (elemental.dom.Element)contentContainer.getElement(); element.getStyle().setProperty("transition", transition); } private void continueDragging(MouseMoveEvent event) { if (dragging) { int absX = event.getX() + contentContainer.getAbsoluteLeft(); int absY = event.getY() + contentContainer.getAbsoluteTop(); // if the mouse is off the screen to the left, right, or top, don't // move the dialog box. This would let users lose dialog boxes, which // would be bad for modal popups. if (absX < clientLeft || absX >= windowWidth || absY < clientTop) { return; } setPopupPosition(absX - dragStartX, absY - dragStartY); } } private void beginDragging(MouseDownEvent event) { if (DOM.getCaptureElement() == null) { /* * Need to check to make sure that we aren't already capturing an element * otherwise events will not fire as expected. If this check isn't here, * any class which extends custom button will not fire its click event for * example. */ dragging = true; DOM.setCapture(header.getElement()); if ("".equals(contentContainer.getElement().getStyle().getPosition())) { contentContainer.getElement().getStyle().setTop(contentContainer.getAbsoluteTop() + 1, Style.Unit.PX); contentContainer.getElement().getStyle().setLeft(contentContainer.getAbsoluteLeft(), Style.Unit.PX); } else { contentContainer.getElement().getStyle().setTop(contentContainer.getAbsoluteTop(), Style.Unit.PX); contentContainer.getElement().getStyle().setLeft(contentContainer.getAbsoluteLeft(), Style.Unit.PX); } contentContainer.getElement().getStyle().setPosition(Style.Position.ABSOLUTE); elemental.dom.Element element = (elemental.dom.Element)contentContainer.getElement(); transition = element.getStyle().getPropertyValue("transition"); element.getStyle().setProperty("transition", "all 0ms"); dragStartX = event.getX(); dragStartY = event.getY(); } } /** * Sets the popup's position relative to the browser's client area. The * popup's position may be set before calling {@link #setShowing(boolean)}. * * @param left * the left position, in pixels * @param top * the top position, in pixels */ public void setPopupPosition(int left, int top) { // Account for the difference between absolute position and the // body's positioning context. left -= Document.get().getBodyOffsetLeft(); top -= Document.get().getBodyOffsetTop(); // Set the popup's position manually, allowing setPopupPosition() to be // called before show() is called (so a popup can be positioned without it // 'jumping' on the screen). Element elem = contentContainer.getElement(); elem.getStyle().setPropertyPx("left", left); elem.getStyle().setPropertyPx("top", top); } @UiTemplate("View.ui.xml") interface MyBinder extends UiBinder<FlowPanel, View> { } private class MouseHandler implements MouseDownHandler, MouseUpHandler, MouseMoveHandler { public void onMouseDown(MouseDownEvent event) { beginDragging(event); } public void onMouseMove(MouseMoveEvent event) { continueDragging(event); } public void onMouseUp(MouseUpEvent event) { endDragging(event); if (lastFocused != null) { lastFocused.setFocus(true); } } } /** * Sets focus on the last focused child element if such exists. */ public void focusLastFocusedElement() { if (lastFocused != null) { lastFocused.setFocus(true); } } /** * Sets focus on the first child of content panel if such exists. * Otherwise sets focus on first child of footer */ public void setFocus() { if (!setFocusOnChildOf(content)) { setFocusOnChildOf(footer); } } /** * Sets focus on the first focusable child if such exists. * @return <code>true</code> if the focus was set */ private boolean setFocusOnChildOf(Widget widget) { List<FocusWidget> focusableChildren = UIUtil.getFocusableChildren(widget); for (FocusWidget focusableWidget : focusableChildren) { if (focusableWidget.isVisible()) { focusableWidget.setFocus(true); return true; } } return false; } public native boolean isElementFocused(Element element) /*-{ return $doc.activeElement == element; }-*/; }