/******************************************************************************* * Copyright (c) 2000, 2011 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.compare; import java.lang.reflect.InvocationTargetException; import java.util.ResourceBundle; import org.eclipse.compare.contentmergeviewer.ContentMergeViewer; import org.eclipse.compare.contentmergeviewer.IFlushable; import org.eclipse.compare.internal.BinaryCompareViewer; import org.eclipse.compare.internal.ChangePropertyAction; import org.eclipse.compare.internal.CompareContentViewerSwitchingPane; import org.eclipse.compare.internal.CompareEditorInputNavigator; import org.eclipse.compare.internal.CompareMessages; import org.eclipse.compare.internal.ComparePreferencePage; import org.eclipse.compare.internal.CompareStructureViewerSwitchingPane; import org.eclipse.compare.internal.CompareUIPlugin; import org.eclipse.compare.internal.ICompareUIConstants; import org.eclipse.compare.internal.IFlushable2; import org.eclipse.compare.internal.OutlineViewerCreator; import org.eclipse.compare.internal.Utilities; import org.eclipse.compare.internal.ViewerDescriptor; import org.eclipse.compare.structuremergeviewer.DiffTreeViewer; import org.eclipse.compare.structuremergeviewer.ICompareInput; import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener; import org.eclipse.compare.structuremergeviewer.IDiffContainer; import org.eclipse.core.resources.IFile; 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.ListenerList; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.PlatformObject; 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.action.Separator; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.IPreferenceStore; 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.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IPersistableElement; import org.eclipse.ui.ISaveablesSource; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.ShowInContext; import org.eclipse.ui.services.IServiceLocator; import org.eclipse.ui.texteditor.ITextEditorExtension3; /** * A compare operation which can present its results in a special editor. * Running the compare operation and presenting the results in a compare editor * are combined in one class because it allows a client to keep the implementation * all in one place while separating it from the innards of a specific UI implementation of compare/merge. * <p> * A <code>CompareEditorInput</code> defines methods for the following sequence steps: * <UL> * <LI>running a lengthy compare operation under progress monitor control, * <LI>creating a UI for displaying the model and initializing the some widgets with the compare result, * <LI>tracking the dirty state of the model in case of merge, * <LI>saving the model. * </UL> * The Compare plug-in's <code>openCompareEditor</code> method takes an <code>CompareEditorInput</code> * and starts sequencing through the above steps. If the compare result is not empty a new compare editor * is opened and takes over the sequence until eventually closed. * <p> * The <code>prepareInput</code> method should contain the * code of the compare operation. It is executed under control of a progress monitor * and can be canceled. If the result of the compare is not empty, that is if there are differences * that needs to be presented, the <code>ICompareInput</code> should hold onto them and return them with * the <code>getCompareResult</code> method. * If the value returned from <code>getCompareResult</code> is not <code>null</code> * a compare editor is opened on the <code>ICompareInput</code> with title and title image initialized by the * corresponding methods of the <code>ICompareInput</code>. * <p> * Creation of the editor's SWT controls is delegated to the <code>createContents</code> method. * Here the SWT controls must be created and initialized with the result of the compare operation. * <p> * If merging is allowed, the modification state of the compared constituents must be tracked and the dirty * state returned from method <code>isSaveNeeded</code>. The value <code>true</code> triggers a subsequent call * to <code>save</code> where the modified resources can be saved. * <p> * The most important part of this implementation is the setup of the compare/merge UI. * The UI uses a simple browser metaphor to present compare results. * The top half of the layout shows the structural compare results (e.g. added, deleted, and changed files), * the bottom half the content compare results (e.g. textual differences between two files). * A selection in the top pane is fed to the bottom pane. If a content viewer is registered * for the type of the selected object, this viewer is installed in the pane. * In addition if a structure viewer is registered for the selection type the top pane * is split vertically to make room for another pane and the structure viewer is installed * in it. When comparing Java files this second structure viewer would show the structural * differences within a Java file, e.g. added, deleted or changed methods and fields. * <p> * Subclasses provide custom setups, e.g. for a Catch-up/Release operation * by passing a subclass of <code>CompareConfiguration</code> and by implementing the <code>prepareInput</code> method. * If a subclass cannot use the <code>DiffTreeViewer</code> which is installed by default in the * top left pane, method <code>createDiffViewer</code> can be overridden. * <p> * If subclasses of this class implement {@link ISaveablesSource}, the compare editor will * pass these models through to the workbench. The editor will still show the dirty indicator * if one of these underlying models is dirty. It is the responsibility of subclasses that * implement this interface to call {@link #setDirty(boolean)} when the dirty state of * any of the models managed by the subclass change dirty state. * * @author Stas Negara - Added getContentViewer() (in order to be able to get to its contained * viewers) * * @see CompareUI * @see CompareEditorInput */ public abstract class CompareEditorInput extends PlatformObject implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress, ICompareContainer { private static final boolean DEBUG= false; /** * The name of the "dirty" property (value <code>"DIRTY_STATE"</code>). */ public static final String DIRTY_STATE= "DIRTY_STATE"; //$NON-NLS-1$ /** * The name of the "title" property. This property is fired when the title * of the compare input changes. Clients should also re-obtain the tool tip * when this property changes. * @see #getTitle() * @since 3.3 */ public static final String PROP_TITLE= ICompareUIConstants.PROP_TITLE; /** * The name of the "title image" property. This property is fired when the title * image of the compare input changes. * @see #getTitleImage() * @since 3.3 */ public static final String PROP_TITLE_IMAGE= ICompareUIConstants.PROP_TITLE_IMAGE; /** * The name of the "selected edition" property. This property is fired when the selected * edition of the compare input changes. * @see #isEditionSelectionDialog() * @see #getSelectedEdition() * @since 3.3 */ public static final String PROP_SELECTED_EDITION= ICompareUIConstants.PROP_SELECTED_EDITION; private static final String COMPARE_EDITOR_IMAGE_NAME= "eview16/compare_view.gif"; //$NON-NLS-1$ private static Image fgTitleImage; private Splitter fComposite; private CompareConfiguration fCompareConfiguration; private CompareViewerPane fStructureInputPane; private CompareViewerSwitchingPane fStructurePane1; private CompareViewerSwitchingPane fStructurePane2; private CompareViewerSwitchingPane fContentInputPane; private CompareViewerPane fFocusPane; private String fMessage; private Object fInput; private String fTitle; private ListenerList fListenerList= new ListenerList(); private CompareNavigator fNavigator; private ContentMergeViewer fLeftDirtyViewer = null; private ContentMergeViewer fRightDirtyViewer = null; private IPropertyChangeListener fDirtyStateListener; boolean fStructureCompareOnSingleClick= true; private ICompareContainer fContainer; private boolean fContainerProvided; private String fHelpContextId; private InternalOutlineViewerCreator fOutlineView; private ViewerDescriptor fContentViewerDescriptor; private ViewerDescriptor fStructureViewerDescriptor; private class InternalOutlineViewerCreator extends OutlineViewerCreator { private OutlineViewerCreator getWrappedCreator() { if (fContentInputPane != null) { Viewer v = fContentInputPane.getViewer(); if (v != null) { return (OutlineViewerCreator)Utilities.getAdapter(v, OutlineViewerCreator.class); } } return null; } public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent, CompareConfiguration configuration) { OutlineViewerCreator creator = getWrappedCreator(); if (creator != null) return creator.findStructureViewer(oldViewer, input, parent, configuration); return null; } public boolean hasViewerFor(Object input) { OutlineViewerCreator creator = getWrappedCreator(); return creator != null; } public Object getInput() { OutlineViewerCreator creator = getWrappedCreator(); if (creator != null) return creator.getInput(); return null; } } //CODINGSPECTATOR: Added the method getContentViewer. public Viewer getContentViewer() { if (fContentInputPane != null) { return fContentInputPane.getViewer(); } return null; } /** * Creates a <code>CompareEditorInput</code> which is initialized with the given * compare configuration. * The compare configuration is passed to subsequently created viewers. * * @param configuration the compare configuration */ public CompareEditorInput(CompareConfiguration configuration) { fCompareConfiguration= configuration; Assert.isNotNull(configuration); fDirtyStateListener= new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { String propertyName= e.getProperty(); if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) { boolean changed= false; Object newValue= e.getNewValue(); if (newValue instanceof Boolean) changed= ((Boolean)newValue).booleanValue(); setDirty(e.getSource(), changed); } } }; IPreferenceStore ps= configuration.getPreferenceStore(); if (ps != null) fStructureCompareOnSingleClick= ps.getBoolean(ComparePreferencePage.OPEN_STRUCTURE_COMPARE); fContainer = configuration.getContainer(); configuration.setContainer(this); } private boolean structureCompareOnSingleClick() { return fStructureCompareOnSingleClick; } private boolean isShowStructureInOutlineView() { Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW); return object instanceof Boolean && ((Boolean)object).booleanValue(); } /* (non Javadoc) * see IAdaptable.getAdapter */ public Object getAdapter(Class adapter) { if (ICompareNavigator.class.equals(adapter) || CompareNavigator.class.equals(adapter)) { return getNavigator(); } if (adapter == IShowInSource.class) { final IFile file = (IFile)Utilities.getAdapter(this, IFile.class); if (file != null) return new IShowInSource() { public ShowInContext getShowInContext() { return new ShowInContext(new FileEditorInput(file), StructuredSelection.EMPTY); } }; } if (adapter == OutlineViewerCreator.class) { synchronized (this) { if (fOutlineView == null) fOutlineView = new InternalOutlineViewerCreator(); return fOutlineView; } } if (adapter == IFindReplaceTarget.class) { if (fContentInputPane != null) { Viewer v = fContentInputPane.getViewer(); if (v != null) { return Utilities.getAdapter(v, IFindReplaceTarget.class); } } } if (adapter == IEditorInput.class) { if (fContentInputPane != null) { Viewer v = fContentInputPane.getViewer(); if (v != null) { return Utilities.getAdapter(v, IEditorInput.class); } } } if (adapter == ITextEditorExtension3.class) { if (fContentInputPane != null) { Viewer v = fContentInputPane.getViewer(); if (v != null) { return Utilities.getAdapter(v, ITextEditorExtension3.class); } } } return super.getAdapter(adapter); } public synchronized ICompareNavigator getNavigator() { if (fNavigator == null) fNavigator= new CompareEditorInputNavigator( new Object[] { fStructureInputPane, fStructurePane1, fStructurePane2, fContentInputPane } ); return fNavigator; } /* (non Javadoc) * see IEditorInput.getImageDescriptor */ public ImageDescriptor getImageDescriptor() { return null; } /* (non Javadoc) * see IEditorInput.getToolTipText */ public String getToolTipText() { return getTitle(); } /* (non Javadoc) * see IEditorInput.getName */ public String getName() { return getTitle(); } /** * Returns <code>null</code> since this editor cannot be persisted. * * @return <code>null</code> because this editor cannot be persisted */ public IPersistableElement getPersistable() { return null; } /** * Returns <code>false</code> to indicate that this input * should not appear in the "File Most Recently Used" menu. * * @return <code>false</code> */ public boolean exists() { return false; } /* * FIXME! */ protected void setMessage(String message) { fMessage= message; } /* * FIXME! */ public String getMessage() { return fMessage; } /** * Returns the title which will be used in the compare editor's title bar. * It can be set with <code>setTitle</code>. * * @return the title */ public String getTitle() { if (fTitle == null) return Utilities.getString("CompareEditorInput.defaultTitle"); //$NON-NLS-1$ return fTitle; } /** * Sets the title which will be used when presenting the compare result. * This method must be called before the editor is opened. * * @param title the title to use for the CompareEditor */ public void setTitle(String title) { String oldTitle = fTitle; fTitle= title; Utilities.firePropertyChange(fListenerList, this, PROP_TITLE, oldTitle, title); } /** * Returns the title image which will be used in the compare editor's title bar. * Returns the title image which will be used when presenting the compare result. * This implementation returns a generic compare icon. * Subclasses can override. * * @return the title image, or <code>null</code> if none */ public Image getTitleImage() { if (fgTitleImage == null) { fgTitleImage= CompareUIPlugin.getImageDescriptor(COMPARE_EDITOR_IMAGE_NAME).createImage(); CompareUI.disposeOnShutdown(fgTitleImage); } return fgTitleImage; } /** * Returns the configuration object for the viewers within the compare editor. * Returns the configuration which was passed to the constructor. * * @return the compare configuration */ public CompareConfiguration getCompareConfiguration() { return fCompareConfiguration; } /** * Adds standard actions to the given <code>ToolBarManager</code>. * <p> * Subclasses may override to add their own actions. * </p> * * @param toolBarManager the <code>ToolBarManager</code> to which to contribute */ public void contributeToToolBar(ToolBarManager toolBarManager) { ResourceBundle bundle= CompareUI.getResourceBundle(); ChangePropertyAction ignoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle, getCompareConfiguration()); toolBarManager.getControl().addDisposeListener(ignoreWhitespace); ChangePropertyAction showPseudoConflicts= ChangePropertyAction.createShowPseudoConflictsAction(bundle, getCompareConfiguration()); toolBarManager.getControl().addDisposeListener(showPseudoConflicts); toolBarManager.add(new Separator()); toolBarManager.add(ignoreWhitespace); toolBarManager.add(showPseudoConflicts); } /** * Runs the compare operation and stores the compare result. * * @param monitor the progress monitor to use to display progress and receive * requests for cancelation * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception, * it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically * wrapped in an <code>InvocationTargetException</code> by the calling context * @exception InterruptedException if the operation detects a request to cancel, * using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing * <code>InterruptedException</code> */ public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException { fInput= prepareInput(monitor); } /** * Runs the compare operation and returns the compare result. * If <code>null</code> is returned no differences were found and no compare editor needs to be opened. * Progress should be reported to the given progress monitor. * A request to cancel the operation should be honored and acknowledged * by throwing <code>InterruptedException</code>. * <p> * Note: this method is typically called in a modal context thread which doesn't have a Display assigned. * Implementors of this method shouldn't therefore allocated any SWT resources in this method. * </p> * * @param monitor the progress monitor to use to display progress and receive * requests for cancelation * @return the result of the compare operation, or <code>null</code> if there are no differences * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception, * it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically * wrapped in an <code>InvocationTargetException</code> by the calling context * @exception InterruptedException if the operation detects a request to cancel, * using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing * <code>InterruptedException</code> */ protected abstract Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException; /** * Returns the compare result computed by the most recent call to the * <code>run</code> method. Returns <code>null</code> if no * differences were found. * * @return the compare result prepared in method <code>prepareInput</code> * or <code>null</code> if there were no differences */ public Object getCompareResult() { return fInput; } /** * Create the SWT controls that are used to display the result of the compare operation. * Creates the SWT Controls and sets up the wiring between the individual panes. * This implementation creates all four panes but makes only the necessary ones visible. * Finally it feeds the compare result into the top left structure viewer * and the content viewer. * <p> * Subclasses may override if they need to change the layout or wiring between panes. * * @param parent the parent control under which the control must be created * @return the SWT control hierarchy for the compare editor */ public Control createContents(Composite parent) { fComposite= new Splitter(parent, SWT.VERTICAL); fComposite.setData(this); Control outline= createOutlineContents(fComposite, SWT.HORIZONTAL); fContentInputPane= createContentViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT, this); if (fFocusPane == null) fFocusPane= fContentInputPane; if (outline != null) fComposite.setVisible(outline, false); fComposite.setVisible(fContentInputPane, true); if (fStructureInputPane != null && fComposite.getChildren().length == 2) fComposite.setWeights(new int[] { 30, 70 }); fComposite.layout(); feedInput(); fComposite.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { /* * When the UI associated with this compare editor input is * disposed each composite being part of the UI releases its * children first. A dispose listener is added to the last * widget found in that structure. Therefore, compare editor * input is disposed at the end making it possible to refer * during widgets disposal. */ Composite composite = fComposite; Control control = composite; while (composite.getChildren().length > 0) { control = composite.getChildren()[composite.getChildren().length - 1]; if (control instanceof Composite) composite = (Composite) control; else break; } control.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent ev) { handleDispose(); } }); } }); if (fHelpContextId != null) PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, fHelpContextId); contentsCreated(); return fComposite; } /** * @param parent the parent control under which the control must be created * @param style the style of widget to construct * @param cei the compare editor input for the viewer * @return the pane displaying content changes * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ protected CompareViewerSwitchingPane createContentViewerSwitchingPane(Splitter parent, int style, CompareEditorInput cei) { return new CompareContentViewerSwitchingPane(parent, style, cei); } /** * Callback that occurs when the UI associated with this compare editor * input is disposed. This method will only be invoked if the UI has been * created (i.e. after the call to {@link #createContents(Composite)}. * Subclasses can extend this method but ensure that the overridden method * is invoked. * * @since 3.3 */ protected void handleDispose() { fContainerProvided = false; fContainer = null; fComposite = null; fStructureInputPane = null; fStructurePane1 = null; fStructurePane2 = null; fContentInputPane = null; fFocusPane = null; fNavigator = null; fCompareConfiguration.dispose(); } /** * Callback that occurs after the control for the input has * been created. If this method gets invoked then {@link #handleDispose()} * will be invoked when the control is disposed. Subclasses may extend this * method to register any listeners that need to be de-registered when the * input is disposed. * @since 3.3 */ protected void contentsCreated() { // Default is to do nothing } /** * @param parent the parent control under which the control must be created * @param direction the layout direction of the contents, either </code>SWT.HORIZONTAL<code> or </code>SWT.VERTICAL<code> * @return the SWT control hierarchy for the outline part of the compare editor * @since 3.0 */ public Control createOutlineContents(Composite parent, int direction) { final Splitter h= new Splitter(parent, direction); fStructureInputPane= createStructureInputPane(h); if (hasChildren(getCompareResult())) fFocusPane= fStructureInputPane; fStructurePane1= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this); h.setVisible(fStructurePane1, false); fStructurePane2= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this); h.setVisible(fStructurePane2, false); // setup the wiring for top left pane fStructureInputPane.addOpenListener( new IOpenListener() { public void open(OpenEvent oe) { feed1(oe.getSelection()); } } ); fStructureInputPane.addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { ISelection s= e.getSelection(); if (s == null || s.isEmpty()) feed1(s); if (isEditionSelectionDialog()) firePropertyChange(new PropertyChangeEvent(this, PROP_SELECTED_EDITION, null, getSelectedEdition())); } } ); fStructureInputPane.addDoubleClickListener( new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { feedDefault1(event.getSelection()); } } ); fStructurePane1.addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { feed2(e.getSelection()); } } ); fStructurePane2.addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { feed3(e.getSelection()); } } ); return h; } /** * Create the pane that will contain the structure input pane (upper left). * By default, a {@link CompareViewerSwitchingPane} is returned. Subclasses * may override to provide an alternate pane. * @param parent the parent composite * @return the structure input pane * @since 3.3 */ protected CompareViewerPane createStructureInputPane( final Composite parent) { return new CompareStructureViewerSwitchingPane(parent, SWT.BORDER | SWT.FLAT, true, this) { protected Viewer getViewer(Viewer oldViewer, Object input) { if (CompareEditorInput.this.hasChildren(input)) { return createDiffViewer(this); } return super.getViewer(oldViewer, input); } }; } /* private */ boolean hasChildren(Object input) { if (input instanceof IDiffContainer) { IDiffContainer dn= (IDiffContainer) input; return dn.hasChildren(); } return false; } private void feedInput() { if (fStructureInputPane != null && (fInput instanceof ICompareInput || isCustomStructureInputPane())) { if (hasChildren(fInput) || isCustomStructureInputPane()) { // The input has multiple entries so set the input of the structure input pane fStructureInputPane.setInput(fInput); } else if (!structureCompareOnSingleClick() || isShowStructureInOutlineView()) { // We want to avoid showing the structure in the editor if we can so first // we'll set the content pane to see if we need to provide a structure internalSetContentPaneInput(fInput); // If the content viewer is unusable if (hasUnusableContentViewer() || (structureCompareOnSingleClick() && isShowStructureInOutlineView() && !hasOutlineViewer(fInput))) { fStructureInputPane.setInput(fInput); } } else { fStructureInputPane.setInput(fInput); } ISelection sel= fStructureInputPane.getSelection(); if (sel == null || sel.isEmpty()) feed1(sel); // we only feed downstream viewers if the top left pane is empty } } private boolean hasOutlineViewer(Object input) { if (!isShowStructureInOutlineView()) return false; OutlineViewerCreator creator = (OutlineViewerCreator)getAdapter(OutlineViewerCreator.class); if (creator != null) return creator.hasViewerFor(input); return false; } private boolean hasUnusableContentViewer() { return fContentInputPane.isEmpty() || fContentInputPane.getViewer() instanceof BinaryCompareViewer; } private boolean isCustomStructureInputPane() { return !(fStructureInputPane instanceof CompareViewerSwitchingPane); } private void feed1(final ISelection selection) { BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() { public void run() { if (selection == null || selection.isEmpty()) { Object input= fStructureInputPane.getInput(); if (input != null) internalSetContentPaneInput(input); if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2)) return; fStructurePane2.setInput(null); // clear downstream pane fStructurePane1.setInput(null); } else { Object input= getElement(selection); internalSetContentPaneInput(input); if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2)) return; if (structureCompareOnSingleClick() || hasUnusableContentViewer()) fStructurePane1.setInput(input); fStructurePane2.setInput(null); // clear downstream pane if (fStructurePane1.getInput() != input) fStructurePane1.setInput(null); } } } ); } private void feedDefault1(final ISelection selection) { BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() { public void run() { if (!selection.isEmpty()) fStructurePane1.setInput(getElement(selection)); } } ); } private void feed2(final ISelection selection) { BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() { public void run() { if (selection.isEmpty()) { Object input= fStructurePane1.getInput(); internalSetContentPaneInput(input); fStructurePane2.setInput(null); } else { Object input= getElement(selection); internalSetContentPaneInput(input); fStructurePane2.setInput(input); } } } ); } private void feed3(final ISelection selection) { BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() { public void run() { if (selection.isEmpty()) internalSetContentPaneInput(fStructurePane2.getInput()); else internalSetContentPaneInput(getElement(selection)); } } ); } private void internalSetContentPaneInput(Object input) { Object oldInput = fContentInputPane.getInput(); fContentInputPane.setInput(input); if (fOutlineView != null) fOutlineView.fireInputChange(oldInput, input); } /** * Returns the first element of the given selection if the selection * is a <code>IStructuredSelection</code> with exactly one element. Returns * <code>null</code> otherwise. * * @param selection the selection * @return the first element of the selection, or <code>null</code> */ private static Object getElement(ISelection selection) { if (selection instanceof IStructuredSelection) { IStructuredSelection ss= (IStructuredSelection) selection; if (ss.size() == 1) return ss.getFirstElement(); } return null; } /** * Asks this input to take focus within its container (editor). * * @noreference Clients should not call this method but they may override if * they implement a different layout with different visual * components. Clients are free to call the inherited method. * * @deprecated Please use {@link #setFocus2()} instead. */ public void setFocus() { setFocus2(); } /** * Asks this input to take focus within its container (editor). * * @noreference Clients should not call this method but they may override if * they implement a different layout with different visual * components. Clients are free to call the inherited method. * * @return <code>true</code> if the input got focus, and <code>false</code> * if it was unable to. * @since 3.5 */ public boolean setFocus2() { if (fFocusPane != null) { return fFocusPane.setFocus(); } else if (fComposite != null) return fComposite.setFocus(); return false; } /** * Factory method for creating a differences viewer for the top left pane. * It is called from <code>createContents</code> and returns a <code>DiffTreeViewer</code>. * <p> * Subclasses may override if they need a different viewer. * </p> * * @param parent the SWT parent control under which to create the viewer's SWT controls * @return a compare viewer for the top left pane */ public Viewer createDiffViewer(Composite parent) { return new DiffTreeViewer(parent, fCompareConfiguration); } /** * Implements the dynamic viewer switching for structure viewers. * The method must return a compare viewer based on the old (or current) viewer * and a new input object. If the old viewer is suitable for showing the new input the old viewer * can be returned. Otherwise a new viewer must be created under the given parent composite or * <code>null</code> can be returned to indicate that no viewer could be found. * <p> * This implementation forwards the request to <code>CompareUI.findStructureViewer</code>. * <p> * Subclasses may override to implement a different strategy. * </p> * @param oldViewer a new viewer is only created if this old viewer cannot show the given input * @param input the input object for which to find a structure viewer * @param parent the SWT parent composite under which the new viewer is created * @return a compare viewer which is suitable for the given input object or <code>null</code> */ public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) { return fStructureViewerDescriptor != null ? fStructureViewerDescriptor.createViewer(oldViewer, parent, fCompareConfiguration) : CompareUI.findStructureViewer(oldViewer, input, parent, fCompareConfiguration); } /** * Implements the dynamic viewer switching for content viewers. * The method must return a compare viewer based on the old (or current) viewer * and a new input object. If the old viewer is suitable for showing the new input the old viewer * can be returned. Otherwise a new viewer must be created under the given parent composite or * <code>null</code> can be returned to indicate that no viewer could be found. * <p> * This implementation forwards the request to <code>CompareUI.findContentViewer</code>. * <p> * Subclasses may override to implement a different strategy. * </p> * @param oldViewer a new viewer is only created if this old viewer cannot show the given input * @param input the input object for which to find a structure viewer * @param parent the SWT parent composite under which the new viewer is created * @return a compare viewer which is suitable for the given input object or <code>null</code> */ public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) { Viewer newViewer = fContentViewerDescriptor != null ? fContentViewerDescriptor.createViewer(oldViewer, parent, fCompareConfiguration) : CompareUI.findContentViewer(oldViewer, input, parent, fCompareConfiguration); boolean isNewViewer= newViewer != oldViewer; if (DEBUG) System.out.println("CompareEditorInput.findContentViewer: " + isNewViewer); //$NON-NLS-1$ if (isNewViewer && newViewer instanceof IPropertyChangeNotifier) { final IPropertyChangeNotifier dsp= (IPropertyChangeNotifier) newViewer; dsp.addPropertyChangeListener(fDirtyStateListener); Control c= newViewer.getControl(); c.addDisposeListener( new DisposeListener() { public void widgetDisposed(DisposeEvent e) { dsp.removePropertyChangeListener(fDirtyStateListener); } } ); } return newViewer; } /** * @param vd * the content viewer descriptor * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ public void setContentViewerDescriptor(ViewerDescriptor vd) { this.fContentViewerDescriptor = vd; } /** * @return the content viewer descriptor set for the input * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ public ViewerDescriptor getContentViewerDescriptor() { return this.fContentViewerDescriptor; } /** * @param vd * the structure viewer descriptor * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ public void setStructureViewerDescriptor(ViewerDescriptor vd) { this.fStructureViewerDescriptor = vd; } /** * @return the structure viewer descriptor set for the input * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ public ViewerDescriptor getStructureViewerDescriptor() { return this.fStructureViewerDescriptor; } /** * Returns <code>true</code> if there are unsaved changes in either left or * right side. The value returned is the value of the * <code>DIRTY_STATE</code> property of this input object. * * Returns <code>true</code> if left or right side has unsaved changes * Subclasses don't have to override if the functionality provided by * <code>setDirty</code> is sufficient. * * @return <code>true</code> if there are changes that need to be saved */ public boolean isSaveNeeded() { return isLeftSaveNeeded() || isRightSaveNeeded(); } /** * Returns <code>true</code> if there are unsaved changes for left side. * * @return <code>true</code> if there are changes that need to be saved * @noreference This method is not intended to be referenced by clients. */ protected boolean isLeftSaveNeeded() { return fLeftDirtyViewer != null; } /** * Returns <code>true</code> if there are unsaved changes for right side. * * @return <code>true</code> if there are changes that need to be saved * @noreference This method is not intended to be referenced by clients. */ protected boolean isRightSaveNeeded() { return fRightDirtyViewer != null; } /** * Returns <code>true</code> if there are unsaved changes. * The method should be called by any parts or dialogs * that contain the input. * By default, this method calls {@link #isSaveNeeded()} * but subclasses may extend. * @return <code>true</code> if there are unsaved changes * @since 3.3 */ public boolean isDirty() { return isSaveNeeded(); } /** * Sets the dirty state of this input to the given value and sends out a * <code>PropertyChangeEvent</code> if the new value differs from the old * value. Direct calling this method with parameter dirty equal to * <code>false</code> when there are unsaved changes in viewers, results in * inconsistent state. The dirty state of compare input should be based only * on the information if there are changes in viewers for left or right * side. * * @param dirty * the dirty state for this compare input */ public void setDirty(boolean dirty) { boolean oldDirty = isSaveNeeded(); boolean newDirty = dirty; if (!newDirty) { fLeftDirtyViewer = null; fRightDirtyViewer = null; } else { if (fLeftDirtyViewer == null) fLeftDirtyViewer = (ContentMergeViewer) fContentInputPane .getViewer(); if (fRightDirtyViewer == null) fRightDirtyViewer = (ContentMergeViewer) fContentInputPane .getViewer(); } if (oldDirty != isSaveNeeded()) { Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(isSaveNeeded())); } } /** * Sets the dirty state of left site of this input to the given value and * sends out a <code>PropertyChangeEvent</code> if the new value for whole * input differs from the old value. Direct calling this method with * parameter dirty equal to <code>false</code> when there are unsaved * changes in left viewer, results in inconsistent state. The dirty state of * compare input should be based only on the information if there are * changes in viewers for left side. * * @param dirty * the dirty state for this compare input * @since 3.7 * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ protected void setLeftDirty(boolean dirty) { boolean oldDirty = isSaveNeeded(); boolean newDirty = dirty; if (!newDirty) { fLeftDirtyViewer = null; } else { if (fLeftDirtyViewer == null) fLeftDirtyViewer = (ContentMergeViewer) fContentInputPane .getViewer(); } if (oldDirty != isSaveNeeded()) { Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(isSaveNeeded())); } } /** * Sets the dirty state of right site of this input to the given value and * sends out a <code>PropertyChangeEvent</code> if the new value for whole * input differs from the old value. Direct calling this method with * parameter dirty equal to <code>false</code> when there are unsaved * changes in right viewer, results in inconsistent state. The dirty state * of compare input should be based only on the information if there are * changes in viewers for right side. * * @param dirty * the dirty state for this compare input * @since 3.7 * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ protected void setRightDirty(boolean dirty) { boolean oldDirty = isSaveNeeded(); boolean newDirty = dirty; if (!newDirty) { fRightDirtyViewer = null; } else { if (fRightDirtyViewer == null) fRightDirtyViewer = (ContentMergeViewer) fContentInputPane .getViewer(); } if (oldDirty != isSaveNeeded()) { Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(isSaveNeeded())); } } /** * Method adds or removes viewers that changed left or right side of this * compare input. Any modification of any of the list of viewers may result * in dirty state change. * * @param source * the object that fired <code>PropertyChangeEvent</code> * modifying the dirty state * @param dirty * value that describes if the changes were added or removed */ private void setDirty(Object source, boolean dirty) { Assert.isNotNull(source); boolean oldDirty = isSaveNeeded(); ContentMergeViewer cmv = (ContentMergeViewer) source; if (dirty) { if (cmv.internalIsLeftDirty()) { if (fLeftDirtyViewer == null) { fLeftDirtyViewer = cmv; } } if (cmv.internalIsRightDirty()) { if (fRightDirtyViewer == null) { fRightDirtyViewer = cmv; } } } else { if (!cmv.internalIsLeftDirty()) { fLeftDirtyViewer = null; } if (!cmv.internalIsRightDirty()) { fRightDirtyViewer = null; } } boolean newDirty = isSaveNeeded(); if (DEBUG) { System.out.println("setDirty(" + source + ", " + dirty + "): " + newDirty); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } if (oldDirty != newDirty) { Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(newDirty)); } } /* (non Javadoc) * see IPropertyChangeNotifier.addListener */ public void addPropertyChangeListener(IPropertyChangeListener listener) { if (listener != null) fListenerList.add(listener); } /* (non Javadoc) * see IPropertyChangeNotifier.removeListener */ public void removePropertyChangeListener(IPropertyChangeListener listener) { if (fListenerList != null) { fListenerList.remove(listener); } } /** * Save any unsaved changes. * Empty implementation. * Subclasses must override to save any changes. * * @param pm an <code>IProgressMonitor</code> that the implementation of save may use to show progress * @deprecated Override method saveChanges instead. */ public void save(IProgressMonitor pm) { // empty default implementation } /** * Save any unsaved changes. * Subclasses must override to save any changes. * This implementation tries to flush changes in all viewers by * calling <code>ISavable.save</code> on them. * * @param monitor an <code>IProgressMonitor</code> that the implementation of save may use to show progress * @throws CoreException * @since 2.0 */ public void saveChanges(IProgressMonitor monitor) throws CoreException { flushViewers(monitor); save(monitor); } /** * Flush the viewer contents into the input. * @param monitor a progress monitor * @since 3.3 */ protected void flushViewers(IProgressMonitor monitor) { // flush changes in any dirty viewer flushViewer(fStructureInputPane, monitor); flushViewer(fStructurePane1, monitor); flushViewer(fStructurePane2, monitor); flushViewer(fContentInputPane, monitor); } /** * @param monitor * @noreference This method is not intended to be referenced by clients. */ protected void flushLeftViewers(IProgressMonitor monitor) { // flush changes in left dirty viewer flushViewer(fStructureInputPane, monitor); flushViewer(fStructurePane1, monitor); flushViewer(fStructurePane2, monitor); flushLeftViewer(fContentInputPane, monitor); } /** * @param monitor * @noreference This method is not intended to be referenced by clients. */ protected void flushRightViewers(IProgressMonitor monitor) { // flush changes in right dirty viewer flushViewer(fStructureInputPane, monitor); flushViewer(fStructurePane1, monitor); flushViewer(fStructurePane2, monitor); flushRightViewer(fContentInputPane, monitor); } private static void flushViewer(CompareViewerPane pane, IProgressMonitor pm) { if (pane != null) { IFlushable flushable = (IFlushable)Utilities.getAdapter(pane, IFlushable.class); if (flushable != null) flushable.flush(pm); } } private static void flushLeftViewer(CompareViewerPane pane, IProgressMonitor pm) { if (pane != null) { IFlushable2 flushable = (IFlushable2)Utilities.getAdapter(pane, IFlushable2.class); if (flushable != null) flushable.flushLeft(pm); } } private static void flushRightViewer(CompareViewerPane pane, IProgressMonitor pm) { if (pane != null) { IFlushable2 flushable = (IFlushable2)Utilities.getAdapter(pane, IFlushable2.class); if (flushable != null) flushable.flushRight(pm); } } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener) */ public void addCompareInputChangeListener(ICompareInput input, ICompareInputChangeListener listener) { if (fContainer == null) { input.addCompareInputChangeListener(listener); } else { fContainer.addCompareInputChangeListener(input, listener); } } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener) */ public void removeCompareInputChangeListener(ICompareInput input, ICompareInputChangeListener listener) { if (fContainer == null) { input.removeCompareInputChangeListener(listener); } else { fContainer.removeCompareInputChangeListener(input, listener); } } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider) */ public void registerContextMenu(MenuManager menu, ISelectionProvider selectionProvider) { if (fContainer != null) fContainer.registerContextMenu(menu, selectionProvider); } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) */ public void setStatusMessage(String message) { if (!fContainerProvided) { // Try the action bars directly IActionBars actionBars= getActionBars(); if (actionBars != null) { IStatusLineManager slm= actionBars.getStatusLineManager(); if (slm != null) { slm.setMessage(message); } } } else if (fContainer != null) { fContainer.setStatusMessage(message); } } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#getActionBars() */ public IActionBars getActionBars() { if (fContainer != null) { IActionBars actionBars = fContainer.getActionBars(); if (actionBars == null && !fContainerProvided) { // The old way to find the action bars return Utilities.findActionBars(fComposite); } return actionBars; } return null; } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#getServiceLocator() */ public IServiceLocator getServiceLocator() { IServiceLocator serviceLocator = fContainer.getServiceLocator(); if (serviceLocator == null && !fContainerProvided) { // The old way to find the service locator return Utilities.findSite(fComposite); } return serviceLocator; } /* (non-Javadoc) * @see org.eclipse.compare.ICompareContainer#getWorkbenchPart() */ public IWorkbenchPart getWorkbenchPart() { if (fContainer != null) return fContainer.getWorkbenchPart(); return null; } /* (non-Javadoc) * @see org.eclipse.jface.operation.IRunnableContext#run(boolean, boolean, org.eclipse.jface.operation.IRunnableWithProgress) */ public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException { if (fContainer != null) fContainer.run(fork, cancelable, runnable); } public void runAsynchronously(IRunnableWithProgress runnable) { if (fContainer != null) fContainer.runAsynchronously(runnable); } /** * Set the container of this input to the given container * @param container the container * @since 3.3 */ public void setContainer(ICompareContainer container) { Assert.isNotNull(container); this.fContainer = container; fContainerProvided = true; } /** * Return the container of this input or <code>null</code> if there is no container * set. * @return the container of this input or <code>null</code> * @since 3.3 */ public final ICompareContainer getContainer() { return fContainer; } /** * Fire the given property change event to all listeners * registered with this compare editor input. * @param event the property change event * @since 3.3 */ protected void firePropertyChange(PropertyChangeEvent event) { Utilities.firePropertyChange(fListenerList, event); } /** * Return whether this compare editor input can be run as a job. * By default, <code>false</code> is returned since traditionally inputs * were prepared in the foreground (i.e the UI was blocked when the * {@link #run(IProgressMonitor)} method (and indirectly the * {@link #prepareInput(IProgressMonitor)} method) was invoked. Subclasses * may override. * @return whether this compare editor input can be run in the background * @since 3.3 */ public boolean canRunAsJob() { return false; } /** * Return whether this input belongs to the given family * when it is run as a job. * @see #canRunAsJob() * @see Job#belongsTo(Object) * @param family the job family * @return whether this input belongs to the given family * @since 3.3 */ public boolean belongsTo(Object family) { return family == this; } /** * Return whether this input is intended to be used to select * a particular edition of an element in a dialog. The result * of this method is only consider if neither sides of the * input are editable. By default, <code>false</code> is returned. * @return whether this input is intended to be used to select * a particular edition of an element in a dialog * @see #getOKButtonLabel() * @see #okPressed() * @see #getSelectedEdition() * @since 3.3 */ public boolean isEditionSelectionDialog() { return false; } /** * Return the label to be used for the <code>OK</code> * button when this input is displayed in a dialog. * By default, different labels are used depending on * whether the input is editable or is for edition selection * (see {@link #isEditionSelectionDialog()}. * @return the label to be used for the <code>OK</code> * button when this input is displayed in a dialog * @since 3.3 */ public String getOKButtonLabel() { if (isEditable()) return CompareMessages.CompareDialog_commit_button; if (isEditionSelectionDialog()) return CompareMessages.CompareEditorInput_0; return IDialogConstants.OK_LABEL; } /** * Return the label used for the <code>CANCEL</code> * button when this input is shown in a compare dialog * using {@link CompareUI#openCompareDialog(CompareEditorInput)}. * @return the label used for the <code>CANCEL</code> button * @since 3.3 */ public String getCancelButtonLabel() { return IDialogConstants.CANCEL_LABEL; } private boolean isEditable() { return getCompareConfiguration().isLeftEditable() || getCompareConfiguration().isRightEditable(); } /** * The <code>OK</code> button was pressed in a dialog. If one or both of * the sides of the input is editable then any changes will be saved. If the * input is for edition selection (see {@link #isEditionSelectionDialog()}), * it is up to subclasses to override this method in order to perform the * appropriate operation on the selected edition. * * @return whether the dialog should be closed or not. * @since 3.3 */ public boolean okPressed() { if (isEditable()) { if (!saveChanges()) return false; } return true; } /** * The <code>CANCEL</code> button was pressed in a dialog. * By default, nothing is done. Subclasses may override. * @since 3.3 */ public void cancelPressed() { // Do nothing } private boolean saveChanges() { try { PlatformUI.getWorkbench().getProgressService().run(true, true, new IRunnableWithProgress() { public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { saveChanges(monitor); } catch (CoreException e) { throw new InvocationTargetException(e); } } }); return true; } catch (InterruptedException x) { // Ignore } catch (OperationCanceledException x) { // Ignore } catch (InvocationTargetException x) { ErrorDialog.openError(fComposite.getShell(), CompareMessages.CompareDialog_error_title, null, new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, NLS.bind(CompareMessages.CompareDialog_error_message, x.getTargetException().getMessage()), x.getTargetException())); } return false; } /** * Return the selected edition or <code>null</code> if no edition is selected. * The result of this method should only be considered if {@link #isEditionSelectionDialog()} * returns <code>true</code>. * @return the selected edition or <code>null</code> * @since 3.3 */ public Object getSelectedEdition() { if (fStructureInputPane != null) { ISelection selection = fStructureInputPane.getSelection(); if (selection instanceof IStructuredSelection) { IStructuredSelection ss = (IStructuredSelection) selection; if (!ss.isEmpty()) return ss.getFirstElement(); } } return null; } /** * Set the help context id for this input. * @param helpContextId the help context id. * @since 3.3 */ public void setHelpContextId(String helpContextId) { this.fHelpContextId = helpContextId; } }