/*******************************************************************************
* 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.plugin.svn.ide.copy;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.ide.api.data.tree.Node;
import org.eclipse.che.ide.api.data.tree.settings.SettingsProvider;
import org.eclipse.che.ide.api.resources.Project;
import org.eclipse.che.ide.resources.tree.ContainerNode;
import org.eclipse.che.ide.resources.tree.ResourceNode;
import org.eclipse.che.ide.resources.tree.SkipHiddenNodesInterceptor;
import org.eclipse.che.ide.resources.tree.SkipLeafsInterceptor;
import org.eclipse.che.ide.ui.Tooltip;
import org.eclipse.che.ide.ui.menu.PositionController;
import org.eclipse.che.ide.ui.smartTree.NodeLoader;
import org.eclipse.che.ide.ui.smartTree.NodeStorage;
import org.eclipse.che.ide.ui.smartTree.SelectionModel;
import org.eclipse.che.ide.ui.smartTree.Tree;
import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent;
import org.eclipse.che.ide.ui.window.Window;
import org.eclipse.che.plugin.svn.ide.SubversionExtensionLocalizationConstants;
import org.eclipse.che.plugin.svn.ide.SubversionExtensionResources;
import org.vectomatic.dom.svg.OMSVGSVGElement;
import java.util.List;
/**
* Implementation of {@link CopyView}.
*
* @author Vladyslav Zhukovskyi
*/
@Singleton
public class CopyViewImpl extends Window implements CopyView {
interface CopyViewImplUiBinder extends UiBinder<Widget, CopyViewImpl> {
}
private static CopyViewImplUiBinder uiBinder = GWT.create(CopyViewImplUiBinder.class);
Button btnCopy;
Button btnCancel;
@UiField
DockLayoutPanel treeContainer;
@UiField
TextBox newNameTextBox;
@UiField
CheckBox sourceCheckBox;
@UiField
CheckBox targetCheckBox;
@UiField
DeckPanel deckPanel;
@UiField
Label sourceLabel;
@UiField
TextBox targetUrlTextBox;
@UiField
TextBox sourceTextBox;
@UiField
TextBox commentTextBox;
@UiField
DeckPanel commentDeckPanel;
@UiField(provided = true)
SubversionExtensionResources resources;
@UiField(provided = true)
SubversionExtensionLocalizationConstants constants;
private final ResourceNode.NodeFactory nodeFactory;
private final SettingsProvider settingsProvider;
private CopyView.ActionDelegate delegate;
private Tree tree;
private OMSVGSVGElement alertMarker;
private static final String PLACEHOLDER = "placeholder";
private static final String PLACEHOLDER_DUMMY = "https://subversion.site.com/svn/sht_site/trunk";
private static final int ANIMATION_DURATION = 350;
@Inject
public CopyViewImpl(SubversionExtensionResources resources,
SubversionExtensionLocalizationConstants constants,
SkipHiddenNodesInterceptor skipHiddenNodesInterceptor,
SkipLeafsInterceptor skipLeafsInterceptor,
ResourceNode.NodeFactory nodeFactory,
SettingsProvider settingsProvider) {
this.resources = resources;
this.constants = constants;
this.nodeFactory = nodeFactory;
this.settingsProvider = settingsProvider;
this.ensureDebugId("svn-copy-window");
this.setWidget(uiBinder.createAndBindUi(this));
btnCancel = createButton(constants.buttonCancel(), "svn-copy-cancel", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
delegate.onCancelClicked();
}
});
addButtonToFooter(btnCancel);
btnCopy = createButton("Copy", "svn-copy-copy", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
delegate.onCopyClicked();
}
});
addButtonToFooter(btnCopy);
alertMarker = resources.alert().getSvg();
alertMarker.getStyle().setWidth(22, Style.Unit.PX);
alertMarker.getStyle().setHeight(22, Style.Unit.PX);
alertMarker.getStyle().setMargin(10, Style.Unit.PX);
getFooter().getElement().appendChild(alertMarker.getElement());
alertMarker.getStyle().setVisibility(Style.Visibility.HIDDEN);
tree = new Tree(new NodeStorage(), new NodeLoader(Sets.newHashSet(skipHiddenNodesInterceptor, skipLeafsInterceptor)));
tree.getSelectionModel().setSelectionMode(SelectionModel.Mode.SINGLE);
tree.getSelectionModel().addSelectionChangedHandler(new SelectionChangedEvent.SelectionChangedHandler() {
@Override
public void onSelectionChanged(SelectionChangedEvent event) {
final List<Node> selection = event.getSelection();
if (selection == null || selection.isEmpty()) {
return;
}
final Node node = selection.get(0);
if (node instanceof ResourceNode) {
delegate.onNodeSelected(((ResourceNode)node).getData());
}
}
});
treeContainer.add(tree);
}
/** {@inheritDoc} */
@Override
public void setDialogTitle(String title) {
super.setTitle(title);
}
/** {@inheritDoc} */
@Override
public void setNewName(String name) {
newNameTextBox.getElement().setAttribute(PLACEHOLDER, name);
}
/** {@inheritDoc} */
@Override
public String getNewName() {
if (!Strings.isNullOrEmpty(newNameTextBox.getText())) {
return newNameTextBox.getText();
} else {
return newNameTextBox.getElement().getAttribute(PLACEHOLDER);
}
}
/** {@inheritDoc} */
@Override
public String getSourcePath() {
return sourceTextBox.getText();
}
/** {@inheritDoc} */
@Override
public void setSourcePath(String path, boolean editable) {
sourceTextBox.setText(path);
sourceTextBox.setEnabled(editable);
sourceTextBox.getElement().setAttribute(PLACEHOLDER, PLACEHOLDER_DUMMY);
}
@UiHandler("newNameTextBox")
@SuppressWarnings("unused")
public void onNewNameFieldChanged(KeyUpEvent event) {
delegate.onNewNameChanged(newNameTextBox.getText());
setComment(getNewName());
}
@UiHandler("targetCheckBox")
@SuppressWarnings("unused")
public void onDestinationUrlCheckBoxActivated(ClickEvent event) {
new SlideAnimation()
.showWidget(deckPanel.getWidget(targetCheckBox.getValue() ? 0 : 1), deckPanel.getWidget(targetCheckBox.getValue() ? 1 : 0));
delegate.onTargetCheckBoxChanged();
new SlideAnimation().showWidget(commentDeckPanel.getWidget(targetCheckBox.getValue() ? 1 : 0),
commentDeckPanel.getWidget(targetCheckBox.getValue() ? 0 : 1));
}
@UiHandler("sourceCheckBox")
@SuppressWarnings("unused")
public void onSourceUrlCheckBoxActivated(ClickEvent event) {
sourceLabel.setText(sourceCheckBox.getValue() ? "URL:" : "Path:");
delegate.onSourceCheckBoxChanged();
setComment(getNewName());
}
@UiHandler("targetUrlTextBox")
@SuppressWarnings("unused")
public void onTargetUrlTextBoxChanged(KeyUpEvent event) {
delegate.onTargetUrlChanged();
}
@UiHandler("sourceTextBox")
@SuppressWarnings("unused")
public void onSourceTextBoxChanged(KeyUpEvent event) {
delegate.onSourcePathChanged();
final String[] parts = sourceTextBox.getText().trim().split("/");
final String projectName = parts[parts.length - 1];
newNameTextBox.getElement().setAttribute(PLACEHOLDER, Strings.isNullOrEmpty(projectName) ? "name" : projectName);
setComment(Strings.isNullOrEmpty(projectName) ? "name" : projectName);
}
/** {@inheritDoc} */
@Override
public void setDelegate(final CopyView.ActionDelegate delegate) {
this.delegate = delegate;
}
/** {@inheritDoc} */
@Override
public void setProjectNode(Project project) {
final ContainerNode node = nodeFactory.newContainerNode(project, settingsProvider.getSettings());
tree.getNodeStorage().clear();
tree.getNodeStorage().add(node);
tree.setExpanded(node, true);
}
/** {@inheritDoc} */
@Override
public void show() {
new SlideAnimation().showWidget(null, deckPanel.getWidget(0));
sourceLabel.setText("Path:");
newNameTextBox.setText(null);
sourceCheckBox.setValue(false);
targetCheckBox.setValue(false);
targetUrlTextBox.setText(null);
targetUrlTextBox.getElement().setAttribute(PLACEHOLDER, PLACEHOLDER_DUMMY);
super.show();
}
/** {@inheritDoc} */
@Override
public void showErrorMarker(String message) {
alertMarker.getStyle().setVisibility(Style.Visibility.VISIBLE);
Tooltip.create((elemental.dom.Element)alertMarker.getElement(),
PositionController.VerticalAlign.TOP,
PositionController.HorizontalAlign.MIDDLE,
message);
btnCopy.setEnabled(false);
}
/** {@inheritDoc} */
@Override
public void hideErrorMarker() {
alertMarker.getStyle().setVisibility(Style.Visibility.HIDDEN);
btnCopy.setEnabled(true);
}
/** {@inheritDoc} */
@Override
public boolean isSourceCheckBoxSelected() {
return sourceCheckBox.getValue();
}
/** {@inheritDoc} */
@Override
public boolean isTargetCheckBoxSelected() {
return targetCheckBox.getValue();
}
/** {@inheritDoc} */
@Override
public String getTargetUrl() {
return targetUrlTextBox.getText();
}
/** {@inheritDoc} */
@Override
public void setComment(String comment) {
commentTextBox.getElement().setAttribute(PLACEHOLDER, "copy '" + comment + "'");
}
/** {@inheritDoc} */
@Override
public String getComment() {
if (!Strings.isNullOrEmpty(commentTextBox.getText())) {
return commentTextBox.getText();
} else {
return commentTextBox.getElement().getAttribute(PLACEHOLDER);
}
}
private class SlideAnimation extends Animation {
/**
* The {@link com.google.gwt.dom.client.Element} holding the {@link Widget} with a lower index.
*/
private Element container1 = null;
/**
* The {@link Element} holding the {@link Widget} with a higher index.
*/
private Element container2 = null;
/**
* A boolean indicating whether container1 is growing or shrinking.
*/
private boolean growing = false;
/**
* The fixed height of a {@link com.google.gwt.user.client.ui.TabPanel} in pixels. If the {@link com.google.gwt.user.client.ui
* .TabPanel}
* does not have a fixed height, this will be set to -1.
*/
private int fixedHeight = -1;
/**
* The old {@link Widget} that is being hidden.
*/
private Widget oldWidget = null;
/**
* Switch to a new {@link Widget}.
*
* @param oldWidget
* the {@link Widget} to hide
* @param newWidget
* the {@link Widget} to show
*/
public void showWidget(Widget oldWidget, Widget newWidget) {
// Immediately complete previous animation
cancel();
// Get the container and index of the new widget
Element newContainer = DOM.getParent(newWidget.getElement());
int newIndex = DOM.getChildIndex(DOM.getParent(newContainer),
newContainer);
// If we aren't showing anything, don't bother with the animation
if (oldWidget == null) {
UIObject.setVisible(newContainer, true);
newWidget.setVisible(true);
return;
}
this.oldWidget = oldWidget;
// Get the container and index of the old widget
Element oldContainer = DOM.getParent(oldWidget.getElement());
int oldIndex = DOM.getChildIndex(DOM.getParent(oldContainer),
oldContainer);
// Figure out whether to grow or shrink the container
if (newIndex > oldIndex) {
container1 = oldContainer;
container2 = newContainer;
growing = false;
} else {
container1 = newContainer;
container2 = oldContainer;
growing = true;
}
// Figure out if the deck panel has a fixed height
com.google.gwt.dom.client.Element deckElem = container1.getParentElement();
int deckHeight = deckElem.getOffsetHeight();
if (growing) {
fixedHeight = container2.getOffsetHeight();
container2.getStyle().setPropertyPx("height", Math.max(1, fixedHeight - 1));
} else {
fixedHeight = container1.getOffsetHeight();
container1.getStyle().setPropertyPx("height", Math.max(1, fixedHeight - 1));
}
if (deckElem.getOffsetHeight() != deckHeight) {
fixedHeight = -1;
}
// Only scope to the deck if it's fixed height, otherwise it can affect
// the rest of the page, even if it's not visible to the user.
run(ANIMATION_DURATION, fixedHeight == -1 ? null : deckElem);
// We call newWidget.setVisible(true) immediately after showing the
// widget's container so users can delay render their widget. Ultimately,
// we should have a better way of handling this, but we need to call
// setVisible for legacy support.
newWidget.setVisible(true);
}
@Override
protected void onComplete() {
if (growing) {
container1.getStyle().setProperty("height", "100%");
UIObject.setVisible(container1, true);
UIObject.setVisible(container2, false);
container2.getStyle().setProperty("height", "100%");
} else {
UIObject.setVisible(container1, false);
container1.getStyle().setProperty("height", "100%");
container2.getStyle().setProperty("height", "100%");
UIObject.setVisible(container2, true);
}
container1.getStyle().setProperty("overflow", "visible");
container2.getStyle().setProperty("overflow", "visible");
container1 = null;
container2 = null;
hideOldWidget();
}
@Override
protected void onStart() {
// Start the animation
container1.getStyle().setProperty("overflow", "hidden");
container2.getStyle().setProperty("overflow", "hidden");
onUpdate(0.0);
UIObject.setVisible(container1, true);
UIObject.setVisible(container2, true);
}
@Override
protected void onUpdate(double progress) {
if (!growing) {
progress = 1.0 - progress;
}
// Container1 expands (shrinks) to its target height
int height1;
int height2;
if (fixedHeight == -1) {
height1 = (int)(progress * container1.getPropertyInt("scrollHeight"));
height2 = (int)((1.0 - progress) * container2.getPropertyInt("scrollHeight"));
} else {
height1 = (int)(progress * fixedHeight);
height2 = fixedHeight - height1;
}
// Issue 2339: If the height is 0px, IE7 will display the entire content
// widget instead of hiding it completely.
if (height1 == 0) {
height1 = 1;
height2 = Math.max(1, height2 - 1);
} else if (height2 == 0) {
height2 = 1;
height1 = Math.max(1, height1 - 1);
}
container1.getStyle().setProperty("height", height1 + "px");
container2.getStyle().setProperty("height", height2 + "px");
}
/**
* Hide the old widget when the animation completes.
*/
private void hideOldWidget() {
// Issue 2510: Hiding the widget isn't necessary because we hide its
// wrapper, but its in here for legacy support.
oldWidget.setVisible(false);
oldWidget = null;
}
}
}