/******************************************************************************* * Copyright (c) 2000, 2010 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.ArrayList; import java.util.ResourceBundle; 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.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 boolean fDirty= false; private ArrayList fDirtyViewers= new ArrayList(); 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. The value returned is the value of * the <code>DIRTY_STATE</code> property of this input object. * * Returns <code>true</code> if this input has unsaved changes, that is if * <code>setDirty(true)</code> has been called. 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 fDirty || fDirtyViewers.size() > 0; } /** * 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. * * @param dirty the dirty state for this compare input */ public void setDirty(boolean dirty) { boolean oldDirty= fDirty || fDirtyViewers.size() > 0; fDirty= dirty; if (!fDirty) fDirtyViewers.clear(); if (oldDirty != dirty) Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty), Boolean.valueOf(dirty)); } private void setDirty(Object source, boolean dirty) { Assert.isNotNull(source); boolean oldDirty= fDirty || fDirtyViewers.size() > 0; if (dirty) fDirtyViewers.add(source); else fDirtyViewers.remove(source); boolean newDirty= fDirty || fDirtyViewers.size() > 0; 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); } 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); } } /* (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; } }