blob: 12a2ad8ef733ed72a10670b29772452ac585c3cd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 BestSolution.at 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:
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
*******************************************************************************/
package org.eclipse.fx.ui.workbench.renderers.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.services.events.IEventBroker;
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.MPlaceholder;
import org.eclipse.e4.ui.model.application.ui.basic.MCompositePart;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MStackElement;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.modeling.EPartService;
import org.eclipse.fx.ui.workbench.base.rendering.ElementRenderer;
import org.eclipse.fx.ui.workbench.base.rendering.RendererFactory;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WCallback;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WLayoutedWidget;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WStack;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WStack.WStackItem;
import org.eclipse.fx.ui.workbench.services.ELifecycleService;
import org.eclipse.fx.ui.workbench.services.lifecycle.annotation.PreClose;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
/**
* Base renderer for {@link MPartStack}
*
* @param <N>
* the native widget
* @param <I>
* the item type
* @param <IC>
* the native item content widget type
*/
public abstract class BaseStackRenderer<N, I, IC> extends BaseRenderer<MPartStack, WStack<N, I, IC>> {
private static final String MAP_ITEM_KEY = "fx.rendering.stackitem"; //$NON-NLS-1$
/**
* Transient tag to inform about a move operation
*/
public final static String MAP_MOVE = "fx.rendering.stackitem.move"; //$NON-NLS-1$
/**
* Tag key used to mark a menu as a tab-menu
*/
public final static String TAG_TAB_CONTEXT_MENU = "tabmenu"; //$NON-NLS-1$
@Inject
RendererFactory factory;
@Inject
MApplication application;
@Inject
ELifecycleService lifecycleService;
boolean inLazyInit;
private @NonNull RendererFactory getFactory() {
RendererFactory factory = this.factory;
if( factory == null ) {
throw new IllegalStateException("Renderfactory must not be null"); //$NON-NLS-1$
}
return factory;
}
@PostConstruct
void init(IEventBroker eventBroker) {
//TODO Switch to EventProcessor.attachChildProcessor
eventBroker.subscribe(UIEvents.ElementContainer.TOPIC_CHILDREN, new EventHandler() {
@Override
public void handleEvent(Event event) {
Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
if (changedObj instanceof MPartStack) {
MPartStack parent = (MPartStack) changedObj;
if (BaseStackRenderer.this == parent.getRenderer()) {
if (UIEvents.isADD(event)) {
handleChildrenAddition(parent, Util.<MStackElement> asCollection(event, UIEvents.EventTags.NEW_VALUE));
} else if (UIEvents.isREMOVE(event)) {
handleChildrenRemove(parent, Util.<MStackElement> asCollection(event, UIEvents.EventTags.OLD_VALUE));
}
}
}
}
});
eventBroker.subscribe(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT, new EventHandler() {
@Override
public void handleEvent(Event event) {
Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
if (changedObj instanceof MPartStack) {
MPartStack parent = (MPartStack) changedObj;
if (BaseStackRenderer.this == parent.getRenderer()) {
String eventType = (String) event.getProperty(UIEvents.EventTags.TYPE);
if (UIEvents.EventTypes.SET.equals(eventType)) {
MUIElement newValue = (MUIElement) event.getProperty(UIEvents.EventTags.NEW_VALUE);
MUIElement oldValue = (MUIElement) event.getProperty(UIEvents.EventTags.OLD_VALUE);
handleSelectedElement(parent, (MStackElement) oldValue, (MStackElement) newValue);
}
}
}
}
});
EventProcessor.attachVisibleProcessor(eventBroker, this);
}
@Nullable
MPart getPart(@NonNull MUIElement tmp) {
MUIElement element = tmp;
if( element instanceof MPart ) {
return (MPart) element;
} else if (element instanceof MPlaceholder) {
return (MPart) ((MPlaceholder) element).getRef();
} else if (element instanceof MElementContainer<?>) {
@SuppressWarnings("unchecked")
MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) element;
element = container.getSelectedElement();
if ((element == null) && !container.getChildren().isEmpty()) {
element = container.getChildren().get(0);
}
if( element != null ) {
return getPart(element);
}
return null;
} else {
return (MPart) element;
}
}
@Override
protected void initWidget(final MPartStack element, final WStack<N, I, IC> widget) {
super.initWidget(element, widget);
// widget.setMinMaxState(WMinMaxState.MAXIMIZED);
widget.setMouseSelectedItemCallback(new WCallback<@NonNull WStackItem<I, IC>, Void>() {
@Override
public Void call(WStackItem<I, IC> param) {
MStackElement domElement = param.getDomElement();
if (domElement != null) {
MPart part = getPart(domElement);
if( part != null ) {
activatationJob(element, part, true);
} else {
getLogger().error("Unable to find part to activate for '"+domElement+"'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return null;
}
});
widget.setKeySelectedItemCallback(new WCallback<@NonNull WStackItem<I, IC>, Void>() {
@Override
public Void call(WStackItem<I, IC> param) {
MStackElement domElement = param.getDomElement();
if (domElement != null) {
MPart part = getPart(domElement);
if( part != null ) {
activatationJob(element, part, false);
} else {
getLogger().error("Unable to find part to activate for '"+domElement+"'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return null;
}
});
widget.registerActivationCallback(new WCallback<Boolean, Void>() {
@Override
public Void call(Boolean param) {
MStackElement selectedElement = element.getSelectedElement();
if (param.booleanValue() && selectedElement != null) {
MPart part = getPart(selectedElement);
if( part != null ) {
activatationJob(element, part, true);
} else {
getLogger().error("Unable to find part to activate for '"+selectedElement+"'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return null;
}
});
}
void activatationJob(@NonNull MPartStack stackToActivate, final @NonNull MPart p, final boolean focus) {
if (shouldActivate(stackToActivate)) {
activate(p, focus);
}
}
private boolean shouldActivate(@NonNull MPartStack stackToActivate) {
if (inContentProcessing(stackToActivate)) {
return false;
}
if (this.application != null) {
IEclipseContext applicationContext = this.application.getContext();
IEclipseContext activeChild = applicationContext.getActiveChild();
if (activeChild == null || activeChild.get(MWindow.class) != this.application.getSelectedElement() || this.application.getSelectedElement() != this.modelService.getTopLevelWindowFor(stackToActivate)) {
return false;
}
}
return true;
}
@SuppressWarnings("null")
@Override
public void doProcessContent(MPartStack element) {
WStack<N, I, IC> stack = getWidget(element);
if( stack == null ) {
getLogger().error("Could not find widget for '"+element+"'"); //$NON-NLS-1$//$NON-NLS-2$
return;
}
List<@NonNull WStackItem<I, IC>> items = new ArrayList<>();
WStackItem<I, IC> initalItem = null;
for (MStackElement e : element.getChildren()) {
// Precreate the rendering context for the subitem
ElementRenderer<@NonNull MStackElement, ?> renderer = getFactory().getRenderer(e);
if (renderer != null && isChildRenderedAndVisible(e)) {
WStackItem<I, IC> item = createStackItem(stack, e, renderer);
items.add(item);
if (e == element.getSelectedElement()) {
initalItem = item;
}
}
}
if (!items.isEmpty()) {
if (initalItem == null || items.size() == 1 || items.get(0) == initalItem) {
stack.addItems(items);
} else {
stack.addItem(initalItem);
if (items.get(items.size() - 1) == initalItem) {
stack.addItems(0, items.subList(0, items.size() - 1));
} else {
int idx = items.indexOf(initalItem);
stack.addItems(0, items.subList(0, idx));
stack.addItems(items.subList(idx + 1, items.size()));
}
}
}
stack.selectItem(stack.getItems().indexOf(initalItem));
// Ensure an element is selected see 436659
if( element.getSelectedElement() == null ) {
if( ! stack.getItems().isEmpty() ) {
element.setSelectedElement(stack.getItems().get(0).getDomElement());
}
}
}
@NonNull
private WStackItem<I, IC> createStackItem(@NonNull WStack<N, I, IC> stack, final @NonNull MStackElement e, @NonNull ElementRenderer<MStackElement, ?> renderer) {
IEclipseContext context = renderer.setupRenderingContext(e);
WStackItem<I, IC> item = ContextInjectionFactory.make(stack.getStackItemClass(), context);
e.getTransientData().put(MAP_ITEM_KEY, item);
if( item == null ) {
throw new IllegalStateException("The item must not be null"); //$NON-NLS-1$
}
item.setDomElement(e);
item.setInitCallback(new WCallback<@NonNull WStackItem<I, IC>, @Nullable IC>() {
@SuppressWarnings("unchecked")
@Override
public IC call(WStackItem<I, IC> param) {
BaseStackRenderer.this.inLazyInit = true;
try {
WLayoutedWidget<MStackElement> widget = engineCreateWidget(e);
if (widget != null) {
return (IC) widget.getStaticLayoutNode();
}
return null;
} finally {
BaseStackRenderer.this.inLazyInit = false;
}
}
});
item.setOnCloseCallback(new WCallback<@NonNull WStackItem<I, IC>, Boolean>() {
@Override
public Boolean call(WStackItem<I, IC> param) {
return Boolean.valueOf(!handleStackItemClose(e, param));
}
});
return item;
}
@SuppressWarnings("null")
void handleChildrenAddition(MPartStack parent, Collection<MStackElement> elements) {
WStack<N, I, IC> widget = getWidget(parent);
if( widget == null ) {
getLogger().error("Could not find widget for '"+parent+"'"); //$NON-NLS-1$//$NON-NLS-2$
return;
}
Iterator<MStackElement> i = elements.iterator();
while (i.hasNext()) {
MStackElement element = (MStackElement) i.next();
if (isChildRenderedAndVisible(element)) {
int idx = getRenderedIndex(parent, element);
ElementRenderer<@NonNull MStackElement, ?> renderer = getFactory().getRenderer(element);
if( renderer != null ) {
WStack<N, I, IC> stack = widget;
@SuppressWarnings("unchecked")
WStackItem<I, IC> item = (WStackItem<I, IC>) element.getTransientData().get(MAP_ITEM_KEY);
if( item == null || ! widget.getStackItemClass().isAssignableFrom(item.getClass()) ) {
item = createStackItem(widget, element, renderer);
}
stack.addItems(idx, Collections.singletonList(item));
} else {
getLogger().error("Could not find renderer for '"+element+"'"); //$NON-NLS-1$//$NON-NLS-2$
}
}
}
//TODO THIS NEEDS TO BE MOVED TO THE CHILD ADDITION HANDLER!!!!
fixContextHierarchy(elements);
// Ensure an element is selected see 436659
if( parent.getSelectedElement() == null ) {
if( ! widget.getItems().isEmpty() ) {
parent.setSelectedElement(widget.getItems().get(0).getDomElement());
}
}
}
void handleChildrenRemove(@NonNull MPartStack parent, Collection<MStackElement> elements) {
WStack<N, I, IC> parentWidget = getWidget(parent);
if( parentWidget == null ) {
getLogger().error("Could not find widget for '"+parent+"'"); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
// more performant group removal
ArrayList<MStackElement> list = new ArrayList<MStackElement>(elements);
MStackElement selectedElement = parent.getSelectedElement();
if ((selectedElement != null) && (list.contains(selectedElement))) {
list.remove(selectedElement);
list.add(selectedElement);// remove and add the selected element to
// the end
}
// build the stack item list out of the model
List<@NonNull WStackItem<I, IC>> items = transmuteList(parentWidget, list);
parentWidget.removeItems(items);
// ArrayList<MStackElement> removeOnHideList = new ArrayList<MStackElement>();
Boolean b = (Boolean) this.application.getContext().get("__efx_engine_shutdown"); //$NON-NLS-1$
if( b == null || ! b.booleanValue() ) {
for (MStackElement element : list) {
// Unnecessary code
// if (element.getTags().contains(EPartService.REMOVE_ON_HIDE_TAG)) {
// removeOnHideList.add(element);
// }
if( ! element.getTransientData().containsKey(MAP_MOVE) ) {
element.getTransientData().remove(MAP_ITEM_KEY);
}
}
// parent.getChildren().removeAll(removeOnHideList);
}
checkSelectedElement(parent);
}
@NonNull
private List<@NonNull WStackItem<I, IC>> transmuteList(WStack<N, I, IC> parentWidget, ArrayList<MStackElement> list) {
ArrayList<@NonNull WStackItem<I, IC>> resultList = new ArrayList<>();
for (WStackItem<I, IC> item : parentWidget.getItems()) {
MStackElement domElement = item.getDomElement();
if (domElement != null && list.contains(domElement) && (domElement.isToBeRendered()) && (isChildRenderedAndVisible(domElement))) {
resultList.add(item);
}
}
return resultList;
}
void handleSelectedElement(final @NonNull MPartStack parent, @Nullable final MStackElement oldElement, @Nullable final MStackElement _newElement) {
MStackElement newElement = _newElement;
if( newElement == null ) {
return;
}
WStack<N, I, IC> stack = getWidget(parent);
if( stack == null ) {
getLogger().error("Could not find widget for '"+parent+"'"); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
int idx = 0;
for (WStackItem<I, IC> i : stack.getItems()) {
if (i.getDomElement() == newElement) {
stack.selectItem(idx);
fixContextHierarchy(newElement);
return;
}
idx++;
}
// Looks like the child is not part of the UI yet (most likely because
// it got removed using IPR#removeGUI)
childRendered(parent, newElement);
stack.selectItem(parent.getChildren().indexOf(newElement));
// TODO Should we do the traversal before???
fixContextHierarchy(newElement);
activateLeafPart(newElement);
}
boolean handleStackItemClose(@NonNull MStackElement e, @NonNull WStackItem<I, IC> item) {
MPart part = e instanceof MCompositePart ? (MPart) e : getPart(e);
if( part == null ) {
getLogger().error("Unable to extract part from '"+e+"'"); //$NON-NLS-1$//$NON-NLS-2$
return true;
}
if (!part.isCloseable()) {
return false;
}
IEclipseContext partContext = part.getContext();
IEclipseContext parentContext = getContextForParent(part);
if( parentContext == null ) {
getLogger().error("The parent context is unknown. This is impossible."); //$NON-NLS-1$
return false;
}
// a part may not have a context if it hasn't been rendered
IEclipseContext context = (partContext == null ? parentContext : partContext).createChild();
if (partContext == null) {
context.set(MPart.class, part);
}
// Allow closes to be 'canceled'
EPartService partService = (EPartService) context.get(EPartService.class.getName());
try {
if (partService.savePart(part, true) && this.lifecycleService.validateAnnotation(PreClose.class, part, context)) {
partService.hidePart(part);
return true;
}
// the user has canceled out of the save operation, so don't close
// the part
return false;
} finally {
context.dispose();
}
}
@SuppressWarnings("null")
@Override
public void childRendered(MPartStack parentElement, MUIElement element) {
if (element == null || this.inLazyInit || inContentProcessing(parentElement) || !isChildRenderedAndVisible(element)) {
return;
}
WStack<N, I, IC> stack = getWidget(parentElement);
if( stack == null ) {
getLogger().error("Could not find widget for '"+parentElement+"'"); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
for (WStackItem<I, IC> i : stack.getItems()) {
if (i.getDomElement() == element) {
return;
}
}
ElementRenderer<@NonNull MStackElement, ?> renderer = getFactory().getRenderer(element);
if( renderer != null ) {
int idx = getRenderedIndex(parentElement, element);
stack.addItems(idx, Collections.singletonList(createStackItem(stack, (MStackElement) element, renderer)));
} else {
getLogger().error("Could not find renderer for '"+element+"'"); //$NON-NLS-1$//$NON-NLS-2$
}
}
@SuppressWarnings("null")
@Override
public void hideChild(MPartStack container, MUIElement changedObj) {
WStack<N, I, IC> stack = getWidget(container);
if (stack == null) {
return;
}
WStackItem<I, IC> item = null;
for (WStackItem<I, IC> i : stack.getItems()) {
if (i.getDomElement() == changedObj) {
item = i;
break;
}
}
if (item != null) {
List<WStackItem<I, IC>> l = Collections.singletonList(item);
stack.removeItems(l);
}
Boolean b = (Boolean) this.application.getContext().get("__efx_engine_shutdown"); //$NON-NLS-1$
if ((b == null || ! b.booleanValue()) && changedObj.getTags().contains(EPartService.REMOVE_ON_HIDE_TAG) && changedObj.isToBeRendered() == false) {
container.getChildren().remove(changedObj);
}
}
}