/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
package org.eclipse.compare; | |
import java.lang.reflect.InvocationTargetException; | |
import java.util.List; | |
import java.util.ArrayList; | |
import java.util.ResourceBundle; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.events.*; | |
import org.eclipse.swt.graphics.Point; | |
import org.eclipse.swt.widgets.*; | |
import org.eclipse.swt.graphics.Image; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.runtime.*; | |
import org.eclipse.ui.IPersistableElement; | |
import org.eclipse.ui.IEditorInput; | |
import org.eclipse.jface.util.*; | |
import org.eclipse.jface.action.Separator; | |
import org.eclipse.jface.action.ToolBarManager; | |
import org.eclipse.swt.custom.BusyIndicator; | |
import org.eclipse.jface.viewers.*; | |
import org.eclipse.jface.operation.IRunnableWithProgress; | |
import org.eclipse.jface.preference.IPreferenceStore; | |
import org.eclipse.jface.resource.ImageDescriptor; | |
import org.eclipse.compare.contentmergeviewer.ContentMergeViewer; | |
import org.eclipse.compare.internal.*; | |
import org.eclipse.compare.structuremergeviewer.*; | |
/** | |
* A compare operation which can present its results in a special editor. | |
* Running the compare operation and presentating the results in a compare editor | |
* are combined in one interface 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>ICompareEditorInput</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>ICompareEditorInput</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>ICompareEditorInput</code> with title and title image initialized by the | |
* corresponding methods of the <code>ICompareEditorInput</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 horizontally 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 Catchup/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. | |
* | |
* @see CompareUI | |
* @see CompareEditorInput | |
*/ | |
public abstract class CompareEditorInput implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress { | |
private static final boolean DEBUG= false; | |
/** | |
* The name of the "dirty" property. | |
*/ | |
public static final String DIRTY_STATE= "DIRTY_STATE"; //$NON-NLS-1$ | |
private static final String COMPARE_EDITOR_IMAGE_NAME= "cview16/compare_view.gif"; //$NON-NLS-1$ | |
private static Image fgTitleImage; | |
private Splitter fComposite; | |
private CompareConfiguration fCompareConfiguration; | |
private CompareViewerSwitchingPane fStructureInputPane; | |
private CompareViewerSwitchingPane fStructurePane1; | |
private CompareViewerSwitchingPane fStructurePane2; | |
private CompareViewerSwitchingPane fContentInputPane; | |
private CompareViewerSwitchingPane fFocusPane; | |
private String fMessage; | |
private ISelection fSelection2; | |
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; | |
private IgnoreWhiteSpaceAction fIgnoreWhitespace; | |
private ShowPseudoConflicts fShowPseudoConflicts; | |
boolean fStructureCompareOnSingleClick= true; | |
/** | |
* 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); | |
ResourceBundle bundle= CompareUIPlugin.getResourceBundle(); | |
fIgnoreWhitespace= new IgnoreWhiteSpaceAction(bundle, configuration); | |
fShowPseudoConflicts= new ShowPseudoConflicts(bundle, configuration); | |
fDirtyStateListener= new IPropertyChangeListener() { | |
public void propertyChange(PropertyChangeEvent e) { | |
String propertyName= e.getProperty(); | |
if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) | |
setDirty(e.getSource(), Utilities.getValue(e, false)); | |
} | |
}; | |
IPreferenceStore ps= configuration.getPreferenceStore(); | |
if (ps != null) | |
fStructureCompareOnSingleClick= ps.getBoolean(ComparePreferencePage.OPEN_STRUCTURE_COMPARE); | |
} | |
private boolean structureCompareOnSingleClick() { | |
return fStructureCompareOnSingleClick; | |
} | |
/* (non Javadoc) | |
* see IAdaptable.getAdapter | |
*/ | |
public Object getAdapter(Class adapter) { | |
if (CompareNavigator.class.equals(adapter)) { | |
if (fNavigator == null) | |
fNavigator= new CompareNavigator( | |
new CompareViewerSwitchingPane[] { | |
fStructureInputPane, | |
fStructurePane1, | |
fStructurePane2, | |
fContentInputPane | |
} | |
); | |
return fNavigator; | |
} | |
return null; | |
} | |
/* (non Javadoc) | |
* see IEditorInput.getImageDescriptor | |
*/ | |
public ImageDescriptor getImageDescriptor() { | |
return null; | |
} | |
/* (non Javadoc) | |
* see IEditorInput.getToolTipText | |
*/ | |
public String getToolTipText() { | |
return fTitle; | |
} | |
/* (non Javadoc) | |
* see IEditorInput.getName | |
*/ | |
public String getName() { | |
return fTitle; | |
} | |
/** | |
* 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() { | |
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) { | |
fTitle= 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) { | |
toolBarManager.add(new Separator()); | |
toolBarManager.add(fIgnoreWhitespace); | |
toolBarManager.add(fShowPseudoConflicts); | |
} | |
/** | |
* 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>. | |
* | |
* @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); | |
final Splitter h= new Splitter(fComposite, SWT.HORIZONTAL); | |
fStructureInputPane= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) { | |
protected Viewer getViewer(Viewer oldViewer, Object input) { | |
if (input instanceof DiffNode) { | |
DiffNode dn= (DiffNode) input; | |
if (dn.hasChildren()) | |
return createDiffViewer(this); | |
} | |
if (input instanceof ICompareInput) | |
return findStructureViewer(oldViewer, (ICompareInput)input, this); | |
return null; | |
} | |
}; | |
fFocusPane= fStructureInputPane; | |
fStructurePane1= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) { | |
protected Viewer getViewer(Viewer oldViewer, Object input) { | |
if (input instanceof ICompareInput) | |
return findStructureViewer(oldViewer, (ICompareInput)input, this); | |
return null; | |
} | |
}; | |
h.setVisible(fStructurePane1, false); | |
fStructurePane2= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) { | |
protected Viewer getViewer(Viewer oldViewer, Object input) { | |
if (input instanceof ICompareInput) | |
return findStructureViewer(oldViewer, (ICompareInput)input, this); | |
return null; | |
} | |
}; | |
h.setVisible(fStructurePane2, false); | |
fContentInputPane= new CompareViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT) { | |
protected Viewer getViewer(Viewer oldViewer, Object input) { | |
if (input instanceof ICompareInput) | |
return findContentViewer(oldViewer, (ICompareInput)input, this); | |
return null; | |
} | |
}; | |
fComposite.setVisible(h, false); | |
fComposite.setVisible(fContentInputPane, true); | |
fComposite.setWeights(new int[] { 30, 70 }); | |
fComposite.layout(); | |
// setup the wiring for top left pane | |
fStructureInputPane.addSelectionChangedListener( | |
new ISelectionChangedListener() { | |
public void selectionChanged(SelectionChangedEvent e) { | |
feed1(e.getSelection()); | |
} | |
} | |
); | |
if (!structureCompareOnSingleClick()) { | |
fStructureInputPane.addDoubleClickListener( | |
new IDoubleClickListener() { | |
public void doubleClick(DoubleClickEvent e) { | |
feedDefault1(e.getSelection()); | |
} | |
} | |
); | |
} | |
// setup the wiring for second pane | |
fStructurePane1.addSelectionChangedListener( | |
new ISelectionChangedListener() { | |
public void selectionChanged(SelectionChangedEvent e) { | |
feed2(e.getSelection()); | |
} | |
} | |
); | |
// setup the wiring for third pane | |
fStructurePane2.addSelectionChangedListener( | |
new ISelectionChangedListener() { | |
public void selectionChanged(SelectionChangedEvent e) { | |
feed3(e.getSelection()); | |
} | |
} | |
); | |
// now deal with activation | |
Listener activationListener= new Listener() { | |
public void handleEvent(Event event) { | |
if (event.widget instanceof CompareViewerSwitchingPane) { | |
fFocusPane= (CompareViewerSwitchingPane) event.widget; | |
} | |
} | |
}; | |
fStructureInputPane.addListener(SWT.Activate, activationListener); | |
fStructurePane1.addListener(SWT.Activate, activationListener); | |
fStructurePane2.addListener(SWT.Activate, activationListener); | |
fContentInputPane.addListener(SWT.Activate, activationListener); | |
if (fInput instanceof ICompareInput) { | |
ICompareInput input2= (ICompareInput) fInput; | |
fStructureInputPane.setInput(input2); | |
feed1(fStructureInputPane.getSelection()); | |
} | |
fComposite.setData("Nav", //$NON-NLS-1$ | |
new CompareViewerSwitchingPane[] { | |
fStructureInputPane, | |
fStructurePane1, | |
fStructurePane2, | |
fContentInputPane | |
} | |
); | |
return fComposite; | |
} | |
private void feed1(final ISelection selection) { | |
BusyIndicator.showWhile(fComposite.getDisplay(), | |
new Runnable() { | |
public void run() { | |
if (selection == null || selection.isEmpty()) { | |
Object input= fStructureInputPane.getInput(); | |
fContentInputPane.setInput(input); | |
fStructurePane2.setInput(null); // clear downstream pane | |
fStructurePane1.setInput(null); | |
} else { | |
Object input= getElement(selection); | |
fContentInputPane.setInput(input); | |
if (structureCompareOnSingleClick()) | |
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(); | |
fContentInputPane.setInput(input); | |
fStructurePane2.setInput(null); | |
} else { | |
Object input= getElement(selection); | |
fContentInputPane.setInput(input); | |
fStructurePane2.setInput(input); | |
} | |
} | |
} | |
); | |
} | |
private void feed3(final ISelection selection) { | |
BusyIndicator.showWhile(fComposite.getDisplay(), | |
new Runnable() { | |
public void run() { | |
if (selection.isEmpty()) | |
fContentInputPane.setInput(fStructurePane2.getInput()); | |
else | |
fContentInputPane.setInput(getElement(selection)); | |
} | |
} | |
); | |
} | |
/** | |
* 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). | |
* <p> | |
* 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. | |
* </p> | |
*/ | |
public void setFocus() { | |
if (fFocusPane != null) { | |
Viewer v= fFocusPane.getViewer(); | |
if (v != null) { | |
Control c= v.getControl(); | |
if (c != null) | |
c.setFocus(); | |
} | |
} else if (fComposite != null) | |
fComposite.setFocus(); | |
} | |
/** | |
* 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> | |
* | |
* @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 CompareUIPlugin.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> | |
* | |
* @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= CompareUIPlugin.findContentViewer(oldViewer, input, parent, fCompareConfiguration); | |
boolean isNewViewer= newViewer != oldViewer; | |
if (DEBUG) System.out.println("CompareEditorInput.findContentViewer: " + isNewViewer); | |
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; | |
} | |
/** | |
* 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 <doce>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; | |
} | |
/** | |
* 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 confirmSave= true; | |
Object o= fCompareConfiguration.getProperty(CompareEditor.CONFIRM_SAVE_PROPERTY); | |
if (o instanceof Boolean) | |
confirmSave= ((Boolean)o).booleanValue(); | |
if (!confirmSave) { | |
fDirty= dirty; | |
if (!fDirty) | |
fDirtyViewers.clear(); | |
} | |
} | |
private void setDirty(Object source, boolean dirty) { | |
Assert.isNotNull(source); | |
boolean oldDirty= fDirtyViewers.size() > 0; | |
if (dirty) | |
fDirtyViewers.add(source); | |
else | |
fDirtyViewers.remove(source); | |
boolean newDirty= fDirtyViewers.size() > 0; | |
if (DEBUG) System.out.println("setDirty("+source+", "+dirty+"): " + newDirty); | |
if (oldDirty != newDirty) | |
Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, new Boolean(oldDirty), new Boolean(newDirty)); | |
} | |
/* (non Javadoc) | |
* see IPropertyChangeNotifier.addListener | |
*/ | |
public void addPropertyChangeListener(IPropertyChangeListener listener) { | |
fListenerList.add(listener); | |
} | |
/* (non Javadoc) | |
* see IPropertyChangeNotifier.removeListener | |
*/ | |
public void removePropertyChangeListener(IPropertyChangeListener listener) { | |
fListenerList.remove(listener); | |
} | |
/** | |
* Save any unsaved changes. | |
* Empty implementation. | |
* Subclasses must override to save any changes. | |
* | |
* @param progressMonitor an <code>IProgressMonitor</code> that the implementation of save may use to show progress | |
* @deprecated Override method saveChanges instead. | |
*/ | |
public void save(IProgressMonitor pm) { | |
} | |
/** | |
* 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 progressMonitor an <code>IProgressMonitor</code> that the implementation of save may use to show progress | |
*/ | |
public void saveChanges(IProgressMonitor pm) throws CoreException { | |
// flush changes in any dirty viewer | |
flushViewer(fStructureInputPane, pm); | |
flushViewer(fStructurePane1, pm); | |
flushViewer(fStructurePane2, pm); | |
flushViewer(fContentInputPane, pm); | |
save(pm); | |
} | |
private static void flushViewer(CompareViewerSwitchingPane pane, IProgressMonitor pm) throws CoreException { | |
if (pane != null) { | |
Viewer v= pane.getViewer(); | |
if (v instanceof ISavable) | |
((ISavable)v).save(pm); | |
} | |
} | |
} | |