/*******************************************************************************
* Copyright (c) 2011, 2016 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 483842, 488537
******************************************************************************/
package org.eclipse.e4.ui.workbench.addons.cleanupaddon;
import javax.inject.Inject;
import org.eclipse.core.runtime.Assert;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.di.UIEventTopic;
import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.advanced.MArea;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspectiveStack;
import org.eclipse.e4.ui.model.application.ui.basic.MCompositePart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainer;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;
import org.eclipse.e4.ui.workbench.IPresentationEngine;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.renderers.swt.SashLayout;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.osgi.service.event.Event;
public class CleanupAddon {
@Inject
IEventBroker eventBroker;
@Inject
EModelService modelService;
@Inject
MApplication app;
@Inject
@Optional
private void subscribeTopicChildren(@UIEventTopic(UIEvents.ElementContainer.TOPIC_CHILDREN) Event event) {
Display display = Display.getCurrent();
Assert.isNotNull(display);
Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
// cleanup add-on only cares about removals and if display is not null
if (!UIEvents.isREMOVE(event)) {
return;
}
final MElementContainer<?> container = (MElementContainer<?>) changedObj;
MUIElement containerParent = container.getParent();
// Determine the elements that should *not* ever be auto-destroyed
if (container instanceof MApplication || container instanceof MPerspectiveStack
|| container instanceof MMenuElement || container instanceof MTrimBar || container instanceof MToolBar
|| container instanceof MArea || container.getTags().contains(IPresentationEngine.NO_AUTO_COLLAPSE)) {
return;
}
if (container instanceof MWindow && containerParent instanceof MApplication) {
return;
}
// Stall the removal to handle cases where the container is only
// transiently empty
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
// Remove it from the display if no visible children
int tbrCount = modelService.toBeRenderedCount(container);
// Cache the value since setting the TBR may change the
// result
boolean lastStack = isLastEditorStack(container);
if (tbrCount == 0 && !lastStack) {
container.setToBeRendered(false);
}
// Remove it from the model if it has no children at all
MElementContainer<?> lclContainer = container;
if (lclContainer.getChildren().size() == 0) {
MElementContainer<MUIElement> parent = container.getParent();
if (parent != null && !lastStack) {
container.setToBeRendered(false);
parent.getChildren().remove(container);
} else if (container instanceof MWindow) {
// Must be a Detached Window
MUIElement eParent = (MUIElement) ((EObject) container).eContainer();
if (eParent instanceof MPerspective) {
((MPerspective) eParent).getWindows().remove(container);
} else if (eParent instanceof MWindow) {
((MWindow) eParent).getWindows().remove(container);
}
}
} else if (container.getChildren().size() == 1 && container instanceof MPartSashContainer) {
// if a sash container has only one element then remove
// it and move
// its child up to where it used to be
MUIElement theChild = container.getChildren().get(0);
MElementContainer<MUIElement> parentContainer = container.getParent();
if (parentContainer != null) {
int index = parentContainer.getChildren().indexOf(container);
// Magic check, are we unwrapping a sash container
if (theChild instanceof MPartSashContainer) {
if (container.getWidget() instanceof Composite) {
Composite theComp = (Composite) container.getWidget();
Object tmp = theChild.getWidget();
theChild.setWidget(theComp);
theComp.setLayout(new SashLayout(theComp, theChild));
theComp.setData(AbstractPartRenderer.OWNING_ME, theChild);
container.setWidget(tmp);
}
}
theChild.setContainerData(container.getContainerData());
container.getChildren().remove(theChild);
parentContainer.getChildren().add(index, theChild);
container.setToBeRendered(false);
parentContainer.getChildren().remove(container);
}
}
}
});
}
/**
* Returns true if and only if the given element should make itself visible
* when its first child becomes visible and make itself invisible whenever
* its last child becomes invisible. Defaults to false for unknown element
* types
*/
private static boolean shouldReactToChildVisibilityChanges(MUIElement theElement) {
// TODO: It may be possible to remove the instanceof checks and just use
// IPresentationEngine.HIDDEN_EXPLICITLY. However, that would require
// explicitly setting IPresentationEngine.HIDDEN_EXPLICITLY on every
// object where we want to keep it hidden even if it has a visible child
// (such as the main toolbar).
return (theElement instanceof MPartSashContainer || theElement instanceof MPartStack
|| theElement instanceof MCompositePart)
&& !theElement.getTags().contains(IPresentationEngine.HIDDEN_EXPLICITLY);
}
@Inject
@Optional
private void subscribeVisibilityChanged(
@UIEventTopic(UIEvents.UIElement.TOPIC_VISIBLE) Event event) {
MUIElement changedObj = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
if (changedObj instanceof MTrimBar || ((Object) changedObj.getParent()) instanceof MToolBar) {
return;
}
if (changedObj.getWidget() instanceof Shell) {
((Shell) changedObj.getWidget()).setVisible(changedObj.isVisible());
} else if (changedObj.getWidget() instanceof Rectangle) {
MElementContainer<MUIElement> parent = changedObj.getParent();
if (!shouldReactToChildVisibilityChanges(parent)) {
return;
}
if (changedObj.isVisible()) {
// Make all the parents visible
if (!parent.isVisible()) {
parent.setVisible(true);
}
} else {
// If there are no more 'visible' children then make the parent go away too
boolean makeInvisible = true;
for (MUIElement kid : parent.getChildren()) {
if (kid.isToBeRendered() && kid.isVisible()) {
makeInvisible = false;
break;
}
}
if (makeInvisible) {
parent.setVisible(false);
}
}
} else if (changedObj.getWidget() instanceof Control) {
Control ctrl = (Control) changedObj.getWidget();
MElementContainer<MUIElement> parent = changedObj.getParent();
if (parent == null || ((Object) parent) instanceof MToolBar) {
return;
}
if (changedObj.isVisible()) {
if (parent.getRenderer() != null) {
Object myParent = ((AbstractPartRenderer) parent.getRenderer())
.getUIContainer(changedObj);
if (myParent instanceof Composite) {
Composite parentComp = (Composite) myParent;
ctrl.setParent(parentComp);
Control prevControl = null;
for (MUIElement childME : parent.getChildren()) {
if (childME == changedObj) {
break;
}
if (childME.getWidget() instanceof Control && childME.isVisible()) {
prevControl = (Control) childME.getWidget();
}
}
if (prevControl != null) {
ctrl.moveBelow(prevControl);
} else {
ctrl.moveAbove(null);
}
ctrl.requestLayout();
}
if (!shouldReactToChildVisibilityChanges(parent)) {
return;
}
// Check if the parent is visible
if (!parent.isVisible()) {
parent.setVisible(true);
}
}
} else {
Shell limbo = (Shell) app.getContext().get("limbo");
// Reparent the control to 'limbo'
Composite curParent = ctrl.getParent();
ctrl.setParent(limbo);
curParent.requestLayout();
// Always leave Window's in the presentation
if ((Object) parent instanceof MWindow) {
return;
}
// If there are no more 'visible' children then make the parent go away too
boolean makeParentInvisible = true;
for (MUIElement kid : parent.getChildren()) {
if (kid.isToBeRendered() && kid.isVisible()) {
makeParentInvisible = false;
break;
}
}
if (isLastEditorStack(parent) && makeParentInvisible) {
return;
}
if (!shouldReactToChildVisibilityChanges(parent)) {
return;
}
// Special check: If a perspective goes invisibe we need to make its
// PerspectiveStack invisible as well...see bug 369528
if (makeParentInvisible) {
parent.setVisible(false);
}
}
}
}
@Inject
@Optional
private void subscribeRenderingChanged(
@UIEventTopic(UIEvents.UIElement.TOPIC_TOBERENDERED) Event event) {
MUIElement changedObj = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
MElementContainer<MUIElement> container = null;
if (changedObj.getCurSharedRef() != null) {
container = changedObj.getCurSharedRef().getParent();
} else {
container = changedObj.getParent();
}
// this can happen for shared parts that aren't attached to any placeholders
if (container == null) {
return;
}
// never hide top-level windows
MUIElement containerElement = container;
if (containerElement instanceof MWindow && containerElement.getParent() != null) {
return;
}
// These elements should neither be shown nor hidden based on their containment state
if (isLastEditorStack(containerElement) || containerElement instanceof MPerspective
|| containerElement instanceof MPerspectiveStack) {
return;
}
Boolean toBeRendered = (Boolean) event.getProperty(UIEvents.EventTags.NEW_VALUE);
if (toBeRendered) {
// Bring the container back if one of its children goes visible
if (!container.isToBeRendered()) {
container.setToBeRendered(true);
}
if (!container.isVisible()
&& !container.getTags().contains(IPresentationEngine.MINIMIZED)) {
container.setVisible(true);
}
} else {
// Never hide the container marked as no_close
if (container.getTags().contains(IPresentationEngine.NO_AUTO_COLLAPSE)) {
return;
}
int visCount = modelService.countRenderableChildren(container);
// Remove stacks with no visible children from the display (but not the
// model)
final MElementContainer<MUIElement> theContainer = container;
if (visCount == 0) {
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
int visCount = modelService.countRenderableChildren(theContainer);
if (!isLastEditorStack(theContainer) && visCount == 0) {
theContainer.setToBeRendered(false);
}
}
});
} else {
// if there are rendered elements but none are 'visible' we should
// make the container invisible as well
boolean makeInvisible = true;
// OK, we have rendered children, are they 'visible' ?
for (MUIElement kid : container.getChildren()) {
if (!kid.isToBeRendered()) {
continue;
}
if (kid.isVisible()) {
makeInvisible = false;
break;
}
}
if (makeInvisible) {
container.setVisible(false);
}
}
}
}
boolean isLastEditorStack(MUIElement element) {
return modelService.isLastEditorStack(element);
}
}