blob: 23baf79eb8b60e761efc28473d994bf461eacbb0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2012 Tasktop Technologies 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:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.context.ui.state;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.context.core.ContextCore;
import org.eclipse.mylyn.context.core.IInteractionElement;
import org.eclipse.mylyn.context.ui.ContextAwareEditorInput;
import org.eclipse.mylyn.context.ui.ContextUi;
import org.eclipse.mylyn.context.ui.IContextAwareEditor;
import org.eclipse.mylyn.internal.context.ui.ContextUiPlugin;
import org.eclipse.mylyn.internal.context.ui.IContextUiPreferenceContstants;
import org.eclipse.mylyn.monitor.ui.MonitorUi;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.internal.EditorReference;
import org.eclipse.ui.internal.IPreferenceConstants;
import org.eclipse.ui.internal.IWorkbenchConstants;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.internal.WorkbenchPage;
/**
* @author Mik Kersten
* @author Shawn Minto
*/
public class EditorStateParticipant extends ContextStateParticipant {
public static final String MEMENTO_EDITORS = "org.eclipse.mylyn.context.ui.editors"; //$NON-NLS-1$
private static final String KEY_MONITORED_WINDOW_OPEN_EDITORS = "MonitoredWindowOpenEditors"; //$NON-NLS-1$
private static final String ATTRIBUTE_CLASS = "class"; //$NON-NLS-1$
private static final String ATTRIBUTE_NUMER = "number"; //$NON-NLS-1$
private static final String ATTRIBUTE_IS_LAUNCHING = "isLaunching"; //$NON-NLS-1$
private static final String ATTRIBUTE_IS_ACTIVE = "isActive"; //$NON-NLS-1$
private boolean previousCloseEditorsSetting = Workbench.getInstance()
.getPreferenceStore()
.getBoolean(IPreferenceConstants.REUSE_EDITORS_BOOLEAN);
private boolean enabled;
private IMemento savedMemento;
private String savedContextHandle;
public EditorStateParticipant() {
this.enabled = true;
}
@Override
public void saveDefaultState(ContextState memento) {
// save platform preference setting
Workbench workbench = (Workbench) PlatformUI.getWorkbench();
previousCloseEditorsSetting = workbench.getPreferenceStore().getBoolean(
IPreferenceConstants.REUSE_EDITORS_BOOLEAN);
workbench.getPreferenceStore().setValue(IPreferenceConstants.REUSE_EDITORS_BOOLEAN, false);
}
@Override
public void restoreState(ContextState state) {
if (Workbench.getInstance().isStarting()) {
return;
}
boolean wasPaused = ContextCore.getContextManager().isContextCapturePaused();
try {
if (!wasPaused) {
ContextCore.getContextManager().setContextCapturePaused(true);
}
// restore editors from memento
String mementoString = null;
IWorkbenchWindow activeWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
try {
IMemento memento = state.getMemento(MEMENTO_EDITORS);
savedMemento = memento;
savedContextHandle = state.getContextHandle();
if (memento != null) {
IMemento[] children = memento.getChildren(KEY_MONITORED_WINDOW_OPEN_EDITORS);
if (children.length > 0) {
// This code supports restore from multiple windows
for (IMemento child : children) {
WorkbenchPage page = getWorkbenchPageForMemento(child, activeWindow);
if (child != null && page != null) {
restoreEditors(page, child, page.getWorkbenchWindow() == activeWindow);
}
}
} else {
// This code is for supporting the old editor management - only the active window
WorkbenchPage page = (WorkbenchPage) activeWindow.getActivePage();
if (memento != null) {
restoreEditors(page, memento, true);
}
}
}
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Could not restore all editors, memento: \"" + mementoString + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$
}
// refresh window
activeWindow.setActivePage(activeWindow.getActivePage());
// open last active context node, guarantees that something is opened even if no editor memento is available
IInteractionElement activeNode = state.getContext().getActiveNode();
if (activeNode != null) {
ContextUi.getUiBridge(activeNode.getContentType()).open(activeNode);
}
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Failed to open editors on activation", e)); //$NON-NLS-1$
} finally {
ContextCore.getContextManager().setContextCapturePaused(false);
}
}
private WorkbenchPage getWorkbenchPageForMemento(IMemento memento, IWorkbenchWindow activeWindow) {
String windowToRestoreClassName = memento.getString(ATTRIBUTE_CLASS);
if (windowToRestoreClassName == null) {
windowToRestoreClassName = ""; //$NON-NLS-1$
}
Integer windowToRestorenumber = memento.getInteger(ATTRIBUTE_NUMER);
if (windowToRestorenumber == null) {
windowToRestorenumber = 0;
}
// try to match the open windows to the one that we want to restore
Set<IWorkbenchWindow> monitoredWindows = MonitorUi.getMonitoredWindows();
for (IWorkbenchWindow window : monitoredWindows) {
int windowNumber = getNumber(window);
if (window.getClass().getCanonicalName().equals(windowToRestoreClassName)
&& windowNumber == windowToRestorenumber) {
return (WorkbenchPage) window.getActivePage();
}
}
// we don't have a good match here, try to make an educated guess
Boolean isActive = memento.getBoolean(ATTRIBUTE_IS_ACTIVE);
if (isActive == null) {
isActive = false;
}
// both of these defaulting to true should ensure that all editors are opened even if their previous editor is not around
boolean shouldRestoreUnknownWindowToActive = true; // TODO could add a preference here
boolean shouldRestoreActiveWindowToActive = true; // TODO could add a preference here
if (isActive && shouldRestoreActiveWindowToActive) {
// if the window that we are trying to restore was the active window, restore it to the active window
return (WorkbenchPage) activeWindow.getActivePage();
}
if (shouldRestoreUnknownWindowToActive) {
// we can't find a good window, so restore it to the active one
return (WorkbenchPage) activeWindow.getActivePage();
}
if (shouldRestoreActiveWindowToActive && shouldRestoreUnknownWindowToActive) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Unable to find window to restore memento to.", new Exception())); //$NON-NLS-1$
}
// we dont have a window that will work, so don't restore the editors
// we shouldn't get here if both *WindowToActive booleans are true
return null;
}
@Override
public void saveState(ContextState state, boolean allowModifications) {
if (PlatformUI.getWorkbench().isClosing()) {
return;
}
if (allowModifications) {
closeContextAwareEditors(state.getContextHandle());
}
IWorkbenchWindow activeWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IWorkbenchWindow launchingWindow = MonitorUi.getLaunchingWorkbenchWindow();
Set<IWorkbenchWindow> monitoredWindows = MonitorUi.getMonitoredWindows();
boolean failed = false;
IMemento newMemento = XMLMemento.createWriteRoot(MEMENTO_EDITORS);
for (IWorkbenchWindow window : monitoredWindows) {
IMemento memento = newMemento.createChild(KEY_MONITORED_WINDOW_OPEN_EDITORS);
memento.putString(ATTRIBUTE_CLASS, window.getClass().getCanonicalName());
int number = getNumber(window);
memento.putInteger(ATTRIBUTE_NUMER, number);
memento.putBoolean(ATTRIBUTE_IS_LAUNCHING, window == launchingWindow);
memento.putBoolean(ATTRIBUTE_IS_ACTIVE, window == activeWindow);
try {
if (is_3_x()) {
saveEditors_e_3_x((WorkbenchPage) window.getActivePage(), memento);
} else if (is_3_8_2()) {
saveEditors_e_8_2(window.getActivePage(), memento);
} else {
saveEditors_e_4_legacy((WorkbenchPage) window.getActivePage(), memento);
}
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Saving of editor state failed", e)); //$NON-NLS-1$
failed = true;
}
}
// retain the state in case editor save/restore fails on e4
if (failed && savedMemento != null && savedContextHandle != null
&& savedContextHandle.equals(state.getContextHandle())) {
IMemento memento = state.createMemento(MEMENTO_EDITORS);
memento.putMemento(savedMemento);
} else {
IMemento memento = state.createMemento(MEMENTO_EDITORS);
memento.putMemento(newMemento);
}
savedMemento = null;
}
protected void saveEditors_e_8_2(IWorkbenchPage page, IMemento memento) throws Exception {
Method getEditorStateMethod = IWorkbenchPage.class.getDeclaredMethod(
"getEditorState", IEditorReference[].class, boolean.class); //$NON-NLS-1$
IMemento[] states = (IMemento[]) getEditorStateMethod.invoke(page, page.getEditorReferences(), true);
for (IMemento state : states) {
IMemento childMemento = memento.createChild("editor"); //$NON-NLS-1$
childMemento.putMemento(state);
}
}
@SuppressWarnings("unchecked")
protected void saveEditors_e_4_legacy(final WorkbenchPage page, IMemento memento) throws Exception {
Method getInternalEditorReferences = WorkbenchPage.class.getDeclaredMethod("getInternalEditorReferences"); //$NON-NLS-1$
final List<EditorReference> editorReferences = (List<EditorReference>) getInternalEditorReferences.invoke(page);
SafeRunner.run(new SafeRunnable() {
public void run() {
for (EditorReference reference : editorReferences) {
// realize editor
IEditorPart editor = reference.getEditor(true);
if (editor != null) {
Method method;
try {
method = EditorReference.class.getDeclaredMethod("persist"); //$NON-NLS-1$
method.setAccessible(true);
method.invoke(reference);
} catch (Exception e) {
// ignore
}
}
}
}
});
E4EditorReflector reflector = new E4EditorReflector();
List<?> editors = reflector.findElements(page);
for (Object editor : editors) {
Map<String, String> persistedState = reflector.getPersistedState(editor);
String state = persistedState.get("memento"); //$NON-NLS-1$
if (state != null) {
XMLMemento editorMemento = XMLMemento.createReadRoot(new StringReader(state));
IMemento childMemento = memento.createChild("editor"); //$NON-NLS-1$
childMemento.putMemento(editorMemento);
}
}
}
protected void saveEditors_e_3_x(WorkbenchPage page, IMemento memento) throws Exception {
Method getEditorManagerMethod = WorkbenchPage.class.getDeclaredMethod("getEditorManager"); //$NON-NLS-1$
Object editorManager = getEditorManagerMethod.invoke(page);
Method getEditorsMethod = editorManager.getClass().getDeclaredMethod("saveState", IMemento.class); //$NON-NLS-1$
getEditorsMethod.invoke(editorManager, memento);
}
private int getNumber(IWorkbenchWindow window) {
IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
for (int i = 0; i < windows.length; i++) {
if (windows[i] == window) {
return i;
}
}
return 0;
}
@Override
public void restoreDefaultState(ContextState memento) {
Workbench.getInstance()
.getPreferenceStore()
.setValue(IPreferenceConstants.REUSE_EDITORS_BOOLEAN, previousCloseEditorsSetting);
closeAllEditors();
}
@Override
public void clearState(String contextHandle, boolean activeContext) {
// if (activeContext) {
// closeContextAwareEditors(contextHandle);
// }
// Workbench.getInstance()
// .getPreferenceStore()
// .setValue(IPreferenceConstants.REUSE_EDITORS_BOOLEAN, previousCloseEditorsSetting);
// if (activeContext) {
// closeAllEditors();
// }
if (activeContext) {
closeAllEditors();
}
}
/**
* HACK: will fail to restore different parts with same name
*/
private void restoreEditors(WorkbenchPage page, IMemento memento, boolean isActiveWindow) {
final ArrayList<?> visibleEditors = new ArrayList<Object>(5);
final IEditorReference activeEditor[] = new IEditorReference[1];
final MultiStatus result = new MultiStatus(PlatformUI.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$
try {
IMemento[] editorMementos = memento.getChildren(IWorkbenchConstants.TAG_EDITOR);
Set<IMemento> editorMementoSet = new LinkedHashSet<IMemento>();
editorMementoSet.addAll(Arrays.asList(editorMementos));
if (is_3_x()) {
restoreEditors_e_3_x(page, visibleEditors, activeEditor, result, editorMementoSet);
} else if (is_3_8_2()) {
restoreEditors_e_3_8_2(page, visibleEditors, result, editorMementoSet);
} else {
restoreEditors_e_4_legacy(page, visibleEditors, activeEditor, result, editorMementoSet);
}
// the 3.8.2 implementation automatically activates the first editor
if (activeEditor[0] != null && isActiveWindow) {
IWorkbenchPart editor = activeEditor[0].getPart(true);
if (editor != null) {
page.activate(editor);
}
}
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Restoring of editor state failed", e)); //$NON-NLS-1$
}
}
/**
* Returns true if the environment supports the Eclipse 3.8.2/4.2.2 API for editor save and restore.
*/
private boolean is_3_8_2() {
try {
IWorkbenchPage.class.getDeclaredMethod(
"openEditors", IEditorInput[].class, String[].class, IMemento[].class, int.class, int.class); //$NON-NLS-1$
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
/**
* Returns true if the environment supports the Eclipse 3.x API for editor save and restore.
*/
protected boolean is_3_x() {
try {
Class<?> editorManager = Class.forName("org.eclipse.ui.internal.EditorManager"); //$NON-NLS-1$
editorManager.getDeclaredMethod("restoreEditorState", //$NON-NLS-1$
IMemento.class, ArrayList.class, IEditorReference[].class, MultiStatus.class);
return true;
} catch (Exception e) {
return false;
}
}
private void restoreEditors_e_3_8_2(WorkbenchPage page, final ArrayList<?> visibleEditors,
final MultiStatus result, Set<IMemento> editorMementoSet) throws Exception {
Method openEditorsMethod = IWorkbenchPage.class.getDeclaredMethod(
"openEditors", IEditorInput[].class, String[].class, IMemento[].class, int.class, int.class); //$NON-NLS-1$
openEditorsMethod.invoke(page, null, null, editorMementoSet.toArray(new IMemento[0]),
IWorkbenchPage.MATCH_INPUT, 0);
}
private void restoreEditors_e_4_legacy(WorkbenchPage page, ArrayList<?> visibleEditors,
IEditorReference[] activeEditor, MultiStatus result, Set<IMemento> mementos) throws Exception {
E4EditorReflector reflector = new E4EditorReflector();
for (IMemento memento : mementos) {
StringWriter writer = new StringWriter();
boolean found = false;
IMemento inputMememnto = memento.getChild("input"); //$NON-NLS-1$
if (inputMememnto != null) {
String pathString = inputMememnto.getString("path"); //$NON-NLS-1$
if (pathString != null) {
IPath path = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(pathString)).getLocation();
for (IEditorReference existingReference : page.getEditorReferences()) {
if (existingReference.getEditorInput() instanceof IPathEditorInput
&& (((IPathEditorInput) existingReference.getEditorInput()).getPath().equals(path))) {
// there is already an editor showing the same file
found = true;
break;
}
}
}
}
if (!found) {
// create part
Object editorPart = reflector.createPart(page.getWorkbenchWindow());
// inject persisted editor state
((XMLMemento) memento).save(writer);
reflector.getPersistedState(editorPart).put("memento", writer.toString()); //$NON-NLS-1$
// create legacy editor control
IEditorReference editorReference = reflector.showPart(page, editorPart);
// make first restored editor the visible editor
if (activeEditor[0] == null) {
activeEditor[0] = editorReference;
}
}
}
}
private void restoreEditors_e_3_x(WorkbenchPage page, final ArrayList<?> visibleEditors,
final IEditorReference[] activeEditor, final MultiStatus result, Set<IMemento> editorMementoSet)
throws Exception {
Method getEditorManagerMethod = WorkbenchPage.class.getDeclaredMethod("getEditorManager"); //$NON-NLS-1$
Object editorManager = getEditorManagerMethod.invoke(page);
Method getEditorsMethod = editorManager.getClass().getDeclaredMethod("getEditors"); //$NON-NLS-1$
List<IEditorReference> alreadyVisibleEditors = Arrays.asList((IEditorReference[]) getEditorsMethod.invoke(editorManager));
Set<String> restoredPartNames = new HashSet<String>();
for (IEditorReference editorReference : alreadyVisibleEditors) {
restoredPartNames.add(editorReference.getPartName());
}
Method restoreEditorStateMethod = editorManager.getClass().getDeclaredMethod("restoreEditorState", //$NON-NLS-1$
IMemento.class, ArrayList.class, IEditorReference[].class, MultiStatus.class);
for (IMemento editorMemento : editorMementoSet) {
String partName = editorMemento.getString(IWorkbenchConstants.TAG_PART_NAME);
if (!restoredPartNames.contains(partName)) {
restoreEditorStateMethod.invoke(editorManager, editorMemento, visibleEditors, activeEditor, result);
} else {
restoredPartNames.add(partName);
}
}
Method setVisibleEditorMethod = editorManager.getClass().getDeclaredMethod("setVisibleEditor", //$NON-NLS-1$
IEditorReference.class, boolean.class);
for (int i = 0; i < visibleEditors.size(); i++) {
setVisibleEditorMethod.invoke(editorManager, visibleEditors.get(i), false);
}
}
public void closeContextAwareEditors(String contextHandle) {
try {
if (PlatformUI.getWorkbench().isClosing()) {
return;
}
for (IWorkbenchWindow window : MonitorUi.getMonitoredWindows()) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
IEditorReference[] references = page.getEditorReferences();
List<IEditorReference> toClose = new ArrayList<IEditorReference>();
for (IEditorReference reference : references) {
if (canClose(reference)) {
try {
IEditorInput input = reference.getEditorInput();
if (shouldForceClose(input, contextHandle)) {
toClose.add(reference);
}
} catch (PartInitException e) {
// ignore
}
}
}
page.closeEditors(toClose.toArray(new IEditorReference[toClose.size()]), true);
}
}
} catch (Throwable t) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN, "Could not auto close editor", t)); //$NON-NLS-1$
}
}
public void closeAllEditors() {
try {
if (PlatformUI.getWorkbench().isClosing()) {
return;
}
for (IWorkbenchWindow window : MonitorUi.getMonitoredWindows()) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
IEditorReference[] references = page.getEditorReferences();
List<IEditorReference> toClose = new ArrayList<IEditorReference>();
for (IEditorReference reference : references) {
if (canClose(reference)) {
toClose.add(reference);
}
}
page.closeEditors(toClose.toArray(new IEditorReference[toClose.size()]), true);
}
}
} catch (Throwable t) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN, "Could not auto close editor", t)); //$NON-NLS-1$
}
}
private boolean shouldForceClose(final IEditorInput input, final String contextHandle) {
final AtomicBoolean result = new AtomicBoolean();
SafeRunnable.run(new ISafeRunnable() {
public void run() throws Exception {
ContextAwareEditorInput inputContext = (ContextAwareEditorInput) input.getAdapter(ContextAwareEditorInput.class);
result.set(inputContext != null && inputContext.forceClose(contextHandle));
}
public void handleException(Throwable e) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Failed to verify editor status", e)); //$NON-NLS-1$
}
});
return result.get();
}
private boolean canClose(final IEditorReference editorReference) {
final IEditorPart editor = editorReference.getEditor(false);
if (editor != null) {
final boolean[] result = new boolean[1];
result[0] = true;
SafeRunnable.run(new ISafeRunnable() {
public void run() throws Exception {
if (editor instanceof IContextAwareEditor) {
result[0] = ((IContextAwareEditor) editor).canClose();
} else {
IContextAwareEditor contextAware = (IContextAwareEditor) editor.getAdapter(IContextAwareEditor.class);
if (contextAware != null) {
result[0] = contextAware.canClose();
}
}
}
public void handleException(Throwable e) {
StatusHandler.log(new Status(IStatus.WARNING, ContextUiPlugin.ID_PLUGIN,
"Failed to verify editor status", e)); //$NON-NLS-1$
}
});
return result[0];
}
return true;
}
@Override
public boolean isEnabled() {
return enabled
&& ContextUiPlugin.getDefault()
.getPreferenceStore()
.getBoolean(IContextUiPreferenceContstants.AUTO_MANAGE_EDITORS);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}