blob: 386ad1507af643d5e26f6e43bbfa6e4a230b3ad3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.compare.internal;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.IPropertyChangeNotifier;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IReusableEditor;
import org.eclipse.ui.ISaveablesLifecycleListener;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.SaveablesLifecycleEvent;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
/**
* A CompareEditor takes a ICompareEditorInput as input.
* Most functionality is delegated to the ICompareEditorInput.
*/
public class CompareEditor extends EditorPart
implements IReusableEditor, ISaveablesSource, IPropertyChangeListener,
ISaveablesLifecycleListener {
public final static String CONFIRM_SAVE_PROPERTY= "org.eclipse.compare.internal.CONFIRM_SAVE_PROPERTY"; //$NON-NLS-1$
private static final int UNINITIALIZED = 0;
private static final int INITIALIZING = 1;
private static final int NO_DIFF = 2;
private static final int CANCELED = 3;
private static final int INITIALIZED = 4;
private static final int ERROR = 5;
private static final int STILL_INITIALIZING = 6;
private static final int CREATING_CONTROL = 7;
private static final int DONE = 8;
private IActionBars fActionBars;
private PageBook fPageBook;
/** the SWT control from the compare editor input*/
private Control fControl;
/** the outline page */
private CompareOutlinePage fOutlinePage;
private CompareSaveable fSaveable;
private Control initializingPage;
private Control emptyPage;
private int state = UNINITIALIZED;
private HashSet<Saveable> knownSaveables;
private final EditorCompareContainer fContainer = new EditorCompareContainer();
private class EditorCompareContainer extends CompareContainer {
@Override
public void registerContextMenu(MenuManager menu, ISelectionProvider provider) {
if (getSite() instanceof IEditorSite) {
IEditorSite es = (IEditorSite) getSite();
es.registerContextMenu(menu, provider, true);
}
}
@Override
public void setStatusMessage(String message) {
if (fActionBars != null) {
IStatusLineManager slm= fActionBars.getStatusLineManager();
if (slm != null) {
slm.setMessage(message);
}
}
}
@Override
public IServiceLocator getServiceLocator() {
return getSite();
}
@Override
protected WorkerJob createWorkerJob() {
WorkerJob workerJob = new WorkerJob(getWorkerJobName()) {
@Override
public boolean belongsTo(Object family) {
if (family == CompareEditor.this)
return true;
return super.belongsTo(family);
}
};
return workerJob;
}
@Override
protected String getWorkerJobName() {
return NLS.bind(CompareMessages.CompareEditor_2, getTitle());
}
@Override
public IWorkbenchPart getWorkbenchPart() {
return CompareEditor.this;
}
@Override
public IActionBars getActionBars() {
return CompareEditor.this.getActionBars();
}
}
/**
* No-argument constructor required for extension points.
*/
public CompareEditor() {
// empty default implementation
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> key) {
if (key.equals(IContentOutlinePage.class)) {
Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW);
if (object instanceof Boolean && ((Boolean)object).booleanValue()) {
if (fOutlinePage != null) {
if (fOutlinePage.getControl() != null && fOutlinePage.getControl().isDisposed()) {
fOutlinePage = null;
} else {
return (T) fOutlinePage;
}
}
fOutlinePage= new CompareOutlinePage(this);
return (T) fOutlinePage;
}
}
if (key == IShowInSource.class
|| key == OutlineViewerCreator.class
|| key == IFindReplaceTarget.class) {
Object input = getEditorInput();
if (input != null) {
return Adapters.adapt(input, key);
}
}
if (key == IEditorInput.class) {
// return (T) Adapters.adapt(getEditorInput(), IEditorInput.class);
// The above call cannot be used because the return type of getEditorInput()
// call is IEditorInput and this itself will be returned by the above function.
// What we want is to call getAdapter call on this IEditorInput object.
return (T) getEditorInput().getAdapter(IEditorInput.class);
}
if (key == ITextEditorExtension3.class) {
return (T) Adapters.adapt(getEditorInput(), ITextEditorExtension3.class);
}
return super.getAdapter(key);
}
/*
* Helper method used by ComapreEditorConfiguration to get at the compare configuration of the editor
*/
CompareConfiguration getCompareConfiguration() {
IEditorInput input= getEditorInput();
if (input instanceof CompareEditorInput)
return ((CompareEditorInput)input).getCompareConfiguration();
return null;
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
if (!(input instanceof CompareEditorInput))
throw new PartInitException(Utilities.getString("CompareEditor.invalidInput")); //$NON-NLS-1$
setSite(site);
setInput(input);
}
@Override
public void setInput(IEditorInput input) {
if (!(input instanceof CompareEditorInput)) {
IStatus s= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, Utilities.getString("CompareEditor.invalidInput"), null); //$NON-NLS-1$
String title= Utilities.getString("CompareEditor.error.setinput.title"); //$NON-NLS-1$
String msg= Utilities.getString("CompareEditor.error.setinput.message"); //$NON-NLS-1$
ErrorDialog.openError(getSite().getShell(), title, msg, s);
return;
}
doSetInput(input);
// Need to refresh the contributor (see #67888)
refreshActionBarsContributor();
}
public void refreshActionBarsContributor() {
IEditorSite editorSite= getEditorSite();
if (editorSite != null) {
IEditorActionBarContributor actionBarContributor= editorSite.getActionBarContributor();
if (actionBarContributor != null) {
actionBarContributor.setActiveEditor(null);
actionBarContributor.setActiveEditor(this);
}
}
}
private void doSetInput(IEditorInput input) {
IEditorInput oldInput= getEditorInput();
disconnectFromInput(oldInput);
Point oldSize = null;
boolean hadPreviousInput = oldInput != null;
if (hadPreviousInput) {
// Cancel any jobs associated with the old input
Job.getJobManager().cancel(this);
if (fControl != null && !fControl.isDisposed()) {
oldSize= fControl.getSize();
if (emptyPage == null)
emptyPage = new Composite(fPageBook, SWT.NONE);
fPageBook.showPage(emptyPage);
fControl.dispose();
fControl = null;
}
}
super.setInput(input);
if (fOutlinePage != null)
fOutlinePage.reset();
final CompareEditorInput cei= (CompareEditorInput) input;
cei.setContainer(fContainer);
setTitleImage(cei.getTitleImage());
setPartName(cei.getTitle());
setTitleToolTip(cei.getToolTipText());
if (input instanceof IPropertyChangeNotifier)
((IPropertyChangeNotifier)input).addPropertyChangeListener(this);
setState(cei.getCompareResult() == null ? INITIALIZING : INITIALIZED);
if (fPageBook != null)
createCompareControl();
if (fControl != null && oldSize != null)
fControl.setSize(oldSize);
boolean hasResult = cei.getCompareResult() != null;
if (!hasResult) {
initializeInBackground(cei, hadPreviousInput);
}
firePropertyChange(IWorkbenchPartConstants.PROP_INPUT);
// We only need to notify of new Saveables if we are changing inputs
if (hadPreviousInput && hasResult) {
registerSaveable();
}
}
private void registerSaveable() {
ISaveablesLifecycleListener lifecycleListener= getSite().getService(ISaveablesLifecycleListener.class);
lifecycleListener.handleLifecycleEvent(
new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_OPEN, internalGetSaveables(true), false));
}
private void disconnectFromInput(IEditorInput oldInput) {
if (oldInput != null) {
if (oldInput instanceof IPropertyChangeNotifier)
((IPropertyChangeNotifier)oldInput).removePropertyChangeListener(this);
// Let the workbench know that the old input's saveables are no longer needed
if (knownSaveables != null && !knownSaveables.isEmpty()) {
ISaveablesLifecycleListener lifecycleListener= getSite().getService(ISaveablesLifecycleListener.class);
lifecycleListener.handleLifecycleEvent(
new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_CLOSE, knownSaveables.toArray(new Saveable[knownSaveables.size()]), false));
knownSaveables.clear();
}
}
}
protected void initializeInBackground(final CompareEditorInput cei, final boolean hadPreviousInput) {
// Need to cancel any running jobs associated with the oldInput
Job job = new Job(NLS.bind(CompareMessages.CompareEditor_0, cei.getTitle())) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
final int[] newState = new int[] { ERROR };
try {
IStatus status = CompareUIPlugin.getDefault().prepareInput(cei, monitor);
if (status.isOK()) {
// We need to update the saveables list
newState[0] = INITIALIZED;
return Status.OK_STATUS;
}
if (status.getCode() == CompareUIPlugin.NO_DIFFERENCE) {
newState[0] = NO_DIFF;
return Status.OK_STATUS;
}
newState[0] = ERROR;
return status;
} catch (OperationCanceledException e) {
newState[0] = CANCELED;
return Status.CANCEL_STATUS;
} finally {
if (monitor.isCanceled())
newState[0] = CANCELED;
Display.getDefault().syncExec(() -> {
if (fPageBook.isDisposed())
return;
// we need to register the saveable if we had a previous input or if
// there are knownSaveables (which means that the workbench called
// getSaveables and got an empty list
if (hadPreviousInput || (knownSaveables != null && !isAllSaveablesKnown())) {
registerSaveable();
}
setState(newState[0]);
createCompareControl();
});
monitor.done();
}
}
@Override
public boolean belongsTo(Object family) {
if (family == CompareEditor.this || family == cei)
return true;
return cei.belongsTo(family);
}
};
job.setUser(true);
Utilities.schedule(job, getSite());
}
/*
* Helper method used to find an action bars using the Utilities#findActionsBars(Control)
*/
public IActionBars getActionBars() {
return fActionBars;
}
/**
* Sets the action bars so the Utilities class can access it.
*/
void setActionBars(IActionBars actionBars) {
fActionBars= actionBars;
}
@Override
public void createPartControl(Composite parent) {
parent.setData(this);
fPageBook = new PageBook(parent, SWT.NONE);
createCompareControl();
IContextService service = getSite().getService(IContextService.class);
if (service != null) {
service.activateContext("org.eclipse.compare.compareEditorScope"); //$NON-NLS-1$
service.activateContext("org.eclipse.ui.textEditorScope"); //$NON-NLS-1$
}
}
private void createCompareControl() {
if (fPageBook.isDisposed())
return;
IEditorInput input= getEditorInput();
if (input instanceof CompareEditorInput) {
CompareEditorInput ci = (CompareEditorInput) input;
if (ci.getCompareResult() == null) {
if (getState() == INITIALIZING) {
getSite().setSelectionProvider(new CompareEditorSelectionProvider());
setPageLater();
} else if (getState() == STILL_INITIALIZING) {
if (initializingPage == null) {
initializingPage = getInitializingMessagePane(fPageBook);
}
fPageBook.showPage(initializingPage);
} else if (getState() == CANCELED) {
// Close the editor when we are canceled
closeEditor();
} else if (getState() == NO_DIFF) {
// Prompt and close the editor as well
setState(DONE);
closeEditor();
CompareUIPlugin.getDefault().handleNoDifference();
} else if (getState() == ERROR) {
// If an error occurred, close the editor
// (the message would be displayed by the progress view)
closeEditor();
}
} else if (fControl == null && getState() != CREATING_CONTROL) {
if (getState() == CANCELED) {
// Close the editor when we are canceled, even when compare
// result has been already prepared
closeEditor();
return;
}
// Set the state in case this method gets called again
setState(CREATING_CONTROL);
if (getSite().getSelectionProvider() == null)
getSite().setSelectionProvider(new CompareEditorSelectionProvider());
try {
fControl = ci.createContents(fPageBook);
} catch (SWTException e) {
// closed while creating
if (e.code == SWT.ERROR_WIDGET_DISPOSED) {
setState(CANCELED);
return;
}
}
fPageBook.showPage(fControl);
PlatformUI.getWorkbench().getHelpSystem().setHelp(fControl, ICompareContextIds.COMPARE_EDITOR);
if (isActive()) {
setFocus();
}
setState(INITIALIZED);
}
}
}
private boolean isActive() {
return getSite().getPage().getActivePart() == this;
}
private void setPageLater() {
Display.getCurrent().timerExec(1000, () -> {
synchronized(CompareEditor.this) {
if (getState() == INITIALIZING) {
setState(STILL_INITIALIZING);
createCompareControl();
}
}
});
}
@Override
public void dispose() {
IEditorInput input= getEditorInput();
if (input instanceof IPropertyChangeNotifier)
((IPropertyChangeNotifier)input).removePropertyChangeListener(this);
super.dispose();
}
@Override
public void setFocus() {
IEditorInput input= getEditorInput();
if (input instanceof CompareEditorInput)
if (!((CompareEditorInput)input).setFocus2())
fPageBook.setFocus();
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
/* (non-Javadoc)
* Always throws an AssertionFailedException.
* @see org.eclipse.ui.part.EditorPart#doSaveAs()
*/
@Override
public void doSaveAs() {
Assert.isTrue(false); // Save As not supported for CompareEditor
}
@Override
public void doSave(IProgressMonitor progressMonitor) {
final IEditorInput input= getEditorInput();
WorkspaceModifyOperation operation= new WorkspaceModifyOperation() {
@Override
public void execute(IProgressMonitor pm) throws CoreException {
if (input instanceof CompareEditorInput)
((CompareEditorInput)input).saveChanges(pm);
}
};
Shell shell= getSite().getShell();
try {
operation.run(progressMonitor);
firePropertyChange(PROP_DIRTY);
} catch (InterruptedException x) {
// NeedWork
} catch (OperationCanceledException x) {
// NeedWork
} catch (InvocationTargetException x) {
String title= Utilities.getString("CompareEditor.saveError.title"); //$NON-NLS-1$
String reason= x.getTargetException().getMessage();
MessageDialog.openError(shell, title, Utilities.getFormattedString("CompareEditor.cantSaveError", reason)); //$NON-NLS-1$
}
}
@Override
public boolean isDirty() {
IEditorInput input= getEditorInput();
if (input instanceof CompareEditorInput)
return ((CompareEditorInput)input).isDirty();
return false;
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(CompareEditorInput.DIRTY_STATE)) {
Object old_value= event.getOldValue();
Object new_value= event.getNewValue();
if (old_value == null || new_value == null || !old_value.equals(new_value))
firePropertyChange(PROP_DIRTY);
} else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE)) {
setPartName(((CompareEditorInput)getEditorInput()).getTitle());
setTitleToolTip(((CompareEditorInput)getEditorInput()).getToolTipText());
} else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE_IMAGE)) {
setTitleImage(((CompareEditorInput)getEditorInput()).getTitleImage());
}
}
@Override
public Saveable[] getSaveables() {
return internalGetSaveables(knownSaveables == null);
}
private Saveable[] internalGetSaveables(boolean init) {
IEditorInput input= getEditorInput();
Saveable[] sourceSaveables = getSaveables(input);
if (init || knownSaveables == null) {
recordSaveables(sourceSaveables);
} else {
for (int i = 0; i < sourceSaveables.length; i++) {
Saveable saveable = sourceSaveables[i];
if (!knownSaveables.contains(saveable)) {
CompareUIPlugin.logErrorMessage(NLS.bind("Saveable {0} was not added using a saveables lifecycle event.", saveable.getName())); //$NON-NLS-1$
knownSaveables.add(saveable);
}
}
if (sourceSaveables.length != knownSaveables.size()) {
CompareUIPlugin.logErrorMessage("Saveables were removed without an appropriate event"); //$NON-NLS-1$
knownSaveables.clear();
recordSaveables(sourceSaveables);
}
}
return sourceSaveables;
}
private boolean isAllSaveablesKnown() {
IEditorInput input= getEditorInput();
Saveable[] sourceSaveables = getSaveables(input);
if (knownSaveables == null) {
return sourceSaveables.length == 0;
}
if (sourceSaveables.length != knownSaveables.size()) {
return false;
}
for (int i = 0; i < sourceSaveables.length; i++) {
Saveable saveable = sourceSaveables[i];
if (!knownSaveables.contains(saveable)) {
return false;
}
}
return true;
}
private void recordSaveables(Saveable[] sourceSaveables) {
if (knownSaveables == null)
knownSaveables = new HashSet<>();
Collections.addAll(knownSaveables, sourceSaveables);
}
private Saveable[] getSaveables(IEditorInput input) {
if (input instanceof ISaveablesSource) {
ISaveablesSource source= (ISaveablesSource) input;
return source.getSaveables();
}
return new Saveable[] { getSaveable() };
}
private Saveable getSaveable() {
if (fSaveable == null) {
fSaveable= new CompareSaveable();
}
return fSaveable;
}
@Override
public Saveable[] getActiveSaveables() {
IEditorInput input= getEditorInput();
if (input instanceof ISaveablesSource) {
ISaveablesSource source= (ISaveablesSource) input;
return source.getActiveSaveables();
}
return new Saveable[] { getSaveable() };
}
private class CompareSaveable extends Saveable {
@Override
public String getName() {
return CompareEditor.this.getPartName();
}
@Override
public String getToolTipText() {
return CompareEditor.this.getTitleToolTip();
}
@Override
public ImageDescriptor getImageDescriptor() {
return ImageDescriptor.createFromImage(CompareEditor.this.getTitleImage());
}
@Override
public void doSave(IProgressMonitor monitor) throws CoreException {
CompareEditor.this.doSave(monitor);
}
@Override
public boolean isDirty() {
return CompareEditor.this.isDirty();
}
@Override
public boolean equals(Object object) {
return object == this;
}
@Override
public int hashCode() {
return CompareEditor.this.hashCode();
}
}
private Composite getInitializingMessagePane(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(getBackgroundColor(parent));
GridLayout layout = new GridLayout();
layout.numColumns = 3;
composite.setLayout(layout);
createDescriptionLabel(composite, CompareMessages.CompareEditor_1);
return composite;
}
private Color getBackgroundColor(Composite parent) {
return parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
}
private Label createDescriptionLabel(Composite parent, String text) {
Label description = new Label(parent, SWT.WRAP);
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalSpan = 2;
description.setLayoutData(data);
description.setText(text);
description.setBackground(getBackgroundColor(parent));
return description;
}
private void closeEditor() {
getSite().getPage().closeEditor(CompareEditor.this, false);
}
private synchronized void setState(int state) {
this.state = state;
}
private int getState() {
return state;
}
@Override
public void handleLifecycleEvent(SaveablesLifecycleEvent event) {
ISaveablesLifecycleListener lifecycleListener=
getSite().getService(ISaveablesLifecycleListener.class);
if (event.getEventType() == SaveablesLifecycleEvent.POST_CLOSE) {
// We may get a post close for a saveable that is not known to the workbench.
// Only pass on the event for known saveables
if (knownSaveables == null || knownSaveables.isEmpty())
return;
java.util.List<Saveable> result = new ArrayList<>();
Saveable[] all = event.getSaveables();
for (int i = 0; i < all.length; i++) {
Saveable saveable = all[i];
if (knownSaveables.contains(saveable))
result.add(saveable);
knownSaveables.remove(saveable);
}
if (result.isEmpty())
return;
event = new SaveablesLifecycleEvent(this,
SaveablesLifecycleEvent.POST_CLOSE,
result.toArray(new Saveable[result.size()]),
false);
} else if (event.getEventType() == SaveablesLifecycleEvent.POST_OPEN) {
recordSaveables(event.getSaveables());
}
lifecycleListener.handleLifecycleEvent(event);
}
}