/*******************************************************************************
* Copyright (c) 2012-2015 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.part;
import org.eclipse.che.ide.api.constraints.Anchor;
import org.eclipse.che.ide.api.constraints.Constraints;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
import org.eclipse.che.ide.api.event.EditorDirtyStateChangedEvent;
import org.eclipse.che.ide.api.mvp.Presenter;
import org.eclipse.che.ide.api.parts.PartPresenter;
import org.eclipse.che.ide.api.parts.PartStack;
import org.eclipse.che.ide.api.parts.PartStackView;
import org.eclipse.che.ide.api.parts.PartStackView.TabItem;
import org.eclipse.che.ide.api.parts.PropertyListener;
import org.eclipse.che.ide.api.parts.base.BasePresenter;
import org.eclipse.che.ide.collections.Array;
import org.eclipse.che.ide.collections.Collections;
import org.eclipse.che.ide.part.projectexplorer.ProjectExplorerPartPresenter;
import org.eclipse.che.ide.workspace.WorkBenchPartController;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.web.bindery.event.shared.EventBus;
import org.eclipse.che.ide.part.projectexplorer.ProjectExplorerPartPresenter;
import org.vectomatic.dom.svg.ui.SVGImage;
import org.vectomatic.dom.svg.ui.SVGResource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implements "Tab-like" UI Component, that accepts PartPresenters as child elements.
* <p/>
* PartStack support "focus" (please don't mix with GWT Widget's Focus feature). Focused PartStack will highlight active Part, notifying
* user what component is currently active.
*
* @author Nikolay Zamosenchuk
* @author Stéphane Daviet
*/
public class PartStackPresenter implements Presenter, PartStackView.ActionDelegate, PartStack {
/** Handles PartStack actions */
public interface PartStackEventHandler {
/** PartStack is being clicked and requests Focus */
void onRequestFocus(PartStack partStack);
}
private HashMap<PartPresenter, Double> partSizes = new HashMap<PartPresenter, Double>();
/** list of parts */
protected final Array<PartPresenter> parts = Collections.createArray();
protected final Array<Integer> viewPartPositions = Collections.createArray();
private Map<String, Constraints> priorityPositionMap = new HashMap<>();
/** view implementation */
protected final PartStackView view;
private final EventBus eventBus;
protected boolean partsClosable = false;
protected PropertyListener propertyListener = new PropertyListener() {
@Override
public void propertyChanged(PartPresenter source, int propId) {
if (PartPresenter.TITLE_PROPERTY == propId) {
updatePartTab(source);
} else if (EditorPartPresenter.PROP_DIRTY == propId) {
eventBus.fireEvent(new EditorDirtyStateChangedEvent(
(EditorPartPresenter)source));
}
}
};
/** current active part */
protected PartPresenter activePart;
protected PartStackEventHandler partStackHandler;
/** Container for every new PartPresenter which will be added to this PartStack. */
protected AcceptsOneWidget partViewContainer;
private WorkBenchPartController workBenchPartController;
@Inject
public PartStackPresenter(EventBus eventBus,
PartStackEventHandler partStackEventHandler,
@Assisted final PartStackView view,
@Assisted WorkBenchPartController workBenchPartController) {
this.view = view;
this.eventBus = eventBus;
partStackHandler = partStackEventHandler;
this.workBenchPartController = workBenchPartController;
partViewContainer = new AcceptsOneWidget() {
@Override
public void setWidget(IsWidget w) {
view.getContentPanel().add(w);
}
};
view.setDelegate(this);
}
/**
* Update part tab, it's may be title, icon or tooltip
*
* @param part
*/
private void updatePartTab(PartPresenter part) {
if (!parts.contains(part)) {
throw new IllegalArgumentException("This part stack not contains: " + part.getTitle());
}
int index = parts.indexOf(part);
view.updateTabItem(index,
part.decorateIcon(
part.getTitleSVGImage() != null ? new SVGImage(part.getTitleSVGImage()) : null),
part.getTitle(),
part.getTitleToolTip(),
part.getTitleWidget()
);
}
/** {@inheritDoc} */
@Override
public void go(AcceptsOneWidget container) {
container.setWidget(view);
if (activePart != null) {
view.setActiveTab(parts.indexOf(activePart));
}
}
/** {@inheritDoc} */
@Override
public void addPart(PartPresenter part) {
addPart(part, null);
}
/** {@inheritDoc} */
@Override
public void addPart(PartPresenter part, Constraints constraint) {
if (parts.contains(part)) {
// part already exists
// activate it
setActivePart(part);
// and return
return;
}
if (part instanceof BasePresenter) {
((BasePresenter)part).setPartStack(this);
}
parts.add(part);
viewPartPositions.add(parts.indexOf(part));
partSizes.put(part, Double.valueOf(part.getSize()));
part.addPropertyListener(propertyListener);
// include close button
SVGResource titleSVGResource = part.getTitleSVGImage();
SVGImage titleSVGImage = null;
if (titleSVGResource != null) {
titleSVGImage = part.decorateIcon(new SVGImage(titleSVGResource));
}
TabItem tabItem =view.addTab(titleSVGImage, part.getTitle(), part.getTitleToolTip(),
part.getTitleWidget(), partsClosable);
bindEvents(tabItem, part);
part.go(partViewContainer);
sortPartsOnView(constraint);
part.onOpen();
// request focus
onRequestFocus();
}
/** {@inheritDoc} */
@Override
public boolean containsPart(PartPresenter part) {
return parts.contains(part);
}
/** {@inheritDoc} */
@Override
public int getNumberOfParts() {
return parts.size();
}
/** {@inheritDoc} */
@Override
public PartPresenter getActivePart() {
return activePart;
}
/** {@inheritDoc} */
@Override
public void setActivePart(PartPresenter part) {
if (activePart == part) {
// request part stack to get the focus
onRequestFocus();
return;
}
// remember size of the previous active part
if (activePart != null && workBenchPartController != null) {
double size = workBenchPartController.getSize();
partSizes.put(activePart, Double.valueOf(size));
}
activePart = part;
if (part == null) {
view.setActiveTab(-1);
workBenchPartController.setHidden(true);
} else {
view.setActiveTab(parts.indexOf(activePart));
}
// request part stack to get the focus
onRequestFocus();
if (activePart != null && workBenchPartController != null) {
workBenchPartController.setHidden(false);
if (partSizes.containsKey(activePart)) {
workBenchPartController.setSize(partSizes.get(activePart));
} else {
workBenchPartController.setSize(activePart.getSize());
}
}
}
/**
* Gets all the parts registered.
*/
public List<PartPresenter> getPartPresenters() {
List<PartPresenter> presenters = new ArrayList<>();
for (int i = 0; i < parts.size(); i++) {
presenters.add(parts.get(i));
}
return presenters;
}
/** {@inheritDoc} */
@Override
public void hidePart(PartPresenter part) {
if (activePart == part) {
if (workBenchPartController != null) {
double size = workBenchPartController.getSize();
partSizes.put(activePart, Double.valueOf(size));
workBenchPartController.setHidden(true);
}
activePart = null;
view.setActiveTab(-1);
}
}
/** {@inheritDoc} */
@Override
public void removePart(PartPresenter part) {
close(part);
}
/**
* Close Part
*
* @param part
*/
protected void close(final PartPresenter part) {
part.onClose(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable throwable) {
}
@Override
public void onSuccess(Void aVoid) {
int partIndex = parts.indexOf(part);
if (activePart == part) {
PartPresenter newActivePart = null;
for (PartPresenter tmpPart : parts.asIterable()) {
if (tmpPart instanceof ProjectExplorerPartPresenter) {
newActivePart = tmpPart;
break;
}
}
setActivePart(newActivePart);
}
view.removeTab(partIndex);
int viewPartPositionsIndex = viewPartPositions.indexOf(partIndex);
if (viewPartPositionsIndex >= 0) {
int lastPosOfViewPart = viewPartPositions.size() - 1;
for (; viewPartPositionsIndex < lastPosOfViewPart; viewPartPositionsIndex++) {
viewPartPositions.set(viewPartPositions.get(viewPartPositionsIndex + 1), viewPartPositionsIndex);
}
viewPartPositions.remove(lastPosOfViewPart);
}
parts.remove(part);
partSizes.remove(part);
part.removePropertyListener(propertyListener);
}
});
}
HandlerRegistration eventsBlocker;
/**
* Bind Activate and Close events to the Tab
*
* @param item
* @param part
*/
protected void bindEvents(final TabItem item, final PartPresenter part) {
item.addCloseHandler(new CloseHandler<PartStackView.TabItem>() {
@Override
public void onClose(CloseEvent<TabItem> event) {
close(part);
}
});
item.addMouseDownHandler(new MouseDownHandler() {
@Override
public void onMouseDown(MouseDownEvent event) {
/* Blocking any events excepting Mouse UP */
eventsBlocker = Event.addNativePreviewHandler(new Event.NativePreviewHandler() {
@Override
public void onPreviewNativeEvent(Event.NativePreviewEvent event) {
if (event.getTypeInt() == Event.ONMOUSEUP) {
eventsBlocker.removeHandler();
return;
}
event.cancel();
event.getNativeEvent().preventDefault();
event.getNativeEvent().stopPropagation();
}
});
if (activePart == part) {
if (partsClosable) {
// request part stack to get the focus
onRequestFocus();
} else {
if (workBenchPartController != null) {
//partsSize = workBenchPartController.getSize();
double size = workBenchPartController.getSize();
partSizes.put(activePart, Double.valueOf(size));
workBenchPartController.setHidden(true);
}
activePart = null;
view.setActiveTab(-1);
}
} else {
// make active
setActivePart(part);
}
}
});
}
/**
* Returns the list of parts.
*
* @return {@link Array} array of parts
*/
protected Array<PartPresenter> getParts() {
return parts;
}
/** {@inheritDoc} */
@Override
public void onRequestFocus() {
partStackHandler.onRequestFocus(PartStackPresenter.this);
}
/** {@inheritDoc} */
@Override
public void setFocus(boolean focused) {
view.setFocus(focused);
}
/**
* Sort parts depending on constraint.
*
* @param constraint
*/
protected void sortPartsOnView(Constraints constraint) {
// TODO remake method of sorting
int oldPartPosition;
int partPositionsSize = viewPartPositions.size();
int positionOfLastElement = viewPartPositions.get(partPositionsSize - 1);
int lastPositionOfSorting = partPositionsSize - 1;
PartPresenter checkPart;
if (partPositionsSize > 1) {
checkPart = parts.get(viewPartPositions.get(partPositionsSize - 2));
Constraints previousConstraint = priorityPositionMap.get(checkPart.getTitle());
if (previousConstraint != null && previousConstraint.myAnchor.equals(Anchor.LAST)) {
oldPartPosition = viewPartPositions.get(partPositionsSize - 2);
viewPartPositions.set(partPositionsSize - 2, viewPartPositions.get(partPositionsSize - 1));
viewPartPositions.set(partPositionsSize - 1, oldPartPosition);
lastPositionOfSorting = partPositionsSize - 2;
}
}
if (constraint != null) {
priorityPositionMap.put(parts.get(positionOfLastElement).getTitle(), constraint);
} else if (priorityPositionMap.size() == 0) {
return;
}
for (int labelOfPartsPos = 0; labelOfPartsPos < partPositionsSize; labelOfPartsPos++) {
checkPart = parts.get(viewPartPositions.get(labelOfPartsPos));
Constraints localeConstraint = priorityPositionMap.get(checkPart.getTitle());
if (localeConstraint != null) {
if (localeConstraint.myAnchor == Anchor.LAST) {
if (viewPartPositions.get(labelOfPartsPos) != positionOfLastElement) {
oldPartPosition = viewPartPositions.get(labelOfPartsPos);
for (int partPosition = labelOfPartsPos; partPosition < partPositionsSize - 1; partPosition++) {
viewPartPositions.set(partPosition, viewPartPositions.get(partPosition + 1));
}
viewPartPositions.set(partPositionsSize - 1, oldPartPosition);
}
continue;
} else if (localeConstraint.myAnchor == Anchor.FIRST) {
if (viewPartPositions.get(labelOfPartsPos) != 0) {
oldPartPosition = viewPartPositions.get(labelOfPartsPos);
for (int partPosition = labelOfPartsPos; partPosition > 0; partPosition--) {
viewPartPositions.set(partPosition, viewPartPositions.get(partPosition - 1));
}
viewPartPositions.set(0, oldPartPosition);
}
continue;
} else if (localeConstraint.myAnchor == Anchor.BEFORE) {
if (partPositionsSize > labelOfPartsPos + 1) {
if (parts.get(viewPartPositions.get(labelOfPartsPos + 1)).getTitle().equals(localeConstraint.myRelativeToActionId))
continue;
}
} else {//Anchor.AFTER
if (labelOfPartsPos > 1) {
if (parts.get(viewPartPositions.get(labelOfPartsPos - 1)).getTitle().equals(localeConstraint.myRelativeToActionId))
continue;
}
}
if (labelOfPartsPos < lastPositionOfSorting) {
oldPartPosition = viewPartPositions.get(labelOfPartsPos);
for (int partPosition = labelOfPartsPos; partPosition < lastPositionOfSorting; partPosition++) {
viewPartPositions.set(partPosition, viewPartPositions.get(partPosition + 1));
}
viewPartPositions.set(lastPositionOfSorting, oldPartPosition);
}
oldPartPosition = viewPartPositions.get(labelOfPartsPos);
for (int partPosition = lastPositionOfSorting; partPosition > 0; partPosition--) {
if (parts.get(viewPartPositions.get(partPosition - 1)).getTitle().equals(localeConstraint.myRelativeToActionId)) {
if (localeConstraint.myAnchor == Anchor.BEFORE) {
viewPartPositions.set(partPosition, oldPartPosition);
} else {
if (partPosition > 1) {
viewPartPositions.set(partPosition, viewPartPositions.get(partPosition - 1));
viewPartPositions.set(partPosition - 1, oldPartPosition);
}
}
break;
} else {
if (partPosition > 1) viewPartPositions.set(partPosition, viewPartPositions.get(partPosition - 1));
}
}
}
}
view.setTabpositions(viewPartPositions);
}
}