blob: 340cb81746bc23e233f9cf8409ac4b37b38a9704 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2015 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 472654
******************************************************************************/
package org.eclipse.e4.ui.internal.workbench;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.e4.core.contexts.IEclipseContext;
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.MGenericStack;
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.MPlaceholder;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.modeling.EPartService;
import org.eclipse.emf.ecore.EObject;
class PartActivationHistory {
private PartServiceImpl partService;
private EModelService modelService;
private LinkedList<MPart> generalActivationHistory = new LinkedList<>();
PartActivationHistory(PartServiceImpl partService, EModelService modelService) {
this.partService = partService;
this.modelService = modelService;
}
public void clear() {
generalActivationHistory.clear();
}
void activate(MPart part, boolean activateBranch) {
IEclipseContext context = part.getContext();
if (activateBranch) {
context.activateBranch();
} else {
IEclipseContext parent = context.getParent();
do {
context.activate();
context = parent;
parent = parent.getParent();
} while (parent.get(MWindow.class) != null);
}
prepend(part);
}
/**
* Places the specified part at the end of the activation history if it is not already in the
* list. If it is already in the activation history, then its position will not change.
*
* @param part
* the part to possibly add to the end of the activation history
*/
void append(MPart part) {
if (!generalActivationHistory.contains(part)) {
generalActivationHistory.addLast(part);
}
}
/**
* Adds the specified part to the front of the activation history.
*
* @param part
* the part to insert into the front of the activation history
*/
void prepend(MPart part) {
generalActivationHistory.remove(part);
generalActivationHistory.addFirst(part);
}
/**
* Checks to see if this element and its parents are actually being rendered.
*/
boolean isValid(MUIElement element) {
if (element == null || !element.isToBeRendered() || !element.isVisible()) {
return false;
}
if (element instanceof MApplication) {
return true;
}
MUIElement parent = element.getParent();
if (parent == null && element instanceof MWindow) {
// might be a detached window
parent = (MUIElement) ((EObject) element).eContainer();
}
if (parent == null) {
return isValid(partService.getLocalPlaceholder(element));
}
return isValid(parent);
}
/**
* Checks to see if this element and its parents are actually being rendered.
*/
boolean isValid(MPerspective perspective, MUIElement element) {
if (element instanceof MApplication) {
return true;
} else if (element == null || !element.isToBeRendered() || !element.isVisible()) {
return false;
}
MElementContainer<?> parent = element.getParent();
if (parent == null) {
for (MPlaceholder placeholder : modelService.findElements(perspective, null,
MPlaceholder.class, null)) {
if (placeholder.getRef() == element) {
return isValid(perspective, placeholder);
}
}
if (element instanceof MWindow) {
// might be a detached window
return isValid(perspective, (MUIElement) ((EObject) element).eContainer());
}
return false;
} else if (parent instanceof MGenericStack && parent.getSelectedElement() != element) {
// if in a stack, then only valid if we're the selected element
return false;
}
return isValid(perspective, parent);
}
/**
* Determines whether this element is contained within an MArea, and to return the area if it
* is.
*/
private MArea isInArea(MUIElement element) {
if (element == null) {
return null;
}
MPlaceholder placeholder = element.getCurSharedRef();
if (placeholder == null) {
MUIElement parent = element.getParent();
if (parent == null) {
// may be null for detached windows
parent = (MUIElement) ((EObject) element).eContainer();
}
return parent instanceof MApplication ? null : parent instanceof MArea ? (MArea) parent
: isInArea(parent);
}
MUIElement parent = placeholder.getParent();
if (parent == null) {
// may be null for detached windows
parent = (MUIElement) ((EObject) placeholder).eContainer();
}
return parent instanceof MApplication ? null : parent instanceof MArea ? (MArea) parent
: isInArea(parent);
}
private MPart getActivationCandidate(MPart part) {
// get all the possible parts that we can possibly activate
Collection<MPart> candidates = part.getContext().get(EPartService.class).getParts();
return findActivationCandidate(candidates, part);
}
/**
* Finds and returns a part that is a valid candidate to be granted activation.
*/
private MPart findActivationCandidate(Collection<MPart> candidates, MPart currentlyActivePart) {
candidates.remove(currentlyActivePart);
MPlaceholder activePlaceholder = partService.getLocalPlaceholder(currentlyActivePart);
for (MPart candidate : candidates) {
// make sure it's rendered and visible
if (isValid(candidate)) {
MPlaceholder placeholder = partService.getLocalPlaceholder(candidate);
MElementContainer<MUIElement> parent = placeholder == null ? candidate.getParent()
: placeholder.getParent();
// stacks require special considerations because we don't want to activate something
// that's not the selected element if possible get the selected element
if (parent instanceof MGenericStack) {
MUIElement selection = parent.getSelectedElement();
// if the selected element is the currently active part, the candidate is valid
if (selection == activePlaceholder || selection == currentlyActivePart) {
return candidate;
}
// if the selected element is the current candidate, the candidate is valid
if (selection == candidate || selection == placeholder) {
return candidate;
}
} else {
// not in a stack, just return the candidate then
return candidate;
}
}
}
return null;
}
MPart getActivationCandidate(Collection<MPart> validParts) {
// check activation history, since the history is global, we need to filter it down first
Collection<MPart> validCandidates = new ArrayList<>();
for (MPart validPart : generalActivationHistory) {
if (validParts.contains(validPart)) {
validCandidates.add(validPart);
}
}
MPart candidate = findActivationCandidate(validCandidates);
if (candidate == null) {
validParts.removeAll(validCandidates);
return findActivationCandidate(validParts);
}
return candidate;
}
private MPart findActivationCandidate(Collection<MPart> candidates) {
for (MPart candidate : candidates) {
// make sure it's rendered and visible
if (isValid(candidate)) {
MPlaceholder placeholder = partService.getLocalPlaceholder(candidate);
MElementContainer<MUIElement> parent = placeholder == null ? candidate.getParent()
: placeholder.getParent();
// stacks require special considerations because we don't want to activate something
// that's not the selected element if possible get the selected element
if (parent instanceof MGenericStack) {
MUIElement selection = parent.getSelectedElement();
// if the selected element is the current candidate, the candidate is valid
if (selection == candidate || selection == placeholder) {
return candidate;
}
} else {
// not in a stack, just return the candidate then
return candidate;
}
}
}
return null;
}
MPart getNextActivationCandidate(Collection<MPart> validParts, MPart part) {
MArea area = isInArea(part);
if (area != null) {
// focus should stay in the area if possible
MPart candidate = getSiblingActivationCandidate(part);
if (candidate != null) {
return candidate;
}
// no sibling candidate, find another part in the area to activate
candidate = findActivationCandidate(
modelService.findElements(area, null, MPart.class, null), part);
if (candidate != null) {
return candidate;
}
}
// check activation history, since the history is global, we need to filter it down first
Collection<MPart> validCandidates = new ArrayList<>();
for (MPart validPart : generalActivationHistory) {
if (validParts.contains(validPart)) {
validCandidates.add(validPart);
}
}
MPart candidate = findActivationCandidate(validCandidates, part);
return candidate == null ? getActivationCandidate(part) : candidate;
}
void forget(MWindow window, MPart part, boolean full) {
if (full) {
generalActivationHistory.remove(part);
} else {
for (MPlaceholder placeholder : modelService.findElements(window, null,
MPlaceholder.class, null)) {
// if there is at least one placeholder around, we should keep this
if (placeholder.getRef() == part && placeholder.isToBeRendered()) {
return;
}
}
generalActivationHistory.remove(part);
}
}
MPart getActivationCandidate(MPerspective perspective) {
for (MPart candidate : generalActivationHistory) {
if (partService.isInContainer(perspective, candidate)
&& isValid(perspective, candidate)) {
return candidate;
}
}
List<String> activeTag = new ArrayList<>();
activeTag.add(EPartService.ACTIVE_ON_CLOSE_TAG);
List<MPart> activeCandidates = modelService.findElements(perspective, null, MPart.class,
activeTag);
if (activeCandidates.size() > 0) {
activeCandidates.get(0).getTags().remove(EPartService.ACTIVE_ON_CLOSE_TAG);
MPart candidate = activeCandidates.get(0);
if (partService.isInContainer(perspective, candidate)
&& isValid(perspective, candidate)) {
return candidate;
}
}
Collection<MPart> candidates = perspective.getContext().get(EPartService.class).getParts();
for (MPart candidate : candidates) {
if (isValid(perspective, candidate)) {
return candidate;
}
}
return null;
}
private MPart getSiblingActivationCandidate(MPart part) {
MPlaceholder placeholder = part.getCurSharedRef();
MUIElement candidate = getSiblingSelectionCandidate(part, placeholder == null ? part
: placeholder);
return (MPart) (candidate instanceof MPlaceholder ? ((MPlaceholder) candidate).getRef()
: candidate);
}
private MUIElement getSiblingSelectionCandidate(MPart part, MUIElement element) {
List<MUIElement> siblings = element.getParent().getChildren();
for (MPart previouslyActivatedPart : generalActivationHistory) {
if (previouslyActivatedPart != part && isValid(previouslyActivatedPart)) {
if (siblings.contains(previouslyActivatedPart)) {
return previouslyActivatedPart;
}
MPlaceholder placeholder = partService.getLocalPlaceholder(previouslyActivatedPart);
if (placeholder != null && placeholder.isToBeRendered()
&& siblings.contains(placeholder)) {
return placeholder;
}
}
}
return null;
}
MUIElement getSiblingSelectionCandidate(MPart part) {
MPlaceholder placeholder = part.getCurSharedRef();
return getSiblingSelectionCandidate(part, placeholder == null ? part : placeholder);
}
}