/**
* 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 java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.e4.core.contexts.Active;
import org.eclipse.e4.core.di.annotations.Execute;
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.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainerElement;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import com.mulgasoft.emacsplus.Beeper;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds;
import com.mulgasoft.emacsplus.commands.EmacsPlusCmdHandler;
/**
* Join frames together based on the context argument
*
* @author mfeber - Initial API and implementation
*/
public class WindowJoinCmd extends E4WindowCmd {
@Inject private Shell shell;
public static enum Join {
ONE, ALL;
}
@Execute
public Object execute(@Active MPart apart, @Active IEditorPart editor, @Named(E4CmdHandler.CMD_CTX_KEY)Join jtype,
@Active EmacsPlusCmdHandler handler) {
MPart active = apart;
boolean joined = preJoin(editor);
switch (jtype) {
case ONE:
active = joinOne(apart);
if (joined) {
// don't switch on split self merge
active = apart;
}
break;
case ALL:
joinAll(apart);
break;
}
postJoin(editor);
if (handler.isUniversalPresent()) {
// convenience hack
// change setting without changing preference store
setSplitSelf(!isSplitSelf());
}
reactivate(active);
forceFocus();
return null;
}
/**
* Use a generic command to remove duplicates
* @param editor
*/
protected boolean preJoin(IEditorPart editor) {
return closeOthers(editor);
}
protected void postJoin(IEditorPart editor) {
// for sub-classes
}
/**
* Close any duplicate editors that match this one
*
* @param editor
* @return true if any were closed
*/
boolean closeOthers(IEditorPart editor) {
IWorkbenchPage page = EmacsPlusUtils.getWorkbenchPage();
int pre = EmacsPlusUtils.getSortedEditors(page).length;
if (isSplitSelf()) {
try {
EmacsPlusUtils.executeCommand(IEmacsPlusCommandDefinitionIds.CLOSE_OTHER_INSTANCES, null, editor);
} catch (Exception e) {}
}
return (pre != EmacsPlusUtils.getSortedEditors(page).length);
}
/**
* Merge the stack containing the selected part into its neighbor
*
* @param apart
* @return the element to select
*/
MPart joinOne(MPart apart) {
PartAndStack ps = getParentStack(apart);
MElementContainer<MUIElement> pstack = ps.getStack();
MPart part = ps.getPart();
MElementContainer<MUIElement> adjacent = getAdjacentElement(pstack, part, true);
MUIElement sel = getSelected(adjacent);
if (pstack == null || join2Stacks(pstack, adjacent, part) == null) {
// Invalid state
Beeper.beep();
}
return (sel instanceof MPart) ? (MPart)sel : apart;
}
/**
* Merge all stacks into one
*
* @param apart - the selected MPart
*/
void joinAll(MPart apart) {
List<MElementContainer<MUIElement>> stacks = getOrderedStacks(apart);
if (stacks.size() > 1) {
PartAndStack ps = getParentStack(apart);
MElementContainer<MUIElement> pstack = ps.getStack();
MPart part = ps.getPart();
// check for unexpected result - who knows what Eclipse might do
if (pstack != null) {
MElementContainer<MUIElement> dropStack = stacks.get(0);
for (int i = 1; i < stacks.size(); i++) {
MElementContainer<MUIElement> stack = stacks.get(i);
if (stack == pstack) {
continue;
}
join2Stacks(stacks.get(i), dropStack, null);
}
// lastly, join in the selected stack
if (pstack != dropStack) {
join2Stacks(pstack, dropStack, part);
}
} else {
Beeper.beep();
}
}
}
/**
* Given 2 partStacks, move children of pstack into dropStack
* @param pstack - source stack
* @param dropStack - destination stack
* @param apart - the initiating part
* @return the enhanced dropStack
*/
protected MElementContainer<MUIElement> join2Stacks(MElementContainer<MUIElement> pstack, MElementContainer<MUIElement> dropStack, MPart apart) {
if (dropStack != null && ((MPartSashContainerElement)dropStack) instanceof MPartStack) {
List<MUIElement> eles = pstack.getChildren();
boolean hasPart = apart != null;
int offset = 1;
List<MUIElement> drops = dropStack.getChildren();
while (eles.size() > (hasPart ? 1 : 0)) {
MUIElement ele = eles.get(eles.size() - offset);
if (hasPart && ele == apart) {
offset++;
continue;
}
eles.remove(ele);
if (hasPart) {
drops.add(0,ele);
} else {
drops.add(ele);
}
}
if (hasPart) {
// Move the selected element to the leftmost position
eles.remove(apart);
drops.add(0,apart);
dropStack.setSelectedElement(apart);
}
checkSizeData(pstack,dropStack);
}
return dropStack;
}
/**
* Check if containerData size needs updating.
* This should be handled by the Eclipse framework, but apparently not...
* @param pstack - source stack
* @param dropStack - destination stack
*/
protected void checkSizeData(MElementContainer<MUIElement> pstack, MElementContainer<MUIElement> dropStack) {
if (pstack.getParent().getContainerData() == null) {
int s1 = getIntData(pstack);;
if (dropStack.getParent().getContainerData() == null) {
// stacks are vertically side by side, add their sizes together
dropStack.setContainerData(String.valueOf(s1 + getIntData(dropStack)));
} else {
// source is vertical & dest is in a horizontal containing PartSash
dropStack.getParent().setContainerData(String.valueOf(s1 + getIntData(dropStack.getParent())));
}
}
}
/**
* Semi-workaround for egregious eclipse bug that denies focus on join which, unfortunately,
* leaves the cursor is invisible.
* If just reactivate is called, then the cursor is visible, but the keyboard has lost focus.
* A defect has been submitted which covers this (and other cases):
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=441010
*/
protected void forceFocus() {
shell.forceFocus();
}
}