/**
* Copyright (c) 2009, 2014 Mark Feber, MulgaSoft
*
* 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
*/
package com.mulgasoft.emacsplus.e4.commands;
import static com.mulgasoft.emacsplus.EmacsPlusUtils.getPreferenceStore;
import static com.mulgasoft.emacsplus.preferences.PrefVars.ENABLE_SPLIT_SELF;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainer;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainerElement;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
/**
* A base class for all split and join window commands (and their associates)
* Use E4 injection
*
* @author mfeber - Initial API and implementation
*/
public abstract class E4WindowCmd extends E4Cmd {
/**
* This is the default "size" for the layout, but for some reason it is not exposed and neither is a method
* for getting the total size by computation.
*/
public static final int TOTAL_SIZE = 10000;
public static final List<String> EDITOR_TAG = Arrays.asList("Editor"); //$NON-NLS-1$
public static final String TOP_TAG = "topLevel"; //$NON-NLS-1$
// split editor in two when true, else just rearrange editors in stack
private static boolean splitSelf = EmacsPlusUtils.getPreferenceBoolean(ENABLE_SPLIT_SELF.getPref());
static {
// listen for changes in the property store
getPreferenceStore().addPropertyChangeListener(
new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (ENABLE_SPLIT_SELF.getPref().equals(event.getProperty())) {
E4WindowCmd.setSplitSelf((Boolean)event.getNewValue());
}
}
}
);
}
/**
* @param splitSelf the splitSelf to set
*/
public static void setSplitSelf(boolean splitSelf) {
E4WindowCmd.splitSelf = splitSelf;
}
/**
* @return the splitSelf
*/
public boolean isSplitSelf() {
return splitSelf;
}
/**
* Activate the cursor in the part
* @param apart
*/
protected void reactivate(MPart apart) {
partService.activate(null);
partService.activate(apart, true);
}
/**
* Simple cons
* @author mfeber - Initial API and implementation
*/
class PartAndStack {
PartAndStack(MPart part, MElementContainer<MUIElement> stack) {
this.part = part;
this.stack = stack;
}
private MPart part;
private MElementContainer<MUIElement> stack;
public MPart getPart() {return part;}
public MElementContainer<MUIElement> getStack() {return stack;}
}
/**
* The parent of the apart is typically a PartStack, but it could be something under an MCompositePart, so look up
* @param apart the original selection
* @return the MPart and PartStack
*/
protected PartAndStack getParentStack(MPart apart) {
MPartSashContainerElement part = apart;
MElementContainer<MUIElement> stack = apart.getParent();
try {
while (!((MPartSashContainerElement)stack instanceof MPartStack)) {
if ((MPartSashContainerElement)stack instanceof MArea) {
// some unexpected structure
stack = null;
part = apart;
break;
} else {
part = (MPartSashContainerElement)stack;
stack = stack.getParent();
}
}
} catch (Exception e) {
// Bail on anything unexpected - will just make the command a noop
stack = null;
part = apart;
}
return new PartAndStack((MPart)part, stack);
}
/**
* Get the ordered list of stacks
* @param apart
* @return a list of MElementContainer<MUIElement> representing all the PartStacks
*/
protected List<MElementContainer<MUIElement>> getOrderedStacks(MPart apart) {
List<MElementContainer<MUIElement>> result = new ArrayList<MElementContainer<MUIElement>>();
MElementContainer<MUIElement> parent = getTopElement(apart.getParent());
if (parent != null) {
result = getStacks(result, parent);
}
return result;
}
List<MElementContainer<MUIElement>> getStacks(MElementContainer<MUIElement> container) {
return getStacks(new ArrayList<MElementContainer<MUIElement>>(), container);
}
private List<MElementContainer<MUIElement>> getStacks(List<MElementContainer<MUIElement>> result, MElementContainer<MUIElement> container) {
for (MUIElement child : container.getChildren()) {
@SuppressWarnings("unchecked") // We type check all the way down
MElementContainer<MUIElement> c = (MElementContainer<MUIElement>)child;
if (child instanceof MPartStack) {
result.add(c);
} else {
getStacks(result,c);
}
}
return result;
}
/**
* Find the first element with which we should join
*
* @param dragStack the stack to join
* @return the target stack
*/
@SuppressWarnings("unchecked") // for safe cast to MElementContainer<MUIElement>
protected MElementContainer<MUIElement> getAdjacentElement(MElementContainer<MUIElement> dragStack, MPart part, boolean stackp) {
MElementContainer<MUIElement> result = null;
if (dragStack != null) {
MElementContainer<MUIElement> psash = dragStack.getParent();
if ((Object)psash instanceof MPartSashContainer) {
List<MUIElement> children = psash.getChildren();
int size = children.size();
if (size > 1) {
int index = children.indexOf(dragStack)+1;
result = (MElementContainer<MUIElement>)children.get((index == size) ? index - 2 : index);
if (stackp) {
result = findTheStack(result);
}
}
}
}
return result;
}
/**
* If parent is a sash, keep looking until we find a PartStack
*
* @param parent a sash of PartStack
* @return the first PartStack we find
*/
@SuppressWarnings("unchecked") // for safe cast to MElementContainer<MUIElement>
private MElementContainer<MUIElement> findTheStack(MElementContainer<MUIElement> parent) {
MElementContainer<MUIElement> result = parent;
if ((MPartSashContainerElement)parent instanceof MPartSashContainer) {
List<MUIElement> children = parent.getChildren();
result = findTheStack((MElementContainer<MUIElement>)children.get(0));
}
return result;
}
/**
* Rotate through stacks by <count> elements
*
* @param part the source part
* @param stack the source stack
* @param count the number to rotate by
* @return the destination stack
*/
protected MElementContainer<MUIElement> findNextStack(MPart part, MElementContainer<MUIElement> stack, int count) {
MElementContainer<MUIElement> nstack = null;
List<MElementContainer<MUIElement>> stacks = getOrderedStacks(part);
int size = stacks.size();
if (size > 1) {
int index = stacks.indexOf(stack) + (count % size);
nstack = (index < 0) ? stacks.get(size + index) : (index < size) ? stacks.get(index) : stacks.get(index - size);
}
return nstack;
}
/**
* @param ele start from here
* @return the most distant parent for the editor area
*/
protected MElementContainer<MUIElement> getTopElement(MElementContainer<MUIElement> ele) {
MElementContainer<MUIElement> parent = ele;
// get the outer container
if (parent != null) {
while (!((Object)parent instanceof MArea) && parent.getParent() != null) {
parent = parent.getParent();
}
}
return parent;
}
protected int getTotalSize(MPart apart) {
int result = 0;
List<MUIElement> topParts = getTopElement(apart.getParent()).getChildren();
for (MUIElement mui : topParts) {
result += sizeIt(mui);
}
return (result == 0 ? TOTAL_SIZE : result);
}
private int sizeIt(MUIElement ele) {
int result = 0;
if (ele.getContainerData() != null) {
result = getIntData(ele);
} else if (ele instanceof MElementContainer) {
@SuppressWarnings("unchecked") //checked
List<MUIElement> mlist = ((MElementContainer<MUIElement>)ele).getChildren();
for (MUIElement mui : mlist) {
result += sizeIt(mui);
}
}
return result;
}
protected int getIntData(MUIElement mui) {
try {
return Integer.parseInt(mui.getContainerData());
} catch (NumberFormatException e) {
// Ignore - someone has messed with the container data
return 0;
}
}
// Frame specific methods
/**
* Find the edit area.
*
* @param w
*
* @return the MArea element containing the editors
*/
protected MUIElement getEditArea(MWindow w) {
// Seems like we should be able to use modelService.find(ID_EDITOR_AREA, w), but that returns a useless PlaceHolder
// NB Selectors aren't supported until Luna
// final Selector match = new Selector() {
// public boolean select(MApplicationElement element) {
// return !modelService.findElements((MUIElement)element, null, MPart.class, EDITOR_TAG, EModelService.IN_ANY_PERSPECTIVE).isEmpty();
// }
// };
// List<MArea> area = modelService.findElements(w, MArea.class, EModelService.IN_SHARED_AREA, match);
List<MArea> area = modelService.findElements(w, null, MArea.class, null, EModelService.IN_SHARED_AREA);
List<MArea> refined = new ArrayList<MArea>();
if (area != null) {
for (MArea m : area) {
if (!modelService.findElements(m, null, MPart.class, EDITOR_TAG, EModelService.IN_ANY_PERSPECTIVE).isEmpty()) {
refined.add(m);
}
}
}
return refined.isEmpty() ? null : refined.get(0);
}
@SuppressWarnings("unchecked") // manually checked
MUIElement getSelected(MUIElement ele) {
MUIElement sel = ele;
while (sel instanceof MElementContainer) {
sel = ((MElementContainer<MUIElement>)sel).getSelectedElement();
}
// on occasion the above returns null which is bizarre, so try skipping the first level
if (sel == null && ele instanceof MElementContainer) {
List<MUIElement> c = ((MElementContainer<MUIElement>)ele).getChildren();
if (c.size() == 1 && c.get(0) instanceof MPartStack) {
sel = ((MElementContainer<MUIElement>)c.get(0)).getSelectedElement();
}
}
return sel;
}
/**
* Get the list of detached windows, if any
*
* NB: The docs don't guarantee a non-null return, but the implementation seems to
* Nor do they guarantee an order, but the implementation currently returns the same order
* @return the list of detached windows
*/
List<MTrimmedWindow> getDetachedFrames() {
final MWindow topWindow = application.getChildren().get(0);
// NB Selectors aren't supported until Luna
// final Selector match = new Selector() {
// public boolean select(MApplicationElement element) {
// boolean result = element != topWindow && ((MTrimmedWindow)element).isToBeRendered();
// return result && !modelService.findElements((MUIElement)element, null, MPart.class, EDITOR_TAG, EModelService.IN_ANY_PERSPECTIVE).isEmpty();
// }
// };
// List<MTrimmedWindow> mts = modelService.findElements(topWindow, MTrimmedWindow.class, EModelService.IN_ANY_PERSPECTIVE, match);
// get the all detached editor trimmed windows
// the implementation searches all detached windows in this case
List<MTrimmedWindow> mts = modelService.findElements(topWindow, null, MTrimmedWindow.class, null, EModelService.IN_ANY_PERSPECTIVE);
List<MTrimmedWindow> refined = new ArrayList<MTrimmedWindow>();
for (MTrimmedWindow mt : mts) {
if (mt != topWindow && mt.isToBeRendered() && !modelService.findElements(mt, null, MPart.class, EDITOR_TAG, EModelService.IN_ANY_PERSPECTIVE).isEmpty()) {
refined.add(mt);
}
}
return refined;
}
}