| /******************************************************************************* |
| * Copyright (c) 2006, 2008 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 |
| *******************************************************************************/ |
| package org.eclipse.team.ui.synchronize; |
| |
| import java.lang.reflect.InvocationTargetException; |
| |
| import org.eclipse.compare.*; |
| import org.eclipse.compare.structuremergeviewer.*; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.jface.action.*; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.ImageRegistry; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.viewers.*; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.*; |
| import org.eclipse.team.internal.ui.*; |
| import org.eclipse.team.internal.ui.history.CompareFileRevisionEditorInput; |
| import org.eclipse.team.internal.ui.synchronize.LocalResourceSaveableComparison; |
| import org.eclipse.team.internal.ui.synchronize.LocalResourceTypedElement; |
| import org.eclipse.team.internal.ui.synchronize.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener; |
| import org.eclipse.team.ui.mapping.SaveableComparison; |
| import org.eclipse.ui.*; |
| import org.eclipse.ui.actions.*; |
| import org.eclipse.ui.keys.IBindingService; |
| import org.eclipse.ui.services.IDisposable; |
| |
| /** |
| * A compare editor input that makes use of a {@link Saveable} to manage the save |
| * lifecycle of the editor input. If the element returned from |
| * {@link #createFileElement(IFile)} is used as the left side of the compare input |
| * and the default saveable returned from {@link #createSaveable()} is used, then |
| * this compare input will provide the complete save lifecycle for the local file. |
| * <p> |
| * Clients may subclass this class. |
| * </p> |
| * @since 3.3 |
| */ |
| public abstract class SaveableCompareEditorInput extends CompareEditorInput implements ISaveablesSource { |
| |
| private ICompareInputChangeListener compareInputChangeListener; |
| private final IWorkbenchPage page; |
| private final ListenerList inputChangeListeners = new ListenerList(ListenerList.IDENTITY); |
| private Saveable saveable; |
| private IPropertyListener propertyListener; |
| |
| /** |
| * Return a typed element that represents a local file. If the element |
| * returned from this method is used as the left contributor of the compare |
| * input for a {@link SaveableCompareEditorInput}, then the file will |
| * be properly saved when the compare editor input or viewers are saved. |
| * @param file the file |
| * @return a typed element that represents a local file. |
| */ |
| public static ITypedElement createFileElement(IFile file) { |
| return new LocalResourceTypedElement(file); |
| } |
| |
| private static ITypedElement getFileElement(ICompareInput input, |
| CompareEditorInput editorInput) { |
| if (input.getLeft() instanceof LocalResourceTypedElement) { |
| return (LocalResourceTypedElement) input.getLeft(); |
| } |
| if (editorInput instanceof CompareFileRevisionEditorInput) { |
| return ((CompareFileRevisionEditorInput) editorInput).getLocalElement(); |
| } |
| return null; |
| } |
| |
| private class InternalResourceSaveableComparison extends LocalResourceSaveableComparison implements ISharedDocumentAdapterListener { |
| private LocalResourceTypedElement lrte; |
| private boolean connected = false; |
| public InternalResourceSaveableComparison( |
| ICompareInput input, CompareEditorInput editorInput) { |
| super(input, editorInput, SaveableCompareEditorInput.getFileElement(input, editorInput)); |
| ITypedElement element = SaveableCompareEditorInput.getFileElement(input, editorInput); |
| if (element instanceof LocalResourceTypedElement) { |
| lrte = (LocalResourceTypedElement) element; |
| if (lrte.isConnected()) { |
| registerSaveable(true); |
| } else { |
| lrte.setSharedDocumentListener(this); |
| } |
| } |
| } |
| protected void fireInputChange() { |
| SaveableCompareEditorInput.this.fireInputChange(); |
| } |
| public void dispose() { |
| super.dispose(); |
| if (lrte != null) |
| lrte.setSharedDocumentListener(null); |
| } |
| public void handleDocumentConnected() { |
| if (connected) |
| return; |
| connected = true; |
| registerSaveable(false); |
| if (lrte != null) |
| lrte.setSharedDocumentListener(null); |
| } |
| |
| private void registerSaveable(boolean init) { |
| ICompareContainer container = getContainer(); |
| IWorkbenchPart part = container.getWorkbenchPart(); |
| if (part != null) { |
| ISaveablesLifecycleListener lifecycleListener= getSaveablesLifecycleListener(part); |
| // Remove this saveable from the lifecycle listener |
| if (!init) |
| lifecycleListener.handleLifecycleEvent( |
| new SaveablesLifecycleEvent(part, SaveablesLifecycleEvent.POST_CLOSE, new Saveable[] { this }, false)); |
| // Now fix the hashing so it uses the connected document |
| initializeHashing(); |
| // Finally, add this saveable back to the listener |
| lifecycleListener.handleLifecycleEvent( |
| new SaveablesLifecycleEvent(part, SaveablesLifecycleEvent.POST_OPEN, new Saveable[] { this }, false)); |
| } |
| } |
| public void handleDocumentDeleted() { |
| // Ignore |
| } |
| public void handleDocumentDisconnected() { |
| // Ignore |
| } |
| public void handleDocumentFlushed() { |
| // Ignore |
| } |
| public void handleDocumentSaved() { |
| // Ignore |
| } |
| } |
| |
| /** |
| * Creates a <code>LocalResourceCompareEditorInput</code> which is initialized with the given |
| * compare configuration. |
| * The compare configuration is passed to subsequently created viewers. |
| * |
| * @param configuration the compare configuration |
| * @param page the workbench page that will contain the editor |
| */ |
| public SaveableCompareEditorInput(CompareConfiguration configuration, IWorkbenchPage page) { |
| super(configuration); |
| this.page = page; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#contentsCreated() |
| */ |
| protected void contentsCreated() { |
| super.contentsCreated(); |
| compareInputChangeListener = new ICompareInputChangeListener() { |
| public void compareInputChanged(ICompareInput source) { |
| if (source == getCompareResult()) { |
| boolean closed = false; |
| if (source.getKind() == Differencer.NO_CHANGE) { |
| closed = closeEditor(true); |
| } |
| if (!closed) { |
| // The editor was closed either because the compare input still has changes |
| // or because the editor input is dirty. In either case, fire the changes |
| // to the registered listeners |
| propogateInputChange(); |
| } |
| } |
| } |
| }; |
| getCompareInput().addCompareInputChangeListener(compareInputChangeListener); |
| |
| if (getSaveable() instanceof SaveableComparison) { |
| SaveableComparison scm = (SaveableComparison) saveable; |
| propertyListener = new IPropertyListener() { |
| public void propertyChanged(Object source, int propId) { |
| if (propId == SaveableComparison.PROP_DIRTY) { |
| setDirty(saveable.isDirty()); |
| } |
| } |
| }; |
| scm.addPropertyListener(propertyListener); |
| } |
| setDirty(saveable.isDirty()); |
| } |
| |
| private ISaveablesLifecycleListener getSaveablesLifecycleListener( |
| IWorkbenchPart part) { |
| ISaveablesLifecycleListener listener = (ISaveablesLifecycleListener)Utils.getAdapter(part, ISaveablesLifecycleListener.class); |
| if (listener == null) |
| listener = (ISaveablesLifecycleListener) part.getSite().getService(ISaveablesLifecycleListener.class); |
| return listener; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#handleDispose() |
| */ |
| protected void handleDispose() { |
| super.handleDispose(); |
| ICompareInput compareInput = getCompareInput(); |
| if (compareInput != null) |
| compareInput.removeCompareInputChangeListener(compareInputChangeListener); |
| compareInputChangeListener = null; |
| if (saveable instanceof SaveableComparison) { |
| SaveableComparison scm = (SaveableComparison) saveable; |
| scm.removePropertyListener(propertyListener); |
| propertyListener = null; |
| } |
| if (saveable instanceof LocalResourceSaveableComparison) { |
| LocalResourceSaveableComparison rsc = (LocalResourceSaveableComparison) saveable; |
| rsc.dispose(); |
| } |
| if (getCompareResult() instanceof IDisposable) { |
| ((IDisposable) getCompareResult()).dispose(); |
| } |
| } |
| |
| /** |
| * Prepare the compare input of this editor input. This method is not intended to be overridden of |
| * extended by subclasses (but is not final for backwards compatibility reasons). |
| * The implementation of this method in this class |
| * delegates the creation of the compare input to the {@link #prepareCompareInput(IProgressMonitor)} |
| * method which subclasses must implement. |
| * @see org.eclipse.compare.CompareEditorInput#prepareInput(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| protected Object prepareInput(IProgressMonitor monitor) |
| throws InvocationTargetException, InterruptedException { |
| final ICompareInput input = prepareCompareInput(monitor); |
| if (input != null) |
| setTitle(NLS.bind(TeamUIMessages.SyncInfoCompareInput_title, new String[] { input.getName()})); |
| return input; |
| } |
| |
| /** |
| * Method called from {@link #prepareInput(IProgressMonitor)} to obtain the input. |
| * It's purpose is to ensure that the input is an instance of {@link ICompareInput}. |
| * @param monitor a progress monitor |
| * @return the compare input |
| * @throws InvocationTargetException |
| * @throws InterruptedException |
| */ |
| protected abstract ICompareInput prepareCompareInput(IProgressMonitor monitor) |
| throws InvocationTargetException, InterruptedException; |
| |
| /** |
| * Return the compare input of this editor input. |
| * @return the compare input of this editor input |
| */ |
| protected final ICompareInput getCompareInput() { |
| return (ICompareInput)getCompareResult(); |
| } |
| |
| /** |
| * Callback from the resource saveable that is invoked when the resource is |
| * saved so that this input can fire a change event for its input. Subclasses |
| * only need this method if the left side of their compare input is |
| * an element returned from {@link #createFileElement(IFile)}. |
| */ |
| protected abstract void fireInputChange(); |
| |
| /** |
| * Close the editor if it is not dirty. If it is still dirty, let the |
| * content merge viewer handle the compare input change. |
| * @param checkForUnsavedChanges whether to check for unsaved changes |
| * @return <code>true</code> if the editor was closed (note that the |
| * close may be asynchronous) |
| */ |
| protected boolean closeEditor(boolean checkForUnsavedChanges) { |
| if (isSaveNeeded() && checkForUnsavedChanges) { |
| return false; |
| } else { |
| Runnable runnable = new Runnable() { |
| public void run() { |
| IEditorPart part = getPage().findEditor(SaveableCompareEditorInput.this); |
| getPage().closeEditor(part, false); |
| } |
| }; |
| if (Display.getCurrent() != null) { |
| runnable.run(); |
| } else { |
| Display display = getPage().getWorkbenchWindow().getShell().getDisplay(); |
| display.asyncExec(runnable); |
| } |
| return true; |
| } |
| } |
| |
| private IWorkbenchPage getPage() { |
| if (page == null) |
| return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); |
| return page; |
| } |
| |
| /* package */ void propogateInputChange() { |
| if (!inputChangeListeners.isEmpty()) { |
| Object[] allListeners = inputChangeListeners.getListeners(); |
| for (int i = 0; i < allListeners.length; i++) { |
| final ICompareInputChangeListener listener = (ICompareInputChangeListener)allListeners[i]; |
| SafeRunner.run(new ISafeRunnable() { |
| public void run() throws Exception { |
| listener.compareInputChanged((ICompareInput)SaveableCompareEditorInput.this.getCompareResult()); |
| } |
| public void handleException(Throwable exception) { |
| // Logged by the safe runner |
| } |
| }); |
| } |
| } |
| } |
| |
| /** |
| * Get the saveable that provides the save behavior for this compare editor input. |
| * The {@link #createSaveable()} is called to create the saveable if it does not yet exist. |
| * This method cannot be called until after the input is prepared (i.e. until after |
| * the {@link #run(IProgressMonitor)} method is called which will in turn will invoke |
| * {@link #prepareCompareInput(IProgressMonitor)}. |
| * @return saveable that provides the save behavior for this compare editor input. |
| */ |
| protected Saveable getSaveable() { |
| if (saveable == null) { |
| saveable = createSaveable(); |
| } |
| return saveable; |
| } |
| |
| /** |
| * Create the saveable that provides the save behavior for this compare editor input. |
| * By default, a saveable that handles local files is returned |
| * @return the saveable that provides the save behavior for this compare editor input |
| */ |
| protected Saveable createSaveable() { |
| Object compareResult = getCompareResult(); |
| Assert.isNotNull(compareResult, "This method cannot be called until after prepareInput is called"); //$NON-NLS-1$ |
| return new InternalResourceSaveableComparison((ICompareInput)compareResult, this); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.ISaveablesSource#getActiveSaveables() |
| */ |
| public Saveable[] getActiveSaveables() { |
| if (getCompareResult() == null) |
| return new Saveable[0]; |
| return new Saveable[] { getSaveable() }; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.ISaveablesSource#getSaveables() |
| */ |
| public Saveable[] getSaveables() { |
| return getActiveSaveables(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener) |
| */ |
| public void addCompareInputChangeListener(ICompareInput input, |
| ICompareInputChangeListener listener) { |
| if (input == getCompareResult()) { |
| inputChangeListeners.add(listener); |
| } else { |
| super.addCompareInputChangeListener(input, listener); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener) |
| */ |
| public void removeCompareInputChangeListener(ICompareInput input, |
| ICompareInputChangeListener listener) { |
| if (input == getCompareResult()) { |
| inputChangeListeners.remove(listener); |
| } else { |
| super.removeCompareInputChangeListener(input, listener); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#getAdapter(java.lang.Class) |
| */ |
| public Object getAdapter(Class adapter) { |
| if (IFile.class.equals(adapter)) { |
| IResource resource = Utils.getResource(getCompareResult()); |
| if (resource instanceof IFile) { |
| return resource; |
| } |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#getTitleImage() |
| */ |
| public Image getTitleImage() { |
| ImageRegistry reg = TeamUIPlugin.getPlugin().getImageRegistry(); |
| Image image = reg.get(ITeamUIImages.IMG_SYNC_VIEW); |
| if (image == null) { |
| image = getImageDescriptor().createImage(); |
| reg.put(ITeamUIImages.IMG_SYNC_VIEW, image); |
| } |
| return image; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.ui.IEditorInput#getImageDescriptor() |
| */ |
| public ImageDescriptor getImageDescriptor() { |
| return TeamUIPlugin.getImageDescriptor(ITeamUIImages.IMG_SYNC_VIEW); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#findContentViewer(org.eclipse.jface.viewers.Viewer, org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.swt.widgets.Composite) |
| */ |
| public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) { |
| Viewer newViewer = super.findContentViewer(oldViewer, input, parent); |
| boolean isNewViewer= newViewer != oldViewer; |
| if (isNewViewer && newViewer instanceof IPropertyChangeNotifier && saveable instanceof IPropertyChangeListener) { |
| // Register the model for change events if appropriate |
| final IPropertyChangeNotifier dsp= (IPropertyChangeNotifier) newViewer; |
| final IPropertyChangeListener pcl = (IPropertyChangeListener) saveable; |
| dsp.addPropertyChangeListener(pcl); |
| Control c= newViewer.getControl(); |
| c.addDisposeListener( |
| new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| dsp.removePropertyChangeListener(pcl); |
| } |
| } |
| ); |
| } |
| return newViewer; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.compare.CompareEditorInput#canRunAsJob() |
| */ |
| public boolean canRunAsJob() { |
| return true; |
| } |
| |
| public boolean isDirty() { |
| if (saveable != null) |
| return saveable.isDirty(); |
| return super.isDirty(); |
| } |
| |
| public void registerContextMenu(final MenuManager menu, |
| final ISelectionProvider selectionProvider) { |
| super.registerContextMenu(menu, selectionProvider); |
| final Saveable saveable = getSaveable(); |
| if (saveable instanceof LocalResourceSaveableComparison) { |
| menu.addMenuListener(new IMenuListener() { |
| public void menuAboutToShow(IMenuManager manager) { |
| handleMenuAboutToShow(manager, saveable, selectionProvider); |
| } |
| }); |
| } |
| } |
| |
| /* package */ void handleMenuAboutToShow (IMenuManager manager, Saveable saveable, ISelectionProvider provider) { |
| if (provider instanceof ITextViewer) { |
| ITextViewer v = (ITextViewer) provider; |
| IDocument d = v.getDocument(); |
| IDocument other = (IDocument)Utils.getAdapter(saveable, IDocument.class); |
| if (d == other) { |
| ITypedElement element = getFileElement(getCompareInput(), this); |
| if (element instanceof IResourceProvider) { |
| IResourceProvider rp = (IResourceProvider) element; |
| IResource resource = rp.getResource(); |
| StructuredSelection selection = new StructuredSelection(resource); |
| IWorkbenchPart workbenchPart = getContainer().getWorkbenchPart(); |
| if (workbenchPart != null) { |
| IWorkbenchSite ws = workbenchPart.getSite(); |
| |
| MenuManager submenu1 = |
| new MenuManager(getShowInMenuLabel()); |
| IContributionItem showInMenu = ContributionItemFactory.VIEWS_SHOW_IN.create(ws.getWorkbenchWindow()); |
| submenu1.add(showInMenu); |
| manager.insertAfter("file", submenu1); //$NON-NLS-1$ |
| MenuManager submenu2 = |
| new MenuManager(TeamUIMessages.OpenWithActionGroup_0); |
| submenu2.add(new OpenWithMenu(ws.getPage(), resource)); |
| manager.insertAfter("file", submenu2); //$NON-NLS-1$ |
| |
| OpenFileAction openFileAction = new OpenFileAction(ws.getPage()); |
| openFileAction.selectionChanged(selection); |
| manager.insertAfter("file", openFileAction); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| } |
| |
| private String getShowInMenuLabel() { |
| String keyBinding= null; |
| |
| IBindingService bindingService= (IBindingService)PlatformUI.getWorkbench().getAdapter(IBindingService.class); |
| if (bindingService != null) |
| keyBinding= bindingService.getBestActiveBindingFormattedFor("org.eclipse.ui.navigate.showInQuickMenu"); //$NON-NLS-1$ |
| |
| if (keyBinding == null) |
| keyBinding= ""; //$NON-NLS-1$ |
| |
| return NLS.bind(TeamUIMessages.SaveableCompareEditorInput_0, keyBinding); |
| } |
| |
| } |