diff options
Diffstat (limited to 'bundles/org.eclipse.compare/compare/org')
170 files changed, 39698 insertions, 0 deletions
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/BufferedContent.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/BufferedContent.java new file mode 100644 index 000000000..307cf578b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/BufferedContent.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.eclipse.compare.internal.ContentChangeNotifier; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.runtime.CoreException; + +/** + * Abstract implementation for a buffered <code>IStreamContentAccessor</code>. + * <p> + * Subclasses must implement the <code>createStream</code> method + * to connect the buffered content with a streamable source (e.g., a file). + * <p> + * As long as the contents of <code>BufferedContent</code> is only retrieved as an input stream + * (by means of <code>getContents</code>) and the <code>BufferedContent</code> is not modified (with + * <code>setContent</code>) no buffering takes place. + * Buffering starts when either method <code>getContent</code> or <code>setContent</code> is called. + * + * @see IContentChangeNotifier + * @see IStreamContentAccessor + */ +public abstract class BufferedContent implements IContentChangeNotifier, IStreamContentAccessor { + + byte[] fContent; + private ContentChangeNotifier fChangeNotifier; + + /** + * Creates a buffered stream content accessor. + */ + protected BufferedContent() { + // empty implementation + } + + /* (non-Javadoc) + * see IStreamContentAccessor.getContents + */ + public InputStream getContents() throws CoreException { + if (fContent != null) + return new ByteArrayInputStream(fContent); + return createStream(); + } + + /** + * Creates and returns a stream for reading the contents. + * <p> + * Subclasses must implement this method. + * </p> + * + * @return the stream from which the content is read + * @exception CoreException if the contents could not be accessed + */ + protected abstract InputStream createStream() throws CoreException; + + /** + * Sets the contents. Registered content change listeners are notified. + * + * @param contents the new contents + */ + public void setContent(byte[] contents) { + fContent= contents; + fireContentChanged(); + } + + /** + * Returns the contents as an array of bytes. + * + * @return the contents as an array of bytes, or <code>null</code> if + * the contents could not be accessed + */ + public byte[] getContent() { + if (fContent == null) { + try { + InputStream is= createStream(); + fContent= Utilities.readBytes(is); + } catch(CoreException ex) { + // NeedWork + } + } + return fContent; + } + + /** + * Discards the buffered content. + */ + public void discardBuffer() { + fContent= null; + } + + /* (non-Javadoc) + * see IContentChangeNotifier.addChangeListener + */ + public void addContentChangeListener(IContentChangeListener listener) { + if (fChangeNotifier == null) + fChangeNotifier= new ContentChangeNotifier(this); + fChangeNotifier.addContentChangeListener(listener); + } + + /* (non-Javadoc) + * see IContentChangeNotifier.removeChangeListener + */ + public void removeContentChangeListener(IContentChangeListener listener) { + if (fChangeNotifier != null) { + fChangeNotifier.removeContentChangeListener(listener); + if (fChangeNotifier.isEmpty()) + fChangeNotifier= null; + } + } + + /** + * Notifies all registered <code>IContentChangeListener</code>s of a content change. + */ + protected void fireContentChanged() { + if (fChangeNotifier == null || fChangeNotifier.isEmpty()) { + return; + } + fChangeNotifier.fireContentChanged(); + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java new file mode 100644 index 000000000..23db3dd30 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java @@ -0,0 +1,710 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.compare.internal.CompareContainer; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.DiffImageDescriptor; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.rangedifferencer.RangeDifference; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.LabelProviderChangedEvent; +import org.eclipse.swt.graphics.Image; + +/** + * A <code>CompareConfiguration</code> object + * controls various UI aspects of compare/merge viewers like + * title labels and images, or whether a side of a merge viewer is editable. + * In addition to these fixed properties <code>ICompareConfiguration</code> provides + * API for an open ended set of properties. Different viewers which share the same + * configuration can communicate via this mechanism. E.g. if a compare editor + * has a button for controlling whether compare viewers ignore white space, + * the button would trigger a change of the boolean <code>IGNORE_WHITESPACE</code> property + * and all interested viewers would receive notification. + * <p> + * Suitable default labels are provided (without images); both the left and right sides + * are editable. + * </p> + * <p> + * Clients may use this class as is, or subclass to add new state and behavior. + * </p> + */ +public class CompareConfiguration { + + /** + * Name of the ignore whitespace property (value <code>"IGNORE_WHITESPACE"</code>). + */ + public static final String IGNORE_WHITESPACE= "IGNORE_WHITESPACE"; //$NON-NLS-1$ + /** + * Name of the show pseudo conflicts property (value <code>"SHOW_PSEUDO_CONFLICTS"</code>). + */ + public static final String SHOW_PSEUDO_CONFLICTS= "SHOW_PSEUDO_CONFLICTS"; //$NON-NLS-1$ + /** + * Name of the use outline view property (value <code>"USE_OUTLINE_VIEW"</code>). + * @since 3.0 + */ + public static final String USE_OUTLINE_VIEW= "USE_OUTLINE_VIEW"; //$NON-NLS-1$ + + private static ImageDescriptor[] fgImages= new ImageDescriptor[16]; + private static boolean fLeftIsLocal= true; + + static { + if (fLeftIsLocal) { + fgImages[Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/del_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.LEFT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/r_inadd_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.RIGHT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/r_outadd_ov.gif"); //$NON-NLS-1$ + + fgImages[Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/add_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.LEFT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/r_indel_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.RIGHT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/r_outdel_ov.gif"); //$NON-NLS-1$ + + fgImages[Differencer.LEFT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/r_inchg_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.RIGHT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/r_outchg_ov.gif"); //$NON-NLS-1$ + } else { + fgImages[Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/add_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.LEFT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/inadd_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.RIGHT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/outadd_ov.gif"); //$NON-NLS-1$ + + fgImages[Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/del_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.LEFT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/indel_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.RIGHT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/outdel_ov.gif"); //$NON-NLS-1$ + + fgImages[Differencer.LEFT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/inchg_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.RIGHT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/outchg_ov.gif"); //$NON-NLS-1$ + } + + fgImages[Differencer.CONFLICTING + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/confadd_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.CONFLICTING + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/confdel_ov.gif"); //$NON-NLS-1$ + fgImages[Differencer.CONFLICTING + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/confchg_ov.gif"); //$NON-NLS-1$ + } + + private IPreferenceStore fPreferenceStore; + private ListenerList fListeners= new ListenerList(); + private HashMap fProperties= new HashMap(); + private boolean fLeftEditable= true; + private boolean fRightEditable= true; + private String fAncestorLabel; + private String fLeftLabel; + private String fRightLabel; + private Image fAncestorImage; + private Image fRightImage; + private Image fLeftImage; + private ICompareContainer fContainer; + private DefaultLabelProvider labelProvider = new DefaultLabelProvider(); + private boolean fDisposed; + private LocalResourceManager fResourceManager; + private Set fIgnoredChanges = new HashSet(6); + + private class DefaultLabelProvider extends LabelProvider implements ICompareInputLabelProvider, ILabelProviderListener { + private Map labelProviders = new HashMap(); + private ICompareInputLabelProvider defaultLabelProvider; + public Image getAncestorImage(Object input) { + ICompareInputLabelProvider provider = getLabelProvider(input); + if (provider != null) { + Image image = provider.getAncestorImage(input); + if (image != null) + return image; + } + return fAncestorImage; + } + public String getAncestorLabel(Object input) { + ICompareInputLabelProvider provider = getLabelProvider(input); + if (provider != null) { + String label = provider.getAncestorLabel(input); + if (label != null) + return label; + } + return fAncestorLabel; + } + public Image getLeftImage(Object input) { + ICompareInputLabelProvider provider = getLabelProvider(input); + if (provider != null) { + Image image = provider.getLeftImage(input); + if (image != null) + return image; + } + return fLeftImage; + } + public String getLeftLabel(Object input) { + ICompareInputLabelProvider provider = getLabelProvider(input); + if (provider != null) { + String label = provider.getLeftLabel(input); + if (label != null) + return label; + } + return fLeftLabel; + } + public Image getRightImage(Object input) { + ICompareInputLabelProvider provider = getLabelProvider(input); + if (provider != null) { + Image image = provider.getRightImage(input); + if (image != null) + return image; + } + return fRightImage; + } + public String getRightLabel(Object input) { + ICompareInputLabelProvider provider = getLabelProvider(input); + if (provider != null) { + String label = provider.getRightLabel(input); + if (label != null) + return label; + } + return fRightLabel; + } + public ICompareInputLabelProvider getLabelProvider(Object input) { + ICompareInputLabelProvider lp = (ICompareInputLabelProvider)labelProviders.get(input); + if (lp == null) + return defaultLabelProvider; + return lp; + } + public void setLabelProvider(ICompareInput input, ICompareInputLabelProvider labelProvider) { + ICompareInputLabelProvider old = (ICompareInputLabelProvider)labelProviders.get(input); + if (old != null) + old.removeListener(this); + labelProviders.put(input, labelProvider); + labelProvider.addListener(this); + } + public Image getImage(Object element) { + ICompareInputLabelProvider provider = getLabelProvider(element); + if (provider != null) { + Image image = provider.getImage(element); + if (image != null) + return image; + } + if (element instanceof ICompareInput) { + ICompareInput ci = (ICompareInput) element; + Image image = ci.getImage(); + if (image != null) + return image; + } + return super.getImage(element); + } + public String getText(Object element) { + ICompareInputLabelProvider provider = getLabelProvider(element); + if (provider != null) { + String label = provider.getText(element); + if (label != null) + return label; + } + if (element instanceof ICompareInput) { + ICompareInput ci = (ICompareInput) element; + String label = ci.getName(); + if (label != null) + return label; + } + return super.getText(element); + } + + public void dispose() { + for (Iterator iterator = labelProviders.values().iterator(); iterator.hasNext();) { + ICompareInputLabelProvider lp = (ICompareInputLabelProvider) iterator.next(); + lp.removeListener(this); + } + if (defaultLabelProvider != null) + defaultLabelProvider.removeListener(this); + defaultLabelProvider = null; + labelProviders.clear(); + } + + public void labelProviderChanged(LabelProviderChangedEvent event) { + fireLabelProviderChanged(new LabelProviderChangedEvent(this, event.getElements())); + } + public void setDefaultLabelProvider(ICompareInputLabelProvider labelProvider) { + if (defaultLabelProvider != null) + defaultLabelProvider.removeListener(this); + defaultLabelProvider = labelProvider; + if (defaultLabelProvider != null) + defaultLabelProvider.addListener(this); + } + } + + /** + * Creates a new configuration with editable left and right sides, + * suitable default labels, and no images. + * The given preference store is used to connect this configuration + * with the Compare preference page properties <code>ComparePreferencePage.INITIALLY_SHOW_ANCESTOR_PANE</code>, + * and <code>CompareConfiguration.IGNORE_WHITESPACE</code>. + * + * @param prefStore the preference store which this configuration holds onto. + * @since 2.0 + */ + public CompareConfiguration(IPreferenceStore prefStore) { + + setProperty("LEFT_IS_LOCAL", Boolean.valueOf(fLeftIsLocal)); //$NON-NLS-1$ + + fPreferenceStore= prefStore; + if (fPreferenceStore != null) { + boolean b= fPreferenceStore.getBoolean(ComparePreferencePage.INITIALLY_SHOW_ANCESTOR_PANE); + setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, new Boolean(b)); + + b= fPreferenceStore.getBoolean(ComparePreferencePage.IGNORE_WHITESPACE); + setProperty(CompareConfiguration.IGNORE_WHITESPACE, new Boolean(b)); + } + } + + /** + * Creates a new configuration with editable left and right sides, + * suitable default labels, and no images. + * This configuration uses the preference store of the Compare plug-in + * (<code>CompareUIPlugin.getDefault().getPreferenceStore()</code>). + */ + public CompareConfiguration() { + this(CompareUIPlugin.getDefault().getPreferenceStore()); + } + + /** + * Returns the preference store of this configuration. + * @return the preference store of this configuration. + * @since 2.0 + */ + public IPreferenceStore getPreferenceStore() { + return fPreferenceStore; + } + + /** + * Returns an image showing the specified change kind. + * The different kind of changes are defined in the <code>Differencer</code>. + * Newly created images are remembered by this class and + * disposed when the <code>dispose</code> method is called. + * + * @param kind the kind of change as defined in <code>Differencer</code>. + * @return an modification of the base image reflecting the kind of change. + * @see org.eclipse.compare.structuremergeviewer.Differencer + * @since 2.0 + */ + public Image getImage(int kind) { + if (fDisposed) + return null; + ImageDescriptor id= fgImages[kind & 15]; + ResourceManager rm = getResourceManager(); + return rm.createImage(id); + } + + private synchronized ResourceManager getResourceManager() { + if (fResourceManager == null) { + fResourceManager = new LocalResourceManager(JFaceResources.getResources()); + } + return fResourceManager; + } + + /** + * Returns an image showing the specified change kind applied to a + * given base image. The different kind of changes are defined in the <code>Differencer</code>. + * Typically an implementation would build a composite image + * from the given base image and an image representing the change kind. + * Newly created images are remembered by this class and + * disposed when the <code>dispose</code> method is called. + * + * @param base the image which is modified to reflect the kind of change + * @param kind the kind of change as defined in <code>Differencer</code>. + * @return an modification of the base image reflecting the kind of change. + * @see org.eclipse.compare.structuremergeviewer.Differencer + */ + public Image getImage(Image base, int kind) { + if (fDisposed) + return null; + kind &= 15; + ImageDescriptor id = new DiffImageDescriptor(base, fgImages[kind], ICompareUIConstants.COMPARE_IMAGE_WIDTH, !fLeftIsLocal); + ResourceManager rm = getResourceManager(); + return rm.createImage(id); + } + + /** + * Dispose of this compare configuration. + * This method is called if the compare configuration is no longer used. + * An implementation must dispose of all resources. + */ + public void dispose() { + fDisposed = true; + if (fResourceManager != null) { + fResourceManager.dispose(); + } + labelProvider.dispose(); + } + + /** + * Fires a <code>PropertyChangeEvent</code> to registered listeners. + * + * @param propertyName the name of the property that has changed + * @param oldValue the property's old value + * @param newValue the property's new value + */ + private void fireChange(String propertyName, Object oldValue, Object newValue) { + PropertyChangeEvent event= null; + Object[] listeners= fListeners.getListeners(); + if (listeners != null) { + for (int i= 0; i < listeners.length; i++) { + IPropertyChangeListener l= (IPropertyChangeListener) listeners[i]; + if (event == null) + event= new PropertyChangeEvent(this, propertyName, oldValue, newValue); + l.propertyChange(event); + } + } + } + + /* (non javadoc) + * see IPropertyChangeNotifier.addListener + */ + public void addPropertyChangeListener(IPropertyChangeListener listener) { + fListeners.add(listener); + } + + /* (non javadoc) + * see IPropertyChangeNotifier.removeListener + */ + public void removePropertyChangeListener(IPropertyChangeListener listener) { + fListeners.remove(listener); + } + + /** + * Sets the property with the given name. + * If the new value differs from the old a <code>PropertyChangeEvent</code> + * is sent to registered listeners. + * + * @param key the name of the property to set + * @param newValue the new value of the property + */ + public void setProperty(String key, Object newValue) { + Object oldValue= fProperties.get(key); + fProperties.put(key, newValue); + if (oldValue == null || !oldValue.equals(newValue)) + fireChange(key, oldValue, newValue); + } + + /** + * Returns the property with the given name, or <code>null</code> + * if no such property exists. + * + * @param key the name of the property to retrieve + * @return the property with the given name, or <code>null</code> if not found + */ + public Object getProperty(String key) { + return fProperties.get(key); + } + + //---- ancestor + + /** + * Sets the label to use for the ancestor of compare/merge viewers. + * This label will be used if the element for which a label + * is requested does not have an ancestor or the element does not have + * a registered label provider or the label provider returns <code>null</code> + * as the label. + * + * @param label the new label for the ancestor of compare/merge viewers + */ + public void setAncestorLabel(String label) { + fAncestorLabel= label; + } + + /** + * Returns the label for the ancestor side of compare/merge viewers. + * This label is typically shown in the title of the ancestor area in a compare viewer. + * + * @param element the input object of a compare/merge viewer or <code>null</code> + * @return the label for the ancestor side or <code>null</code> + */ + public String getAncestorLabel(Object element) { + return labelProvider.getAncestorLabel(element); + } + + /** + * Sets the image to use for the ancestor of compare/merge viewers. + * The CompareConfiguration does not automatically dispose the old image. + * This image will be used if the element for which a image + * is requested does not have an ancestor or the element does not have + * a registered label provider or the label provider returns <code>null</code> + * as the image. + * + * @param image the new image for the ancestor of compare/merge viewers + */ + public void setAncestorImage(Image image) { + fAncestorImage= image; + } + + /** + * Returns the image for the ancestor side of compare/merge viewers. + * This image is typically shown in the title of the ancestor area in a compare viewer. + * + * @param element the input object of a compare/merge viewer or <code>null</code> + * @return the image for the ancestor side or <code>null</code> + */ + public Image getAncestorImage(Object element) { + return labelProvider.getAncestorImage(element); + } + + //---- left side + + /** + * Controls whether the left side of a merge viewer is editable. + * + * @param editable if the value is <code>true</code> left side is editable + */ + public void setLeftEditable(boolean editable) { + fLeftEditable= editable; + } + + /** + * Returns whether the left hand side of a merge viewer is editable. + * + * @return <code>true</code> if the left hand side is editable + */ + public boolean isLeftEditable() { + return fLeftEditable; + } + + /** + * Sets the label to use for the left side of compare/merge viewers. + * This label will be used if the element for which a label + * is requested does not have a left contributor or the element does not have + * a registered label provider or the label provider returns <code>null</code> + * as the label. + * + * @param label the new label for the left side of compare/merge viewers + */ + public void setLeftLabel(String label) { + fLeftLabel= label; + } + + /** + * Returns the label for the left hand side of compare/merge viewers. + * This label is typically shown in the title of the left side of a compare viewer. + * + * @param element the input object of a compare/merge viewer or <code>null</code> + * @return the label for the left hand side or <code>null</code> + */ + public String getLeftLabel(Object element) { + return labelProvider.getLeftLabel(element); + } + + /** + * Sets the image to use for the left side of compare/merge viewers. + * The compare configuration does not automatically dispose the old image. + * This image will be used if the element for which a image + * is requested does not have an left contributor or the element does not have + * a registered label provider or the label provider returns <code>null</code> + * as the image. + * + * @param image the new image for the left side of compare/merge viewers + */ + public void setLeftImage(Image image) { + fLeftImage= image; + } + + /** + * Returns the image for the left hand side of compare/merge viewers. + * This image is typically shown in the title of the left side of a compare viewer. + * + * @param element the input object of a compare/merge viewer or <code>null</code> + * @return the image for the left hand side or <code>null</code> + */ + public Image getLeftImage(Object element) { + return labelProvider.getLeftImage(element); + } + + //---- right side + + /** + * Controls whether the right side of a merge viewer is editable. + * + * @param editable if the value is <code>true</code> right side is editable + */ + public void setRightEditable(boolean editable) { + fRightEditable= editable; + } + + /** + * Returns whether the right hand side of a merge viewer is editable. + * + * @return <code>true</code> if the right hand side is editable + */ + public boolean isRightEditable() { + return fRightEditable; + } + + /** + * Sets the label to use for the right side of compare/merge viewers. + * This label will be used if the element for which a label + * is requested does not have an right contributor or the element does not have + * a registered label provider or the label provider returns <code>null</code> + * as the label. + * + * @param label the new label for the right side of compare/merge viewers + */ + public void setRightLabel(String label) { + fRightLabel= label; + } + + /** + * Returns the label for the right hand side of compare/merge viewers. + * This label is typically shown in the title of the right side of a compare viewer. + * + * @param element the input object of a compare/merge viewer or <code>null</code> + * @return the label for the right hand side or <code>null</code> + */ + public String getRightLabel(Object element) { + return labelProvider.getRightLabel(element); + } + + /** + * Sets the image to use for the right side of compare/merge viewers. + * The compare configuration does not automatically dispose the old image. + * This image will be used if the element for which a image + * is requested does not have an right contributor or the element does not have + * a registered label provider or the label provider returns <code>null</code> + * as the image. + * + * @param image the new image for the right side of compare/merge viewers + */ + public void setRightImage(Image image) { + fRightImage= image; + } + + /** + * Returns the image for the right hand side of compare/merge viewers. + * This image is typically shown in the title of the right side of a compare viewer. + * + * @param element the input object of a compare/merge viewer or <code>null</code> + * @return the image for the right hand side or <code>null</code> + */ + public Image getRightImage(Object element) { + return labelProvider.getRightImage(element); + } + + /** + * Return the container of the compare associated with this configuration. + * @return the container of the compare associated with this configuration + * @since 3.3 + */ + public ICompareContainer getContainer() { + if (fContainer == null) { + // Create a default container in case one is not provided + fContainer= new CompareContainer(); + } + return fContainer; + } + + /** + * Set the container of the compare associated with this configuration. + * @param container the container of the compare associated with this configuration. + * @since 3.3 + */ + public void setContainer(ICompareContainer container) { + fContainer = container; + } + + /** + * Return the label provider that is used to determine the + * text and labels return by this compare configuration. + * @return the label provider that is used to determine the + * text and labels return by this compare configuration + * @see #getAncestorImage(Object) + * @see #getAncestorLabel(Object) + * @see #getLeftImage(Object) + * @see #getLeftLabel(Object) + * @see #getRightImage(Object) + * @see #getRightLabel(Object) + * @since 3.3 + */ + public ICompareInputLabelProvider getLabelProvider() { + return labelProvider; + } + + /** + * Set the label provider for the given compare input. The compare configuration + * will not dispose of the label provider when the configuration is disposed. + * It is up to the provider of the label provider to ensure that it is + * disposed when it is no longer needed. + * @param input the compare input + * @param labelProvider the label provider for the compare input + * @since 3.3 + */ + public void setLabelProvider(ICompareInput input, ICompareInputLabelProvider labelProvider) { + this.labelProvider.setLabelProvider(input, labelProvider); + } + + /** + * Set the default label provider for this configuration. The default label + * provider is used when a particular label provider has not been assigned + * using + * {@link #setLabelProvider(ICompareInput, ICompareInputLabelProvider)}. + * The compare configuration will not dispose of the label provider when the + * configuration is disposed. It is up to the provider of the label provider + * to ensure that it is disposed when it is no longer needed. + * + * @param labelProvider the default label provider + * @since 3.3 + */ + public void setDefaultLabelProvider(ICompareInputLabelProvider labelProvider) { + this.labelProvider.setDefaultLabelProvider(labelProvider); + } + + /** + * Set whether given change kind should be ignored while computing + * differences between documents. Changes specified by this method will be + * excluded from a comparison result. + * + * @param kind + * type of change, possible values are: + * {@link RangeDifference#CHANGE} + * {@link RangeDifference#CONFLICT} {@link RangeDifference#RIGHT} + * {@link RangeDifference#LEFT} {@link RangeDifference#ANCESTOR} + * {@link RangeDifference#ERROR} + * @param ignored + * whether given kind should be included in the ignored set + * @since 3.5 + */ + public void setChangeIgnored(int kind, boolean ignored) { + if (ignored) { + fIgnoredChanges.add(new Integer(kind)); + } else { + fIgnoredChanges.remove(new Integer(kind)); + } + } + + /** + * Return if a given change kind is ignored while computing differences + * between documents. + * + * @param kind + * type of change, possible values are: + * {@link RangeDifference#CHANGE} + * {@link RangeDifference#CONFLICT} {@link RangeDifference#RIGHT} + * {@link RangeDifference#LEFT} {@link RangeDifference#ANCESTOR} + * {@link RangeDifference#ERROR} + * @return whether kind of change is ignored + * @since 3.5 + */ + public boolean isChangeIgnored(int kind) { + return fIgnoredChanges.contains(new Integer(kind)); + } + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareEditorInput.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareEditorInput.java new file mode 100644 index 000000000..85a8c5fcf --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareEditorInput.java @@ -0,0 +1,1571 @@ +/******************************************************************************* + * 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. + * + * @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 fLeftDirty = false; + private boolean fRightDirty = false; + 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; + } + } + + /** + * 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 fLeftDirty; + } + + /** + * 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 fRightDirty; + } + + /** + * 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(); + fLeftDirty = dirty; + fRightDirty = dirty; + + 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(); + fLeftDirty = dirty; + + 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(); + fRightDirty = dirty; + + 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 == cmv.internalIsLeftDirty()) { + fLeftDirty = cmv.internalIsLeftDirty(); + } + + if (dirty == cmv.internalIsRightDirty()) { + fRightDirty = cmv.internalIsRightDirty(); + } + 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; + } + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareNavigator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareNavigator.java new file mode 100644 index 000000000..90ed8e2ea --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareNavigator.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 org.eclipse.compare.internal.Utilities; +import org.eclipse.core.runtime.IAdaptable; + +/** + * Supports cross-pane navigation through the differences of a compare container. + * <p> + * Clients may subclass this class. + * </p> + * @see INavigatable + * @since 3.3 + */ +public abstract class CompareNavigator implements ICompareNavigator { + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareNavigator#selectChange(boolean) + */ + public boolean selectChange(boolean next) { + // find most down stream CompareViewerPane + INavigatable[] navigators= getNavigatables(); + Object downStreamInput = null; + for (int i = navigators.length - 1; i >=0; i--) { + INavigatable navigatable = navigators[i]; + if (navigatable.getInput() == downStreamInput) { + // Skip to up stream pane if it has the same input + continue; + } + if (navigatable.selectChange(next ? INavigatable.NEXT_CHANGE : INavigatable.PREVIOUS_CHANGE)) { + // at end of this navigator + downStreamInput = navigatable.getInput(); + continue; + } + // not at end + if (i + 1 < navigators.length && navigators[i+1] != null && navigators[i+1].getInput() != downStreamInput) { + // The navigation has invoked a change in a downstream pane. + // Set the selected change depending on the direction we are navigating + navigators[i+1].selectChange(next ? INavigatable.FIRST_CHANGE : INavigatable.LAST_CHANGE); + } + return false; + } + + return true; + } + + protected abstract INavigatable[] getNavigatables(); + + /** + * Return the {@link INavigatable} for the given object. + * If the object implements {@link INavigatable}, then + * the object is returned. Otherwise, if the object + * implements {@link IAdaptable}, the object is + * adapted to {@link INavigatable}. + * @param object the object + * @return the {@link INavigatable} for the given object or <code>null</code> + */ + protected final INavigatable getNavigator(Object object) { + if (object == null) + return null; + Object data= Utilities.getAdapter(object, INavigatable.class); + if (data instanceof INavigatable) + return (INavigatable) data; + return null; + } + + /** + * Return whether a call to {@link ICompareNavigator#selectChange(boolean)} with the same parameter + * would succeed. + * @param next if <code>true</code> the next change is selected, otherwise the previous change + * @return whether a call to {@link ICompareNavigator#selectChange(boolean)} with the same parameter + * would succeed. + * @since 3.3 + */ + public boolean hasChange(boolean next) { + INavigatable[] navigators= getNavigatables(); + Object downStreamInput = null; + for (int i = navigators.length - 1; i >=0; i--) { + INavigatable navigatable = navigators[i]; + if (navigatable.getInput() == downStreamInput) { + // Skip to up stream pane if it has the same input + continue; + } + if (navigatable.hasChange(next ? INavigatable.NEXT_CHANGE : INavigatable.PREVIOUS_CHANGE)) { + return true; + } + // at end of this navigator + downStreamInput = navigatable.getInput(); + } + return false; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareUI.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareUI.java new file mode 100644 index 000000000..6d1c20941 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareUI.java @@ -0,0 +1,436 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.util.ResourceBundle; + +import org.eclipse.compare.internal.*; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IStructureCreator; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IReusableEditor; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.plugin.AbstractUIPlugin; + + +/** + * The class <code>CompareUI</code> defines the entry point to initiate a configurable + * compare operation on arbitrary resources. The result of the compare + * is opened into a compare editor where the details can be browsed and + * edited in dynamically selected structure and content viewers. + * <p> + * The Compare UI provides a registry for content and structure compare viewers, + * which is initialized from extensions contributed to extension points + * declared by this plug-in. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class CompareUI { + + /** + * Compare Plug-in ID (value <code>"org.eclipse.compare"</code>). + * @since 2.0 + */ + public static final String PLUGIN_ID= "org.eclipse.compare"; //$NON-NLS-1$ + + /** + * The id of the Compare Preference Page + * (value <code>"org.eclipse.compare.internal.ComparePreferencePage"</code>). + * + * @since 3.1 + */ + public static final String PREFERENCE_PAGE_ID= "org.eclipse.compare.internal.ComparePreferencePage"; //$NON-NLS-1$ + + /** + * Image descriptor for the disabled icon of the 'Next' tool bar button. + * @since 2.0 + */ + public static final ImageDescriptor DESC_DTOOL_NEXT= CompareUIPlugin.getImageDescriptor(ICompareUIConstants.DTOOL_NEXT); + /** + * Image descriptor for the normal icon of the 'Next' tool bar button. + * @since 2.0 + */ + public static final ImageDescriptor DESC_CTOOL_NEXT= CompareUIPlugin.getImageDescriptor(ICompareUIConstants.CTOOL_NEXT); + /** + * Image descriptor for the roll-over icon of the 'Next' tool bar button. + * @since 2.0 + */ + public static final ImageDescriptor DESC_ETOOL_NEXT= CompareUIPlugin.getImageDescriptor(ICompareUIConstants.ETOOL_NEXT); + + /** + * Image descriptor for the disabled icon of the 'Previous' tool bar button. + * @since 2.0 + */ + public static final ImageDescriptor DESC_DTOOL_PREV= CompareUIPlugin.getImageDescriptor(ICompareUIConstants.DTOOL_PREV); + /** + * Image descriptor for the normal icon of the 'Previous' tool bar button. + * @since 2.0 + */ + public static final ImageDescriptor DESC_CTOOL_PREV= CompareUIPlugin.getImageDescriptor(ICompareUIConstants.CTOOL_PREV); + /** + * Image descriptor for the roll-over icon of the 'Previous' tool bar button. + * @since 2.0 + */ + public static final ImageDescriptor DESC_ETOOL_PREV= CompareUIPlugin.getImageDescriptor(ICompareUIConstants.ETOOL_PREV); + + /** + * Name of the title property of a compare viewer. + * If a property with this name is set + * on the top level SWT control of a viewer, it is used as a title in the pane's + * title bar. + */ + public static final String COMPARE_VIEWER_TITLE= "org.eclipse.compare.CompareUI.CompareViewerTitle"; //$NON-NLS-1$ + + private CompareUI() { + // empty implementation + } + + public static AbstractUIPlugin getPlugin() { + return CompareUIPlugin.getDefault(); + } + + /** + * Returns this plug-in's resource bundle. + * + * @return the plugin's resource bundle + */ + public static ResourceBundle getResourceBundle() { + return CompareUIPlugin.getDefault().getResourceBundle(); + } + + /** + * Performs the comparison described by the given input and opens a + * compare editor on the result in the currently active workbench page. + * + * @param input the input on which to open the compare editor + */ + public static void openCompareEditor(CompareEditorInput input) { + openCompareEditor(input, true); + } + + /** + * Performs the comparison described by the given input and opens a compare + * editor on the result in the currently active workbench page. + * + * @param input + * the input on which to open the compare editor + * @param activate + * if <code>true</code> the editor will be activated + * @see IWorkbenchPage#openEditor(org.eclipse.ui.IEditorInput, String, + * boolean) + * @since 3.5 + */ + public static void openCompareEditor(CompareEditorInput input, boolean activate) { + openCompareEditorOnPage(input, null, activate); + } + + /** + * Performs the comparison described by the given input and opens a + * compare editor on the result in the given workbench page. + * + * @param input the input on which to open the compare editor + * @param page the workbench page in which to open the compare editor + * @since 2.1 + */ + public static void openCompareEditorOnPage(CompareEditorInput input, IWorkbenchPage page) { + openCompareEditorOnPage(input, page, true); + } + + /** + * Performs the comparison described by the given input and opens a compare + * editor on the result in the given workbench page. + * + * @param input + * the input on which to open the compare editor + * @param page + * the workbench page in which to open the compare editor + * @param activate + * if <code>true</code> the editor will be activated + * @see IWorkbenchPage#openEditor(org.eclipse.ui.IEditorInput, String, + * boolean) + */ + private static void openCompareEditorOnPage(CompareEditorInput input, IWorkbenchPage page, boolean activate) { + CompareUIPlugin plugin= CompareUIPlugin.getDefault(); + if (plugin != null) + plugin.openCompareEditor(input, page, null, activate); + } + + /** + * Performs the comparison described by the given input and + * shows the result in the given editor. + * + * @param input the input on which to open the compare editor + * @param editor the compare editor to reuse or null to create a new one + * @since 3.0 + */ + public static void reuseCompareEditor(CompareEditorInput input, IReusableEditor editor) { + reuseCompareEditor(input, editor, true); + } + + /** + * Performs the comparison described by the given input and shows the result + * in the given editor. + * + * @param input + * the input on which to open the compare editor + * @param editor + * the compare editor to reuse or null to create a new one + * @param activate + * if <code>true</code> the editor will be activated + * @see IWorkbenchPage#openEditor(org.eclipse.ui.IEditorInput, String, + * boolean) + */ + private static void reuseCompareEditor(CompareEditorInput input, IReusableEditor editor, boolean activate) { + CompareUIPlugin plugin= CompareUIPlugin.getDefault(); + if (plugin != null) + plugin.openCompareEditor(input, null, editor, activate); + } + + /** + * Performs the comparison described by the given input and opens a + * modal compare dialog on the result. + * + * @param input the input on which to open the compare dialog + */ + public static void openCompareDialog(CompareEditorInput input) { + CompareUIPlugin plugin= CompareUIPlugin.getDefault(); + if (plugin != null) + plugin.openCompareDialog(input); + } + + /** + * Registers an image descriptor for the given type. + * + * @param type the type + * @param descriptor the image descriptor + */ + public static void registerImageDescriptor(String type, ImageDescriptor descriptor) { + CompareUIPlugin.registerImageDescriptor(type, descriptor); + } + + /** + * Returns a shared image for the given type, or a generic image if none + * has been registered for the given type. + * <p> + * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + * </p> + * + * @param type the type + * @return the image + */ + public static Image getImage(String type) { + return CompareUIPlugin.getImage(type); + } + + /** + * Registers the given image for being disposed when this plug-in is shutdown. + * + * @param image the image to register for disposal + */ + public static void disposeOnShutdown(Image image) { + CompareUIPlugin.disposeOnShutdown(image); + } + + /** + * Returns a shared image for the given adaptable. + * This convenience method queries the given adaptable + * for its <code>IWorkbenchAdapter.getImageDescriptor</code>, which it + * uses to create an image if it does not already have one. + * <p> + * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + * </p> + * + * @param adaptable the adaptable for which to find an image + * @return an image + */ + public static Image getImage(IAdaptable adaptable) { + return CompareUIPlugin.getImage(adaptable); + } + + + /** + * Creates a stream merger for the given content type. + * If no stream merger is registered for the given content type <code>null</code> is returned. + * + * @param type the type for which to find a stream merger + * @return a stream merger for the given type, or <code>null</code> if no + * stream merger has been registered + * @deprecated Clients should obtain an <code>org.eclipse.team.core.mapping.IStorageMerger</code> from the + * <code>org.eclipse.team.core.Team#createMerger(IContentType)</code> method. + */ + public static IStreamMerger createStreamMerger(IContentType type) { + return CompareUIPlugin.getDefault().createStreamMerger(type); + } + + /** + * Creates a stream merger for the given file extension. + * If no stream merger is registered for the file extension <code>null</code> is returned. + * + * @param type the type for which to find a stream merger + * @return a stream merger for the given type, or <code>null</code> if no + * stream merger has been registered + * @deprecated Clients should obtain an <code>org.eclipse.team.core.mapping.IStorageMerger</code> from the + * <code>org.eclipse.team.core.Team#createMerger(String)</code> method. + */ + public static IStreamMerger createStreamMerger(String type) { + return CompareUIPlugin.getDefault().createStreamMerger(type); + } + + /** + * Returns a structure compare viewer based on an old viewer and an input object. + * If the old viewer is suitable for showing the input, the old viewer + * is returned. Otherwise, the input's type is used to find a viewer descriptor in the registry + * which in turn is used to create a structure compare viewer under the given parent composite. + * If no viewer descriptor can be found <code>null</code> is returned. + * + * @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 + * @param configuration a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public static Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent, + CompareConfiguration configuration) { + + return CompareUIPlugin.getDefault().findStructureViewer(oldViewer, input, parent, configuration); + } + + /** + * Returns a content compare viewer based on an old viewer and an input object. + * If the old viewer is suitable for showing the input the old viewer + * is returned. Otherwise the input's type is used to find a viewer descriptor in the registry + * which in turn is used to create a content compare viewer under the given parent composite. + * If no viewer descriptor can be found <code>null</code> is returned. + * + * @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 content viewer + * @param parent the SWT parent composite under which the new viewer is created + * @param configuration a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public static Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent, + CompareConfiguration configuration) { + return CompareUIPlugin.getDefault().findContentViewer(oldViewer, input, parent, configuration); + } + + /** + * Returns a content compare viewer based on an old viewer and an input + * object. If the old viewer is suitable for showing the input the old + * viewer is returned. Otherwise the input's type is used to find a viewer + * descriptor in the registry which in turn is used to create a content + * compare viewer under the given parent composite. In order to determine + * the input's type, the input must either implement IStreamContentAccessor + * and ITypedElement or ICompareInput. If no viewer descriptor can be found + * <code>null</code> is returned. + * + * @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 content viewer. Must + * implement either <code>IStreamContentAccessor</code> and<code> + * ITypedElement</code> or <code>ICompareInput</code>. + * @param parent the SWT parent composite under which the new viewer is created + * @param configuration a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public static Viewer findContentViewer(Viewer oldViewer, Object input, Composite parent, + CompareConfiguration configuration) { + + return CompareUIPlugin.getDefault().findContentViewer(oldViewer, input, parent, configuration); + } + + /** + * Adds an alias for the given type. Subsequent calls to + * <code>findStructureViewer</code> treat alias as a synonym for type and + * return the same viewer. + * + * @param type + * a type name for which a viewer has been registered + * @param alias + * a type name which should be treated as a synonym of type + * @since 2.0 + * @noreference This method is for internal use only. Clients should not + * call this method. + */ + public static void addStructureViewerAlias(String type, String alias) { + CompareUIPlugin.getDefault().addStructureViewerAlias(type, alias); + } + + /** + * Remove all aliases for the given type. This method does not affect the + * initial binding between type and viewer. If no aliases exist for the + * given type this method does nothing. + * + * @param type + * the type name for which all synonyms are removed. + * @since 2.0 + * @noreference This method is for internal use only. Clients should not + * call this method. + */ + public static void removeAllStructureViewerAliases(String type) { + CompareUIPlugin.getDefault().removeAllStructureViewerAliases(type); + } + + /** + * Retrieve a document for the given input or return <code>null</code> if + * no document has been registered for the input. + * @param input the object for which to retrieve a document + * @return a document or <code>null</code> if no document was registered for the input + * @since 3.1 + */ + public static IDocument getDocument(Object input) { + return DocumentManager.get(input); + } + + /** + * Register a document for the given input. + * @param input the object for which to register a document + * @param document the document to register + * @since 3.1 + */ + public static void registerDocument(Object input, IDocument document) { + DocumentManager.put(input, document); + } + + /** + * Unregister the given document. + * @param document the document to unregister + * @since 3.1 + */ + public static void unregisterDocument(IDocument document) { + DocumentManager.remove(document); + } + + /** + * Create and return a structure creator for the given typed element. + * Return <code>null</code> if an appropriate structure creator could + * not be obtained. + * @param element the typed element + * @return structure creator for the given typed element or <code>null</code> + * @since 3.4 + */ + public static IStructureCreator createStructureCreator(ITypedElement element) { + StructureCreatorDescriptor scd= CompareUIPlugin.getDefault().getStructureCreator(element.getType()); + if (scd != null) { + return scd.createStructureCreator(); + } + return null; + } + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerPane.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerPane.java new file mode 100644 index 000000000..cab95e2d0 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerPane.java @@ -0,0 +1,334 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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 org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.action.ToolBarManager; +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.OpenEvent; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.custom.ViewForm; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +/** + * A <code>CompareViewerPane</code> is a convenience class which installs a + * <code>CLabel</code> and a <code>Toolbar</code> in a <code>ViewForm</code>. + * <P> + * Double clicking onto the <code>CompareViewerPane</code>'s title bar maximizes + * the <code>CompareViewerPane</code> to the size of an enclosing <code>Splitter</code> + * (if there is one). + * If more <code>Splitters</code> are nested maximizing walks up and + * maximizes to the outermost <code>Splitter</code>. + * + * @since 2.0 + */ +public class CompareViewerPane extends ViewForm implements ISelectionProvider, + IDoubleClickListener, ISelectionChangedListener, IOpenListener, IAdaptable { + + private ToolBarManager fToolBarManager; + private Object fInput; + private ListenerList fSelectionListeners= new ListenerList(); + private ListenerList fDoubleClickListener= new ListenerList(); + private ListenerList fOpenListener= new ListenerList(); + + /** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * + * @param container a widget which will be the container of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception org.eclipse.swt.SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + */ + public CompareViewerPane(Composite container, int style) { + super(container, style); + + marginWidth= 0; + marginHeight= 0; + + Control topLeft = createTopLeft(this); + setTopLeft(topLeft); + + MouseAdapter ml= new MouseAdapter() { + public void mouseDoubleClick(MouseEvent e) { + Control content= getContent(); + if (content != null && content.getBounds().contains(e.x, e.y)) + return; + Control parent= getParent(); + if (parent instanceof Splitter) + ((Splitter)parent).setMaximizedControl(CompareViewerPane.this); + } + }; + + addMouseListener(ml); + getTopLeft().addMouseListener(ml); + + addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fToolBarManager != null) { + fToolBarManager.removeAll(); + fToolBarManager.dispose(); + } + fInput= null; + fSelectionListeners= null; + setImage(null); + } + }); + } + + /** + * @param parent + * a widget which will be the parent of the control (cannot be + * null) + * @return the control to be placed in the top left corner of the pane + * @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 Control createTopLeft(Composite parent) { + CLabel label = new CLabel(this, SWT.NONE) { + public Point computeSize(int wHint, int hHint, boolean changed) { + return super.computeSize(wHint, Math.max(24, hHint), changed); + } + }; + return label; + } + + /** + * Set the pane's title text. + * The value <code>null</code> clears it. + * + * @param label the text to be displayed in the pane or null + */ + public void setText(String label) { + CLabel cl= (CLabel) getTopLeft(); + if (cl != null && !cl.isDisposed()) + cl.setText(label); + } + + /** + * Set the pane's title Image. + * The value <code>null</code> clears it. + * + * @param image the image to be displayed in the pane or null + */ + public void setImage(Image image) { + CLabel cl= (CLabel) getTopLeft(); + if (cl != null) + cl.setImage(image); + } + + /** + * Returns a <code>ToolBarManager</code> if the given parent is a + * <code>CompareViewerPane</code> or <code>null</code> otherwise. + * + * @param parent a <code>Composite</code> or <code>null</code> + * @return a <code>ToolBarManager</code> if the given parent is a <code>CompareViewerPane</code> otherwise <code>null</code> + */ + public static ToolBarManager getToolBarManager(Composite parent) { + if (parent instanceof CompareViewerPane) { + CompareViewerPane pane= (CompareViewerPane) parent; + return pane.getToolBarManager(); + } + return null; + } + + /** + * Clears tool items in the <code>CompareViewerPane</code>'s control bar. + * + * @param parent a <code>Composite</code> or <code>null</code> + */ + public static void clearToolBar(Composite parent) { + ToolBarManager tbm= getToolBarManager(parent); + if (tbm != null) { + tbm.removeAll(); + tbm.update(true); + } + } + + //---- private stuff + + private ToolBarManager getToolBarManager() { + if (fToolBarManager != null && fToolBarManager.getControl() == null) + return null; + if (fToolBarManager == null) { + final ToolBar tb = new ToolBar(this, SWT.FLAT); + setTopCenter(tb); + fToolBarManager = new ToolBarManager(tb); + tb.getAccessible().addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + if (e.childID != ACC.CHILDID_SELF) { + ToolItem item = tb.getItem(e.childID); + if (item != null) { + String toolTip = item.getToolTipText(); + if (toolTip != null) { + e.result = toolTip; + } + } + } + } + }); + } + return fToolBarManager; + } + + /** + * Returns the current input of this pane or null if the pane has no input. + * + * @return an <code>Object</code> that is the input to this pane or null if the pane has no input. + * + * @since 3.3 + */ + public Object getInput() { + return fInput; + } + + /** + * Sets the input object of this pane. + * + * @param input the new input object or <code>null</code> + * @since 3.3 + */ + public void setInput(Object input) { + if (fInput != input) + fInput= input; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void addSelectionChangedListener(ISelectionChangedListener l) { + fSelectionListeners.add(l); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void removeSelectionChangedListener(ISelectionChangedListener l) { + fSelectionListeners.remove(l); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() + */ + public ISelection getSelection() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) + */ + public void setSelection(ISelection s) { + // Default is to do nothing + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) + */ + public void selectionChanged(SelectionChangedEvent ev) { + Object[] listeners= fSelectionListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) + ((ISelectionChangedListener) listeners[i]).selectionChanged(ev); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent) + */ + public void doubleClick(DoubleClickEvent event) { + Object[] listeners= fDoubleClickListener.getListeners(); + for (int i= 0; i < listeners.length; i++) + ((IDoubleClickListener) listeners[i]).doubleClick(event); + } + + /** + * Add a double-click listener to the pane. The listener will get + * invoked when the contents of the pane are double-clicked. Adding + * a listener that is already registered has no effect. + * @param listener the listener + * @since 3.3 + */ + public void addDoubleClickListener(IDoubleClickListener listener) { + fDoubleClickListener.add(listener); + } + + /** + * Remove a double-click listener. Removing a listener that is not + * registered has no effect. + * @param listener the listener + * @since 3.3 + */ + public void removeDoubleClickListener(IDoubleClickListener listener) { + fDoubleClickListener.remove(listener); + } + + /** + * Add an open listener to the pane. The listener will get + * invoked when the contents of the pane are double-clicked. Adding + * a listener that is already registered has no effect. + * @param listener the listener + * @since 3.3 + */ + public void addOpenListener(IOpenListener listener) { + fOpenListener.add(listener); + } + + /** + * Remove an open listener. Removing a listener that is not + * registered has no effect. + * @param listener the listener + * @since 3.3 + */ + public void removeOpenListener(IOpenListener listener) { + fOpenListener.remove(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IOpenListener#open(org.eclipse.jface.viewers.OpenEvent) + */ + public void open(OpenEvent event) { + Object[] listeners= fOpenListener.getListeners(); + for (int i= 0; i < listeners.length; i++) + ((IOpenListener) listeners[i]).open(event); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) + */ + public Object getAdapter(Class adapter) { + return Platform.getAdapterManager().getAdapter(this, adapter); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerSwitchingPane.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerSwitchingPane.java new file mode 100644 index 000000000..47e6f5aac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerSwitchingPane.java @@ -0,0 +1,397 @@ +/******************************************************************************* + * 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 org.eclipse.compare.contentmergeviewer.IFlushable; +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.IFlushable2; +import org.eclipse.compare.internal.NullViewer; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.Viewer; +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.swt.widgets.Display; + +import com.ibm.icu.text.MessageFormat; + + +/** + * A custom <code>CompareViewerPane</code> that supports dynamic viewer switching. + * + * <p> + * Clients must implement the viewer switching strategy by implementing + * the <code>getViewer(Viewer, Object)</code> method. + * <p> + * If a property with the name <code>CompareUI.COMPARE_VIEWER_TITLE</code> is set + * on the top level SWT control of a viewer, it is used as a title in the <code>CompareViewerPane</code>'s + * title bar. + * + * @since 2.0 + */ +public abstract class CompareViewerSwitchingPane extends CompareViewerPane { + + private Viewer fViewer; + private boolean fControlVisibility= false; + private String fTitle; + private String fTitleArgument; + + /** + * Creates a <code>CompareViewerSwitchingPane</code> as a child of the given parent and with the + * specified SWT style bits. + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception org.eclipse.swt.SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + */ + public CompareViewerSwitchingPane(Composite parent, int style) { + this(parent, style, false); + } + + /** + * Creates a <code>CompareViewerSwitchingPane</code> as a child of the given parent and with the + * specified SWT style bits. + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * @param visibility the initial visibility of the CompareViewerSwitchingPane + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception org.eclipse.swt.SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + */ + public CompareViewerSwitchingPane(Composite parent, int style, boolean visibility) { + super(parent, style); + + fControlVisibility= visibility; + + setViewer(new NullViewer(this)); + + addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fViewer != null) + fViewer.removeSelectionChangedListener(CompareViewerSwitchingPane.this); + if (fViewer instanceof StructuredViewer) { + StructuredViewer sv= (StructuredViewer) fViewer; + sv.removeDoubleClickListener(CompareViewerSwitchingPane.this); + sv.removeOpenListener(CompareViewerSwitchingPane.this); + } + fViewer= null; + } + } + ); + } + + /** + * Returns the current viewer. + * + * @return the current viewer + */ + public Viewer getViewer() { + return fViewer; + } + + private void setViewer(Viewer newViewer) { + + if (newViewer == fViewer) + return; + + boolean oldEmpty= isEmpty(); + + if (fViewer != null) { + + fViewer.removeSelectionChangedListener(this); + + if (fViewer instanceof StructuredViewer) { + StructuredViewer sv= (StructuredViewer) fViewer; + sv.removeDoubleClickListener(this); + sv.removeOpenListener(this); + } + + Control content= getContent(); + setContent(null); + + fViewer.setInput(null); + + if (content != null && !content.isDisposed()) + content.dispose(); + + } else { + oldEmpty= false; + } + + setContent(null); + + fViewer= newViewer; + + if (fViewer != null) { + // we have to remember and restore the old visibility of the CustomPane + // since setContent changes the visibility + boolean old= getVisible(); + setContent(fViewer.getControl()); + setVisible(old); // restore old visibility + + boolean newEmpty= isEmpty(); + + fViewer.addSelectionChangedListener(this); + + if (fViewer instanceof StructuredViewer) { + StructuredViewer sv= (StructuredViewer) fViewer; + sv.addDoubleClickListener(this); + sv.addOpenListener(this); + } + + if (oldEmpty != newEmpty) { // re-layout my container + Composite parent= getParent(); + if (parent instanceof Splitter) + ((Splitter)parent).setVisible(this, fControlVisibility ? !newEmpty : true); + } + + layout(true); + } + } + + /** + * Returns the optional title argument that has been set with + * <code>setTitelArgument</code> or <code>null</code> if no optional title + * argument has been set. + * + * @return the optional title argument or <code>null</code> + * @noreference This method is for internal use only. Clients should not + * call this method. + * @nooverride This method is not intended to be re-implemented or extended + * by clients. + */ + public String getTitleArgument() { + return fTitleArgument; + } + + /** + * Returns <code>true</code> if no viewer is installed or if the current viewer + * is a <code>NullViewer</code>. + * + * @return <code>true</code> if no viewer is installed or if the current viewer is a <code>NullViewer</code> + */ + public boolean isEmpty() { + return fViewer == null || fViewer instanceof NullViewer; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.CompareViewerPane#getSelection() + */ + public ISelection getSelection() { + if (fViewer != null) + return fViewer.getSelection(); + return super.getSelection(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.CompareViewerPane#setSelection(org.eclipse.jface.viewers.ISelection) + */ + public void setSelection(ISelection s) { + if (fViewer != null) + fViewer.setSelection(s); + } + + private boolean hasFocus2() { + // do we have focus? + Display display= getDisplay(); + if (display != null) + for (Control focus= display.getFocusControl(); focus != null; focus= focus.getParent()) + if (focus == this) + return true; + return false; + } + + /** + * @param input the input + * @return true, if the input is considered as changed + * @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 boolean inputChanged(Object input) { + return getInput() != input; + } + + /** + * Sets the input object of this pane. + * For this input object a suitable viewer is determined by calling the abstract + * method <code>getViewer(Viewer, Object)</code>. + * If the returned viewer differs from the current one, the old viewer + * is disposed and the new one installed. Then the input object is fed + * into the newly installed viewer by calling its <code>setInput(Object)</code> method. + * If new and old viewer don't differ no new viewer is installed but just + * <code>setInput(Object)</code> is called. + * If the input is <code>null</code> the pane is cleared, + * that is the current viewer is disposed. + * + * @param input the new input object or <code>null</code> + */ + public void setInput(Object input) { + + if (!inputChanged(input)) + return; + + boolean hadFocus = hasFocus2(); + + super.setInput(input); + + // viewer switching + Viewer newViewer= null; + if (input != null) + newViewer= getViewer(fViewer, input); + + if (newViewer == null) { + if (fViewer instanceof NullViewer) + return; + newViewer= new NullViewer(this); + } + + setViewer(newViewer); + + // set input + fViewer.setInput(input); + + if (getViewer() == null || !Utilities.okToUse(getViewer().getControl())) + return; + + Image image= null; + if (!(fViewer instanceof NullViewer) && input instanceof ICompareInput) + image= ((ICompareInput)input).getImage(); + setImage(image); + + String title= null; + if (fViewer != null) { + Control c= fViewer.getControl(); + if (c != null) { + Object data= c.getData(CompareUI.COMPARE_VIEWER_TITLE); + if (data instanceof String) + title= (String) data; + if (hadFocus) + c.setFocus(); + } + } + + fTitle= title; + updateTitle(); + } + + /** + * Sets an additional and optional argument for the pane's title. + * + * @param argument + * an optional argument for the pane's title + * @noreference This method is for internal use only. Clients should not + * call this method. + * @nooverride This method is not intended to be re-implemented or extended + * by clients. + */ + public void setTitleArgument(String argument) { + fTitleArgument= argument; + updateTitle(); + } + + private void updateTitle() { + if (fTitle != null) { + if (fTitleArgument != null) { + String format= CompareMessages.CompareViewerSwitchingPane_Titleformat; + String t= MessageFormat.format(format, new String[] { fTitle, fTitleArgument } ); + setText(t); + } else + setText(fTitle); + } else { + setText(""); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + * @since 3.3 + * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) + */ + public Object getAdapter(Class adapter) { + if (adapter == INavigatable.class) { + if (isEmpty()) + return null; + Viewer viewer= getViewer(); + if (viewer == null) + return null; + Control control= viewer.getControl(); + if (control == null) + return null; + Object data= control.getData(INavigatable.NAVIGATOR_PROPERTY); + if (data instanceof INavigatable) + return data; + } + if (adapter == IFlushable.class) { + Viewer v= getViewer(); + if (v != null) { + IFlushable flushable = (IFlushable)Utilities.getAdapter(v, IFlushable.class); + if (flushable != null) + return flushable; + } + } + if (adapter == IFlushable2.class) { + Viewer v= getViewer(); + if (v != null) { + IFlushable2 flushable = (IFlushable2)Utilities.getAdapter(v, IFlushable2.class); + if (flushable != null) + return flushable; + } + } + return super.getAdapter(adapter); + } + + /* (non-Javadoc) + * @see org.eclipse.swt.widgets.Composite#setFocus() + */ + public boolean setFocus() { + Viewer v= getViewer(); + if (v != null) { + Control c= v.getControl(); + if (c != null) { + if (c.setFocus()) + return true; + } + } + return super.setFocus(); + } + + /** + * Returns a viewer which is able to display the given input. + * If no viewer can be found, <code>null</code> is returned. + * The additional argument oldViewer represents the viewer currently installed + * in the pane (or <code>null</code> if no viewer is installed). + * It can be returned from this method if the current viewer can deal with the + * input (and no new viewer must be created). + * + * @param oldViewer the currently installed viewer or <code>null</code> + * @param input the input object for which a viewer must be determined or <code>null</code> + * @return a viewer for the given input, or <code>null</code> if no viewer can be determined + */ + abstract protected Viewer getViewer(Viewer oldViewer, Object input); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/EditionSelectionDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/EditionSelectionDialog.java new file mode 100644 index 000000000..ddb220aa4 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/EditionSelectionDialog.java @@ -0,0 +1,1196 @@ +/******************************************************************************* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ResourceBundle; + +import com.ibm.icu.text.DateFormat; +import com.ibm.icu.text.MessageFormat; +import com.ibm.icu.util.Calendar; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.swt.widgets.Widget; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.internal.CompareContainer; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ResizableDialog; +import org.eclipse.compare.internal.StructureCreatorDescriptor; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.compare.structuremergeviewer.IStructureCreator; + + +/** + * A dialog where one input element can be compared against + * a list of historic variants (editions) of the same input element. + * The dialog can be used to implement functions like "Compare/Replace with Version" or + * "Compare/Replace from Local History" on workspace resources. + * <p> + * In addition it is possible to specify a subsection of the input element + * (e.g. a method in a Java source file) by means of a "path". + * In this case the dialog compares only the subsection (as specified by the path) + * with the corresponding subsection in the list of editions. + * Only those editions are shown where the subsection differs from the same subsection in + * another edition thereby minimizing the number of presented variants. + * This functionality can be used to implement "Replace from Local History" + * for the Java language. + * <p> + * Subsections of an input element are determined by first finding an + * <code>IStructureCreator</code> for the input's type. + * Then the method <code>locate</code> is used to extract the subsection. + * <p> + * Each edition (variant in the list of variants) must implement the <code>IModificationDate</code> interface + * so that the dialog can sort the editions and present them in a tree structure where every + * node corresponds one day. + * <p> + * The functionality is surfaced in a single function <code>selectEdition</code>. + * <p> + * Clients may instantiate this class; it is not intended to be subclassed. + * </p> + * + * @see IModificationDate + * @see ITypedElement + * + * @deprecated Use an <code>org.eclipse.team.ui.history.IHistoryPageSource</code> in conjunction with + * the <code>org.eclipse.team.ui.history.IHistoryView</code> or a <code>HistoryPageCompareEditorInput</code>. + * For sub-file elements, a <code>org.eclipse.team.ui.history.ElementLocalHistoryPageSource</code> can be used. + * @noextend This class is not intended to be subclassed by clients. + */ +public class EditionSelectionDialog extends ResizableDialog { + + /** + * An item in an underlying edition. + */ + private static class Pair { + + private ITypedElement fEdition; + private ITypedElement fItem; + private String fContent; + private IStructureCreator fStructureCreator; + private boolean fHasError= false; + + Pair(IStructureCreator structureCreator, ITypedElement edition, ITypedElement item) { + fStructureCreator= structureCreator; + fEdition= edition; + fItem= item; + } + + Pair(IStructureCreator structureCreator, ITypedElement edition) { + this(structureCreator, edition, edition); + } + + ITypedElement getEdition() { + return fEdition; + } + + ITypedElement getItem() { + return fItem; + } + + /* + * The content is lazily loaded + */ + private String getContent() { + if (fContent == null) { + if (fStructureCreator != null) + fContent= fStructureCreator.getContents(fItem, false); + else { + if (fItem instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) fItem; + try { + fContent= Utilities.readString(sca); + } catch (CoreException ex) { + // NeedWork + CompareUIPlugin.log(ex); + } + } + } + if (fContent == null) + fContent= ""; //$NON-NLS-1$ + } + return fContent; + } + + public boolean equals(Object other) { + if (other != null && other.getClass() == getClass()) { + if (getContent().equals(((Pair)other).getContent())) + return true; + } + return super.equals(other); + } + + public int hashCode() { + return getContent().hashCode(); + } + } + + // Configuration options + private CompareConfiguration fCompareConfiguration; + private ArrayList fArrayList= new ArrayList(); + /** use a side-by-side compare viewer */ + private boolean fCompare= true; + /** show target on right hand side */ + private boolean fTargetIsRight= false; + /** hide entries which have identical content */ + private boolean fHideIdentical= true; + /** add mode if true, otherwise replace mode */ + private boolean fAddMode= false; + /** compare mode if true, otherwise replace/add mode */ + private boolean fCompareMode= false; + /** perform structure compare on editions */ + private boolean fStructureCompare= false; + /** allow for multiple selection */ + private boolean fMultiSelect= false; + + /** + * Maps from members to their corresponding editions. + * Has only a single entry if dialog is used in "Replace" (and not "Add") mode. + */ + private HashMap fMemberEditions; + /** + * Maps from members to their corresponding selected edition. + */ + private HashMap fMemberSelection; + /** The editions of the current selected member */ + private List fCurrentEditions; + private Thread fThread; + private Pair fTargetPair; + /** The selected edition in the edition viewer */ + private ITypedElement fSelectedItem; + private String fTitleArg; + private Image fTitleImage; + + // SWT controls + private CompareViewerSwitchingPane fContentPane; + private Button fCommitButton; + private Table fMemberTable; + private CompareViewerPane fMemberPane; + private Tree fEditionTree; + private CompareViewerPane fEditionPane; + private Image fDateImage; + private Image fTimeImage; + private CompareViewerSwitchingPane fStructuredComparePane; + private Label statusLabel; + + /** + * Creates a new modal, resizable dialog. + * Various titles, icons, and labels are configured from the given resource bundle. + * The following resource keys are used: + * <pre> + * key type description + * title String dialog title + * width Integer initial width of dialog + * height Integer initial height of dialog + * treeTitleFormat MessageFormat pane title for edition tree; arg 0 is the target + * dateIcon String icon for node in edition tree; path relative to plug-in + * timeIcon String icon for leaf in edition tree; path relative to plug-in + * todayFormat MessageFormat format string if date is todays date; arg 0 is date + * yesterdayFormat MessageFormat format string if date is yesterdays date; arg 0 is date + * dayFormat MessageFormat format string if date is any other date; arg 0 is date + * editionLabel String label for editions side of compare viewer; arg 0 is the date + * targetLabel String label for target side of compare viewer + * buttonLabel String label for OK button; default is IDialogConstants.OK_LABEL + * </pre> + * + * @param parent if not <code>null</code> the new dialog stays on top of this parent shell + * @param bundle <code>ResourceBundle</code> to configure the dialog + */ + public EditionSelectionDialog(Shell parent, ResourceBundle bundle) { + super(parent, bundle); + } + + private CompareConfiguration getCompareConfiguration() { + if (fCompareConfiguration == null) { + fCompareConfiguration= new CompareConfiguration(); + fCompareConfiguration.setLeftEditable(false); + fCompareConfiguration.setRightEditable(false); + fCompareConfiguration.setContainer(new CompareContainer() { + public void setStatusMessage(String message) { + if (statusLabel != null && !statusLabel.isDisposed()) { + if (message == null) { + statusLabel.setText(""); //$NON-NLS-1$ + } else { + statusLabel.setText(message); + } + } + } + }); + } + return fCompareConfiguration; + } + + /** + * Sets the help context for this dialog. + * + * @param contextId the help context id. + * @since 3.2 + */ + public void setHelpContextId(String contextId) { + super.setHelpContextId(contextId); + } + + /** + * Sets an additional and optional argument for the edition pane's title. + * + * @param titleArgument an optional argument for the edition pane's title + * @since 2.0 + */ + public void setEditionTitleArgument(String titleArgument) { + fTitleArg= titleArgument; + } + + /** + * Sets an optional image for the edition pane's title. + * + * @param titleImage an optional image for the edition pane's title + * @since 2.0 + */ + public void setEditionTitleImage(Image titleImage) { + fTitleImage= titleImage; + } + + /** + * Select the previous edition (presenting a UI). + * + * @param target the input object against which the editions are compared; must not be <code>null</code> + * @param inputEditions the list of editions (element type: <code>ITypedElement</code>s) + * @param ppath If <code>null</code> dialog shows full input; if non <code>null</code> it extracts a subsection + * @return returns the selected edition or <code>null</code> if error occurred. + * The returned <code>ITypedElement</code> is one of the original editions + * if <code>path</code> was <code>null</code>; otherwise + * it is an <code>ITypedElement</code> returned from <code>IStructureCreator.locate(path, item)</code> + * @since 2.0 + */ + public ITypedElement selectPreviousEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath) { + Assert.isNotNull(target); + fTargetPair= new Pair(null, target); + + // sort input editions + final int count= inputEditions.length; + final IModificationDate[] editions= new IModificationDate[count]; + for (int i= 0; i < count; i++) + editions[i]= (IModificationDate) inputEditions[i]; + if (count > 1) + internalSort(editions); + + // find StructureCreator if ppath is not null + IStructureCreator structureCreator= null; + if (ppath != null) { + String type= target.getType(); + StructureCreatorDescriptor scd= CompareUIPlugin.getDefault().getStructureCreator(type); + if (scd != null) + structureCreator= scd.createStructureCreator(); + } + + if (fAddMode) { + // does not work in add mode + return null; + } + + if (structureCreator != null) { + Pair pair= createPair(structureCreator, ppath, target); + if (pair != null) + fTargetPair= pair; + else + ppath= null; // couldn't extract item because of error + } + + // from front (newest) to back (oldest) + for (int i= 0; i < count; i++) { + + ITypedElement edition= (ITypedElement) editions[i]; + Pair pair= null; + + if (structureCreator != null && ppath != null) { + // extract sub element from edition + pair= createPair(structureCreator, ppath, edition); + } else { + pair= new Pair(null, edition); + } + + if (pair != null && pair.fHasError) + return null; + + if (pair != null && !fTargetPair.equals(pair)) { + return pair.fItem; + } + } + + // nothing found + return null; + } + + /** + * Presents this modal dialog with the functionality described in the class comment above. + * + * @param target the input object against which the editions are compared; must not be <code>null</code> + * @param inputEditions the list of editions (element type: <code>ITypedElement</code>s) + * @param ppath If <code>null</code> dialog shows full input; if non <code>null</code> it extracts a subsection + * @return returns the selected edition or <code>null</code> if dialog was cancelled. + * The returned <code>ITypedElement</code> is one of the original editions + * if <code>path</code> was <code>null</code>; otherwise + * it is an <code>ITypedElement</code> returned from <code>IStructureCreator.locate(path, item)</code> + */ + public ITypedElement selectEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath) { + + Assert.isNotNull(target); + fTargetPair= new Pair(null, target); + + // sort input editions + final int count= inputEditions.length; + final IModificationDate[] editions= new IModificationDate[count]; + for (int i= 0; i < count; i++) + editions[i]= (IModificationDate) inputEditions[i]; + if (count > 1) + internalSort(editions); + + // find StructureCreator if ppath is not null + IStructureCreator structureCreator= null; + if (ppath != null) { + String type= target.getType(); + StructureCreatorDescriptor scd= CompareUIPlugin.getDefault().getStructureCreator(type); + if (scd != null) + structureCreator= scd.createStructureCreator(); + } + + if (!fAddMode) { + // replace mode + + if (structureCreator != null) { + Pair pair= createPair(structureCreator, ppath, target); + if (pair != null) + fTargetPair= pair; + else + ppath= null; // couldn't extract item because of error + } + + // set the left and right labels for the compare viewer + String targetLabel= getTargetLabel(target, fTargetPair.getItem()); + if (fTargetIsRight) + getCompareConfiguration().setRightLabel(targetLabel); + else + getCompareConfiguration().setLeftLabel(targetLabel); + + if (structureCreator != null && ppath != null) { // extract sub element + + final IStructureCreator sc= structureCreator; + final Object path= ppath; + + // construct the comparer thread + // and perform the background extract + fThread= new Thread() { + public void run() { + + // from front (newest) to back (oldest) + for (int i= 0; i < count; i++) { + + if (fEditionTree == null || fEditionTree.isDisposed()) + break; + ITypedElement edition= (ITypedElement) editions[i]; + + // extract sub element from edition + Pair pair= createPair(sc, path, edition); + if (pair != null) + sendPair(pair); + } + sendPair(null); + } + }; + } else { + // create tree widget + create(); + + // from front (newest) to back (oldest) + for (int i= 0; i < count; i++) + addMemberEdition(new Pair(null, (ITypedElement) editions[i])); + } + + } else { + // add mode + final Object container= ppath; + Assert.isNotNull(container); + + if (structureCreator == null) + return null; // error + + // extract all elements of container + final HashSet current= new HashSet(); + IStructureComparator sco= structureCreator.locate(container, target); + if (sco != null) { + Object[] children= sco.getChildren(); + if (children != null) + for (int i= 0; i < children.length; i++) + current.add(children[i]); + } + + final IStructureCreator sc= structureCreator; + + // construct the comparer thread + // and perform the background extract + fThread= new Thread() { + public void run() { + + // from front (newest) to back (oldest) + for (int i= 0; i < count; i++) { + + if (fEditionTree == null || fEditionTree.isDisposed()) + break; + ITypedElement edition= (ITypedElement) editions[i]; + + IStructureComparator sco2= sc.locate(container, edition); + if (sco2 != null) { + Object[] children= sco2.getChildren(); + if (children != null) { + for (int i2= 0; i2 < children.length; i2++) { + ITypedElement child= (ITypedElement) children[i2]; + if (!current.contains(child)) + sendPair(new Pair(sc, edition, child)); + } + } + } + } + sendPair(null); + } + }; + } + + open(); + + if (getReturnCode() == OK) + return fSelectedItem; + return null; + } + + private Pair createPair(IStructureCreator sc, Object path, ITypedElement input) { + IStructureComparator scmp= sc.locate(path, input); + if (scmp == null && sc.getStructure(input) == null) { // parse error + Pair p= new Pair(sc, input); + p.fHasError= true; + return p; + } + if (scmp instanceof ITypedElement) + return new Pair(sc, input, (ITypedElement) scmp); + return null; + } + + /** + * Controls whether identical entries are shown or not (default). + * This method must be called before <code>selectEdition</code>. + * + * @param hide if true identical entries are hidden; otherwise they are shown. + * @since 2.0 + */ + public void setHideIdenticalEntries(boolean hide) { + fHideIdentical= hide; + } + + /** + * Controls whether workspace target is on the left (the default) or right hand side. + * + * @param isRight if true target is shown on right hand side. + * @since 2.0 + */ + public void setTargetIsRight(boolean isRight) { + fTargetIsRight= isRight; + } + + /** + * Controls whether the <code>EditionSelectionDialog</code> is in 'add' mode + * or 'replace' mode (the default). + * + * @param addMode if true dialog is in 'add' mode. + * @since 2.0 + */ + public void setAddMode(boolean addMode) { + fAddMode= addMode; + fMultiSelect= addMode; + } + + /** + * Controls whether the <code>EditionSelectionDialog</code> is in 'compare' mode + * or 'add/replace' (the default) mode. + * + * @param compareMode if true dialog is in 'add' mode. + * @since 2.0 + */ + public void setCompareMode(boolean compareMode) { + fCompareMode= compareMode; + fStructureCompare= fCompareMode && !fAddMode; + } + + /** + * Returns the input target that has been specified with the most recent call + * to <code>selectEdition</code>. If a not <code>null</code> path was specified this method + * returns a subsection of this target (<code>IStructureCreator.locate(path, target)</code>) + * instead of the input target. + * <p> + * For example if the <code>target</code> is a Java compilation unit and <code>path</code> specifies + * a method, the value returned from <code>getTarget</code> will be the method not the compilation unit. + * + * @return the last specified target or a subsection thereof. + */ + public ITypedElement getTarget() { + return fTargetPair.getItem(); + } + + /** + * Returns the editions that have been selected with the most + * recent call to <code>selectEdition</code>. + * + * @return the selected editions as an array. + * @since 2.1 + */ + public ITypedElement[] getSelection() { + ArrayList result= new ArrayList(); + if (fMemberSelection != null) { + Iterator iter= fArrayList.iterator(); + while (iter.hasNext()) { + Object edition= iter.next(); + Object item= fMemberSelection.get(edition); + if (item != null) + result.add(item); + } + } else if (fSelectedItem != null) + result.add(fSelectedItem); + return (ITypedElement[]) result.toArray(new ITypedElement[result.size()]); + } + + /** + * Returns a label for identifying the target side of a compare viewer. + * This implementation extracts the value for the key "targetLabel" from the resource bundle + * and passes it as the format argument to <code>MessageFormat.format</code>. + * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) + * is the name of the given input element. + * <p> + * Subclasses may override to create their own label. + * </p> + * + * @param target the target element for which a label must be returned + * @param item if a path has been specified in <code>selectEdition</code> a sub element of the given target; otherwise the same as target + * @return a label the target side of a compare viewer + */ + protected String getTargetLabel(ITypedElement target, ITypedElement item) { + String format= null; + if (target instanceof ResourceNode) + format= Utilities.getString(fBundle, "workspaceTargetLabel", null); //$NON-NLS-1$ + if (format == null) + format= Utilities.getString(fBundle, "targetLabel"); //$NON-NLS-1$ + if (format == null) + format= "x{0}"; //$NON-NLS-1$ + + return formatString(format, target.getName()); + } + + private String formatString(String string, String variable) { + // Only process the string if it contains a variable or an escaped quote (see bug 190023) + if (hasVariable(string) || hasDoubleQuotes(string)) + return MessageFormat.format(string, new Object[] { variable }); + return string; + } + + private boolean hasDoubleQuotes(String string) { + return string.indexOf("''") != -1; //$NON-NLS-1$ + } + + private boolean hasVariable(String string) { + return string.indexOf("{0}") != -1; //$NON-NLS-1$ + } + + /** + * Returns a label for identifying the edition side of a compare viewer. + * This implementation extracts the value for the key "editionLabel" from the resource bundle + * and passes it as the format argument to <code>MessageFormat.format</code>. + * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) + * is the formatted modification date of the given input element. + * <p> + * Subclasses may override to create their own label. + * </p> + * + * @param selectedEdition the selected edition for which a label must be returned + * @param item if a path has been specified in <code>selectEdition</code> a sub element of the given selectedEdition; otherwise the same as selectedEdition + * @return a label for the edition side of a compare viewer + */ + protected String getEditionLabel(ITypedElement selectedEdition, ITypedElement item) { + String format= null; + if (selectedEdition instanceof ResourceNode) + format= Utilities.getString(fBundle, "workspaceEditionLabel", null); //$NON-NLS-1$ + else if (selectedEdition instanceof HistoryItem) + format= Utilities.getString(fBundle, "historyEditionLabel", null); //$NON-NLS-1$ + if (format == null) + format= Utilities.getString(fBundle, "editionLabel"); //$NON-NLS-1$ + if (format == null) + format= "x{0}"; //$NON-NLS-1$ + + + String date= ""; //$NON-NLS-1$ + if (selectedEdition instanceof IModificationDate) { + long modDate= ((IModificationDate)selectedEdition).getModificationDate(); + date= DateFormat.getDateTimeInstance().format(new Date(modDate)); + } + + return formatString(format, date); + } + + /** + * Returns a label for identifying a node in the edition tree viewer. + * This implementation extracts the value for the key "workspaceTreeFormat" or + * "treeFormat" (in that order) from the resource bundle + * and passes it as the format argument to <code>MessageFormat.format</code>. + * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) + * is the formatted modification date of the given input element. + * <p> + * Subclasses may override to create their own label. + * </p> + * + * @param edition the edition for which a label must be returned + * @param item if a path has been specified in <code>edition</code> a sub element of the given edition; otherwise the same as edition + * @param date this date will be returned as part of the formatted string + * @return a label of a node in the edition tree viewer + * @since 2.0 + */ + protected String getShortEditionLabel(ITypedElement edition, ITypedElement item, Date date) { + String format= null; + if (edition instanceof ResourceNode) + format= Utilities.getString(fBundle, "workspaceTreeFormat", null); //$NON-NLS-1$ + if (format == null) + format= Utilities.getString(fBundle, "treeFormat", null); //$NON-NLS-1$ + if (format == null) + format= "x{0}"; //$NON-NLS-1$ + + String ds= DateFormat.getTimeInstance().format(date); + return formatString(format, ds); + } + + /** + * Returns an image for identifying the edition side of a compare viewer. + * This implementation extracts the value for the key "editionLabel" from the resource bundle + * and passes it as the format argument to <code>MessageFormat.format</code>. + * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) + * is the formatted modification date of the given input element. + * <p> + * Subclasses may override to create their own label. + * </p> + * + * @param selectedEdition the selected edition for which a label must be returned + * @param item if a path has been specified in <code>selectEdition</code> a sub element of the given selectedEdition; otherwise the same as selectedEdition + * @return a label the edition side of a compare viewer + * @since 2.0 + */ + protected Image getEditionImage(ITypedElement selectedEdition, ITypedElement item) { + if (selectedEdition instanceof ResourceNode) + return selectedEdition.getImage(); + if (selectedEdition instanceof HistoryItem) { + if (fTimeImage == null) { + String iconName= Utilities.getString(fBundle, "timeIcon", "obj16/resource_obj.gif"); //$NON-NLS-1$ //$NON-NLS-2$ + ImageDescriptor id= CompareUIPlugin.getImageDescriptor(iconName); + if (id != null) + fTimeImage= id.createImage(); + } + return fTimeImage; + } + return null; + } + + /* (non Javadoc) + * Creates SWT control tree. + */ + protected synchronized Control createDialogArea(Composite parent2) { + + Composite parent= (Composite) super.createDialogArea(parent2); + + getShell().setText(Utilities.getString(fBundle, "title")); //$NON-NLS-1$ + + Splitter vsplitter= new Splitter(parent, SWT.VERTICAL); + vsplitter.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL + | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); + + vsplitter.addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fCompareConfiguration != null) { + fCompareConfiguration.dispose(); + fCompareConfiguration= null; + } + if (fDateImage != null) { + fDateImage.dispose(); + fDateImage= null; + } + if (fTimeImage != null) { + fTimeImage.dispose(); + fTimeImage= null; + } + } + } + ); + + if (fAddMode) { + // we need two panes: the left for the elements, the right one for the editions + Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); + + fMemberPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); + fMemberPane.setText(Utilities.getString(fBundle, "memberPaneTitle")); //$NON-NLS-1$ + + int flags= SWT.H_SCROLL | SWT.V_SCROLL; + if (fMultiSelect) + flags|= SWT.CHECK; + fMemberTable= new Table(fMemberPane, flags); + fMemberTable.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (e.detail == SWT.CHECK) { + if (e.item instanceof TableItem) { + TableItem ti= (TableItem) e.item; + Object data= ti.getData(); + if (ti.getChecked()) + fArrayList.add(data); + else + fArrayList.remove(data); + + if (fCommitButton != null) + fCommitButton.setEnabled(fArrayList.size() > 0); + + fMemberTable.setSelection(new TableItem[] { ti }); + } + } + handleMemberSelect(e.item); + } + } + ); + fMemberPane.setContent(fMemberTable); + fMemberTable.setFocus(); + + fEditionPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); + } else { + if (fStructureCompare) { + // we need two panes: the left for the elements, the right one for the structured diff + Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); + + fEditionPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); + fStructuredComparePane= new CompareViewerSwitchingPane(hsplitter, SWT.BORDER | SWT.FLAT, true) { + protected Viewer getViewer(Viewer oldViewer, Object input) { + if (input instanceof ICompareInput) + return CompareUI.findStructureViewer(oldViewer, (ICompareInput)input, this, getCompareConfiguration()); + return null; + } + }; + fStructuredComparePane.addSelectionChangedListener( + new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent e) { + feedInput2(e.getSelection()); + } + } + ); + } else { + // only a single pane showing the editions + fEditionPane= new CompareViewerPane(vsplitter, SWT.BORDER | SWT.FLAT); + } + if (fTitleArg == null) + fTitleArg= fTargetPair.getItem().getName(); + String titleFormat= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ + String title= MessageFormat.format(titleFormat, new String[] { fTitleArg }); + fEditionPane.setText(title); + if (fTitleImage != null) + fEditionPane.setImage(fTitleImage); + } + + fEditionTree= new Tree(fEditionPane, SWT.H_SCROLL | SWT.V_SCROLL); + fEditionTree.addSelectionListener( + new SelectionAdapter() { +// public void widgetDefaultSelected(SelectionEvent e) { +// handleDefaultSelected(); +// } + public void widgetSelected(SelectionEvent e) { + feedInput(e.item); + } + } + ); + fEditionPane.setContent(fEditionTree); + + // now start the thread (and forget about it) + if (fThread != null) { + fThread.start(); + fThread= null; + } + + fContentPane= new CompareViewerSwitchingPane(vsplitter, SWT.BORDER | SWT.FLAT) { + protected Viewer getViewer(Viewer oldViewer, Object input) { + return CompareUI.findContentViewer(oldViewer, input, this, getCompareConfiguration()); + } + }; + vsplitter.setWeights(new int[] { 30, 70 }); + + statusLabel = new Label(parent, SWT.NONE); + statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + applyDialogFont(parent); + return parent; + } + + /* (non-Javadoc) + * Method declared on Dialog. + */ + protected void createButtonsForButtonBar(Composite parent) { + String buttonLabel= Utilities.getString(fBundle, "buttonLabel", IDialogConstants.OK_LABEL); //$NON-NLS-1$ + if (fCompareMode) { + // only a 'Done' button + createButton(parent, IDialogConstants.CANCEL_ID, buttonLabel, false); + } else { + // a 'Cancel' and a 'Add/Replace' button + fCommitButton= createButton(parent, IDialogConstants.OK_ID, buttonLabel, true); + fCommitButton.setEnabled(false); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + } + } + + /** + * Overidden to disable dismiss on double click in compare mode. + * @since 2.0 + */ + protected void okPressed() { + if (fCompareMode) { + // don't dismiss dialog + } else + super.okPressed(); + } + + //---- private stuff ---------------------------------------------------------------------------------------- + + /* + * Asynchroneously sends a Pair (or null) to the UI thread. + */ + private void sendPair(final Pair pair) { + if (fEditionTree != null && !fEditionTree.isDisposed()) { + Display display= fEditionTree.getDisplay(); + display.asyncExec( + new Runnable() { + public void run() { + addMemberEdition(pair); + } + } + ); + } + } + + private static void internalSort(IModificationDate[] keys) { + Arrays.sort(keys, new Comparator() { + public int compare(Object o1, Object o2) { + IModificationDate d1= (IModificationDate) o1; + IModificationDate d2= (IModificationDate) o2; + long d= d2.getModificationDate() - d1.getModificationDate(); + if (d < 0) + return -1; + if (d > 0) + return 1; + return 0; + } + }); + } + + /* + * Adds the given Pair to the member editions. + * If HIDE_IDENTICAL is true the new Pair is only added if its contents + * is different from the preceeding Pair. + * If the argument is <code>null</code> the message "No Editions found" is shown + * in the member or edition viewer. + */ + private void addMemberEdition(Pair pair) { + + if (pair == null) { // end of list of pairs + if (fMemberTable != null) { + if (!fMemberTable.isDisposed() && fMemberTable.getItemCount() == 0) { + if (fMultiSelect) { + fMemberTable.dispose(); + fMemberTable= new Table(fMemberPane, SWT.NONE); + fMemberPane.setContent(fMemberTable); + } + TableItem ti= new TableItem(fMemberTable, SWT.NONE); + ti.setText(Utilities.getString(fBundle, "noAdditionalMembersMessage")); //$NON-NLS-1$ + } + return; + } + if (fEditionTree != null && !fEditionTree.isDisposed() && fEditionTree.getItemCount() == 0) { + TreeItem ti= new TreeItem(fEditionTree, SWT.NONE); + ti.setText(Utilities.getString(fBundle, "notFoundInLocalHistoryMessage")); //$NON-NLS-1$ + } + return; + } + + if (fMemberEditions == null) + fMemberEditions= new HashMap(); + if (fMultiSelect && fMemberSelection == null) + fMemberSelection= new HashMap(); + + ITypedElement item= pair.getItem(); + List editions= (List) fMemberEditions.get(item); + if (editions == null) { + editions= new ArrayList(); + fMemberEditions.put(item, editions); + if (fMemberTable != null && !fMemberTable.isDisposed()) { + ITypedElement te= item; + String name= te.getName(); + + // find position + TableItem[] items= fMemberTable.getItems(); + int where= items.length; + for (int i= 0; i < where; i++) { + String n= items[i].getText(); + if (n.compareTo(name) > 0) { + where= i; + break; + } + } + + TableItem ti= new TableItem(fMemberTable, where, SWT.NULL); + ti.setImage(te.getImage()); + ti.setText(name); + ti.setData(editions); + } + } + if (fHideIdentical) { + Pair last= fTargetPair; + int size= editions.size(); + if (size > 0) + last= (Pair) editions.get(size-1); + if (last != null && last.equals(pair)) + return; // don't add since the new one is equal to old + } + editions.add(pair); + + if (!fAddMode || editions == fCurrentEditions) + addEdition(pair); + } + + /* + * Returns the number of s since Jan 1st, 1970. + * The given date is converted to GMT and daylight saving is taken into account too. + */ + private long dayNumber(long date) { + int ONE_DAY_MS= 24*60*60 * 1000; // one day in milli seconds + + Calendar calendar= Calendar.getInstance(); + long localTimeOffset= calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + + return (date + localTimeOffset) / ONE_DAY_MS; + } + + /* + * Adds the given Pair to the edition tree. + * It takes care of creating tree nodes for different dates. + */ + private void addEdition(Pair pair) { + if (fEditionTree == null || fEditionTree.isDisposed()) + return; + + // find last day + TreeItem[] days= fEditionTree.getItems(); + TreeItem lastDay= null; + if (days.length > 0) + lastDay= days[days.length-1]; + + boolean first= lastDay == null; + + ITypedElement edition= pair.getEdition(); + ITypedElement item= pair.getItem(); + + long ldate= ((IModificationDate)edition).getModificationDate(); + long day= dayNumber(ldate); + Date date= new Date(ldate); + if (lastDay == null || day != dayNumber(((Date)lastDay.getData()).getTime())) { + lastDay= new TreeItem(fEditionTree, SWT.NONE); + if (fDateImage == null) { + String iconName= Utilities.getString(fBundle, "dateIcon", "obj16/day_obj.gif"); //$NON-NLS-2$ //$NON-NLS-1$ + ImageDescriptor id= CompareUIPlugin.getImageDescriptor(iconName); + if (id != null) + fDateImage= id.createImage(); + } + lastDay.setImage(fDateImage); + String df= DateFormat.getDateInstance().format(date); + long today= dayNumber(System.currentTimeMillis()); + + String formatKey; + if (day == today) + formatKey= "todayFormat"; //$NON-NLS-1$ + else if (day == today-1) + formatKey= "yesterdayFormat"; //$NON-NLS-1$ + else + formatKey= "dayFormat"; //$NON-NLS-1$ + String pattern= Utilities.getString(fBundle, formatKey); + if (pattern != null) + df= MessageFormat.format(pattern, new String[] { df }); + lastDay.setText(df); + lastDay.setData(date); + } + TreeItem ti= new TreeItem(lastDay, SWT.NONE); + ti.setImage(getEditionImage(edition, item)); + + String s= getShortEditionLabel(edition, item, date); + if (pair.fHasError) { + String pattern= Utilities.getString(fBundle, "parseErrorFormat"); //$NON-NLS-1$ + s= MessageFormat.format(pattern, new String[] { s } ); + } + ti.setText(s); + + ti.setData(pair); + + // determine selected TreeItem + TreeItem selection= first ? ti : null; + if (fMemberSelection != null) { + Object selected= fMemberSelection.get(fCurrentEditions); + if (selected != null) { + if (selected == pair.getItem()) + selection= ti; + else + selection= null; + } + } + if (selection != null) { + fEditionTree.setSelection(new TreeItem[] { selection }); + if (!fAddMode) + fEditionTree.setFocus(); + feedInput(selection); + } + + if (first) // expand first node + lastDay.setExpanded(true); + } + + /* + * Feeds selection from member viewer to edition viewer. + */ + private void handleMemberSelect(Widget w) { + Object data= w.getData(); + if (data instanceof List) { + List editions= (List) data; + if (editions != fCurrentEditions) { + fCurrentEditions= editions; + fEditionTree.removeAll(); + + String pattern= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ + String title= MessageFormat.format(pattern, new Object[] { ((Item)w).getText() }); + fEditionPane.setText(title); + + Iterator iter= editions.iterator(); + while (iter.hasNext()) { + Object item= iter.next(); + if (item instanceof Pair) + addEdition((Pair) item); + } + } + } + } + + private void setInput(Object input) { + if (!fCompare && input instanceof ICompareInput) { + ICompareInput ci= (ICompareInput) input; + if (fTargetIsRight) + input= ci.getLeft(); + else + input= ci.getRight(); + } + fContentPane.setInput(input); + if (fStructuredComparePane != null) + fStructuredComparePane.setInput(input); + } + + /* + * Feeds selection from edition viewer to content (and structure) viewer. + */ + private void feedInput(Widget w) { + Object input= w.getData(); + boolean isOK= false; + if (input instanceof Pair) { + Pair pair= (Pair) input; + fSelectedItem= pair.getItem(); + isOK= !pair.fHasError; + + ITypedElement edition= pair.getEdition(); + String editionLabel= getEditionLabel(edition, fSelectedItem); + Image editionImage= getEditionImage(edition, fSelectedItem); + + if (fAddMode) { + if (fMemberSelection != null) + fMemberSelection.put(fCurrentEditions, fSelectedItem); + setInput(fSelectedItem); + fContentPane.setText(editionLabel); + fContentPane.setImage(editionImage); + } else { + getCompareConfiguration(); + if (fTargetIsRight) { + fCompareConfiguration.setLeftLabel(editionLabel); + fCompareConfiguration.setLeftImage(editionImage); + setInput(new DiffNode(fSelectedItem, fTargetPair.getItem())); + } else { + fCompareConfiguration.setRightLabel(editionLabel); + fCompareConfiguration.setRightImage(editionImage); + setInput(new DiffNode(fTargetPair.getItem(), fSelectedItem)); + } + } + } else { + fSelectedItem= null; + setInput(null); + } + if (fCommitButton != null) { + if (fMultiSelect) + fCommitButton.setEnabled(isOK && fSelectedItem != null && fArrayList.size() > 0); + else + fCommitButton.setEnabled(isOK && fSelectedItem != null && fTargetPair.getItem() != fSelectedItem); + } + } + + /* + * Feeds selection from structure viewer to content viewer. + */ + private void feedInput2(ISelection sel) { + if (sel instanceof IStructuredSelection) { + IStructuredSelection ss= (IStructuredSelection) sel; + if (ss.size() == 1) + fContentPane.setInput(ss.getFirstElement()); + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/HistoryItem.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/HistoryItem.java new file mode 100644 index 000000000..e35243516 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/HistoryItem.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare; + +import java.io.InputStream; +import java.io.BufferedInputStream; + +import org.eclipse.swt.graphics.Image; + +import org.eclipse.compare.IResourceProvider; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IFileState; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +/** + * A combination <code>IFileState</code> and <code>ITypedElement</code> that can be used as + * an input to a compare viewer or other places where an <code>IStreamContentAccessor</code> + * is needed. + * <p> + * <p> + * Clients may instantiate this class; it is not intended to be subclassed. + * </p> + * @noextend This class is not intended to be subclassed by clients. + */ +public class HistoryItem implements IEncodedStreamContentAccessor, ITypedElement, IModificationDate, IResourceProvider { + + private ITypedElement fBase; + private IFileState fFileState; + + /** + * Creates a <code>HistoryItem</code> object which combines the given <code>IFileState</code> + * and <code>ITypedElement</code> into an object + * which is suitable as input for a compare viewer or <code>ReplaceWithEditionDialog</code>. + * + * @param base the implementation of the <code>ITypedElement</code> interface delegates to this base <code>ITypedElement</code> + * @param fileState the <code>IFileState</code> from which the streamable contents and the modification time is derived from + */ + public HistoryItem(ITypedElement base, IFileState fileState) { + fBase= base; + fFileState= fileState; + } + + /* (non-Javadoc) + * see ITypedElement.getName + */ + public String getName() { + return fBase.getName(); + } + + /* (non-Javadoc) + * see ITypedElement.getImage + */ + public Image getImage() { + return fBase.getImage(); + } + + /* (non-Javadoc) + * see ITypedElement.getType + */ + public String getType() { + return fBase.getType(); + } + + /* (non-Javadoc) + * see IModificationDate.getModificationDate + */ + public long getModificationDate() { + return fFileState.getModificationTime(); + } + + /* (non-Javadoc) + * see IStreamContentAccessor.getContents + */ + public InputStream getContents() throws CoreException { + return new BufferedInputStream(fFileState.getContents()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEncodedStreamContentAccessor#getCharset() + */ + public String getCharset() throws CoreException { + String charset= fFileState.getCharset(); + if (charset == null) { + IResource resource= getResource(); + if (resource instanceof IEncodedStorage) + charset= ((IEncodedStorage)resource).getCharset(); + } + return charset; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.IResourceProvider#getResource() + */ + public IResource getResource() { + IPath fullPath= fFileState.getFullPath(); + return ResourcesPlugin.getWorkspace().getRoot().findMember(fullPath); + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareContainer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareContainer.java new file mode 100644 index 000000000..d80262c0c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareContainer.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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 org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ui.*; +import org.eclipse.ui.services.IServiceLocator; + +/** + * A compare container is used to represent any UI that can contain compare viewers. + * <p> + * This interface is not intended to be implemented by clients. + * </p> + * @since 3.3 + */ +public interface ICompareContainer extends IRunnableContext{ + + /** + * Register for change events for the given compare input. Although clients can register + * with the compare input directly, registering through the container allows for + * deterministic and optimized behavior in some cases. Registering multiple times for the + * same compare input has no effect. + * @param input the compare input + * @param listener the compare input change listener + */ + public void addCompareInputChangeListener(ICompareInput input, ICompareInputChangeListener listener); + + /** + * Remove the change listener from the given compare input. Removing a listener that is not + * registered has no effect. + * @param input the compare input + * @param listener the compare input change listener + */ + public void removeCompareInputChangeListener(ICompareInput input, ICompareInputChangeListener listener); + + /** + * Register the content menu with the container to give the container a chance to + * add additional items to the context menu such as popup menu object contributions. + * The provided menu should have a {@link IWorkbenchActionConstants#MB_ADDITIONS} + * separator as this is where the container will add actions. + * @param menu the menu being registered + * @param selectionProvider the selection provider + */ + public void registerContextMenu(MenuManager menu, ISelectionProvider selectionProvider); + + /** + * Set the status message displayed by the container to the given message + * @param message the status message + */ + public void setStatusMessage(String message); + + /** + * Return the action bars for the container or <code>null</code> if the container + * does not have an action bars. + * @return the action bars for the container or <code>null</code> + */ + public IActionBars getActionBars(); + + /** + * Return the service locator for the container or <code>null</code> if the container + * does not have one. + * @return the service locator for the container or <code>null</code> + */ + public IServiceLocator getServiceLocator(); + + /** + * Return the {@link ICompareNavigator} associated with this container or <code>null</code> + * if the container does not have a global navigator. + * @return the {@link ICompareNavigator} associated with this container or <code>null</code> + */ + public ICompareNavigator getNavigator(); + + /** + * Queue the given task to be run asynchronously. If the given runnable was + * previously queued to run asynchronously and it has not yet run, the task + * position will be moved to the end of the queue. If the task that is being + * queued is currently running, the running task will be canceled and added + * to the end of the queue. + * <p> + * This method should be treated as a request to run the given task asynchronously. + * However, clients should not assume that the code will be run asynchronously. + * Depending on the container implementation, a call to this method may or may + * not block the caller until the task is completed. Also, the task may be executed + * in a modal or non-modal fashion. + * + * @param runnable the task to be performed + */ + public void runAsynchronously(IRunnableWithProgress runnable); + + /** + * Return the workbench part associated with this container or + * <code>null</code> if there is no part or it is not available. + * @return the workbench part associated with this container or + * <code>null</code> + */ + public IWorkbenchPart getWorkbenchPart(); + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareInputLabelProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareInputLabelProvider.java new file mode 100644 index 000000000..4d74b744e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareInputLabelProvider.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2006 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 org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.graphics.Image; + +/** + * A label provider that provides the label and image for the left, right and + * ancestor sides for a compare input being shown in compare/merge viewers. + * <p> + * This interface may be implemented by clients. + * + * @since 3.3 + */ +public interface ICompareInputLabelProvider extends ILabelProvider { + + /** + * Returns the label for the ancestor side of compare/merge viewers. + * This label is typically shown in the title of the ancestor area in a compare viewer. + * + * @param input the input object of a compare/merge viewer or <code>null</code> + * @return the label for the ancestor side or <code>null</code> + */ + String getAncestorLabel(Object input); + + /** + * Returns the image for the ancestor side of compare/merge viewers. + * This image is typically shown in the title of the ancestor area in a compare viewer. + * + * @param input the input object of a compare/merge viewer or <code>null</code> + * @return the image for the ancestor side or <code>null</code> + */ + Image getAncestorImage(Object input); + + /** + * Returns the label for the left hand side of compare/merge viewers. + * This label is typically shown in the title of the left side of a compare viewer. + * + * @param input the input object of a compare/merge viewer or <code>null</code> + * @return the label for the left hand side or <code>null</code> + */ + String getLeftLabel(Object input); + + /** + * Returns the image for the left hand side of compare/merge viewers. + * This image is typically shown in the title of the left side of a compare viewer. + * + * @param input the input object of a compare/merge viewer or <code>null</code> + * @return the image for the left hand side or <code>null</code> + */ + Image getLeftImage(Object input); + + /** + * Returns the label for the right hand side of compare/merge viewers. + * This label is typically shown in the title of the right side of a compare viewer. + * + * @param input the input object of a compare/merge viewer or <code>null</code> + * @return the label for the right hand side or <code>null</code> + */ + String getRightLabel(Object input); + + /** + * Returns the image for the right hand side of compare/merge viewers. + * This image is typically shown in the title of the right side of a compare viewer. + * + * @param input the input object of a compare/merge viewer or <code>null</code> + * @return the image for the right hand side or <code>null</code> + */ + Image getRightImage(Object input); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareNavigator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareNavigator.java new file mode 100644 index 000000000..f0105f6e2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareNavigator.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 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; + +/** + * A <code>ICompareNavigator</code> is used to navigate through the individual + * differences of a <code>CompareEditorInput</code> or another type of Compare container. + * <p> + * You can retrieve an object implementing the <code>ICompareNavigator</code> from a + * <code>CompareEditorInput</code> by calling <code>getAdapter(ICompareNavigator)</code> + * on the <code>CompareEditorInput</code>. + * </p> + * <p> + * Although it is legal for clients to implement this interface, it is better + * to subclass {@link CompareNavigator}. + * + * @since 3.0 + */ +public interface ICompareNavigator { + + /** + * Starting from the current selection <code>selectChange</code> selects and reveals the next (previous) change. + * If the end (or beginning) is reached, the method returns <code>true</code>. + * + * @param next if <code>true</code> the next change is selected, otherwise the previous change + * @return returns <code>true</code> if end (beginning) is reached, <code>false</code> otherwise + */ + public boolean selectChange(boolean next); + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeListener.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeListener.java new file mode 100644 index 000000000..656eab5bc --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeListener.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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; + +/** + * An <code>IContentChangeListener</code> is informed about content changes of a + * <code>IContentChangeNotifier</code>. + * <p> + * Clients may implement this interface. + * </p> + * + * @see IContentChangeNotifier + */ +public interface IContentChangeListener { + + /** + * Called whenever the content of the given source has changed. + * + * @param source the source whose contents has changed + */ + void contentChanged(IContentChangeNotifier source); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeNotifier.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeNotifier.java new file mode 100644 index 000000000..d460a1a3f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeNotifier.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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; + +/** + * Interface common to all objects that provide a means for registering + * for content change notification. + * <p> + * Clients may implement this interface. + * </p> + * + * @see IContentChangeListener + */ +public interface IContentChangeNotifier { + + /** + * Adds a content change listener to this notifier. + * Has no effect if an identical listener is already registered. + * + * @param listener a content changed listener + */ + void addContentChangeListener(IContentChangeListener listener); + + /** + * Removes the given content changed listener from this notifier. + * Has no effect if the listener is not registered. + * + * @param listener a content changed listener + */ + void removeContentChangeListener(IContentChangeListener listener); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContent.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContent.java new file mode 100644 index 000000000..d8a88a75a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContent.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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; + +/** + * Common interface for objects with editable contents. + * Typically it is implemented by objects that also implement + * the <code>IStreamContentAccessor</code> interface. + * <p> + * Clients may implement this interface. + * <p> + * Note that implementing <code>IEditableContent</code> does not + * automatically mean that it is editable. An object is only editable if + * it implements <code>IEditableContent</code> and the <code>isEditable</code> method returns <code>true</code>. + * + * @see IStreamContentAccessor + */ +public interface IEditableContent { + + /** + * Returns <code>true</code> if this object can be modified. + * If it returns <code>false</code> the other methods of this API must not be called. + * + * @return <code>true</code> if this object can be modified + */ + boolean isEditable(); + + /** + * Replaces the current content with the given new bytes. + * + * @param newContent this new contents replaces the old contents + */ + void setContent(byte[] newContent); + + /** + * This method is called on a parent to add or remove a child, + * or to copy the contents of a child. + * + * What to do is encoded in the two arguments as follows: + * <TABLE> + * <TR> + * <TD>add:</TD> + * <TD>dest == null</TD> + * <TD>src != null</TD> + * </TR> + * <TR> + * <TD>remove:</TD> + * <TD>dest != null</TD> + * <TD>src == null</TD> + * </TR> + * <TR> + * <TD>copy:</TD> + * <TD>dest != null</TD> + * <TD>src != null</TD> + * </TR> + * </TABLE> + * @param dest the existing child of this object to be replaced; if <code>null</code> a new child can be added. + * @param src the new child to be added or replaced; if <code>null</code> an existing child can be removed. + * @return the argument <code>dest</code> + */ + ITypedElement replace(ITypedElement dest, ITypedElement src); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContentExtension.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContentExtension.java new file mode 100644 index 000000000..a5c605f86 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContentExtension.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2006 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 org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.texteditor.IDocumentProviderExtension; + +/** + * Extends the {@link IEditableContent} interface to support validate edit. + * Clients should only use this interface if they obtained the content + * from an {@link IStreamContentAccessor}. If content was obtained through an + * {@link ISharedDocumentAdapter} then validation should be performed through + * the {@link IDocumentProviderExtension} interface. + * @see IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], Object) + * @since 3.3 + */ +public interface IEditableContentExtension { + + /** + * Return whether the typed element being displayed + * is read-only. a read-only element will require a + * call to validateEdit before the element can be modified on disk. + * @return whether the typed element is read-only + */ + boolean isReadOnly(); + + /** + * If the element is read-only, this method should be called + * to attempt to make it writable. + * @param shell a shell used to prompt the user if required. + * @return a status object that is <code>OK</code> if things are fine, + * otherwise a status describing reasons why modifying the given files is not + * reasonable. A status with a severity of <code>CANCEL</code> is returned + * if the validation was canceled, indicating the edit should not proceed. + */ + IStatus validateEdit(Shell shell); + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEncodedStreamContentAccessor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEncodedStreamContentAccessor.java new file mode 100644 index 000000000..8ae157fba --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IEncodedStreamContentAccessor.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 org.eclipse.core.runtime.CoreException; + +/** + * Extension for <code>IStreamContentAccessor</code>. Extends the original + * concept of a <code>IStreamContentAccessor</code> to answer the Charset (encoding) used for the stream. + * + * @since 3.0 + */ +public interface IEncodedStreamContentAccessor extends IStreamContentAccessor { + + /** + * Returns the name of a charset encoding to be used when decoding this + * stream accessor's contents into characters. Returns <code>null</code> if a proper + * encoding cannot be determined. + * <p> + * <b>Note</b>: this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * <code>UnsupportedEncodingException</code> where this charset is used. + * </p> + * @return the name of a charset, or <code>null</code> + * @exception CoreException if an error happens while determining + * the charset. See any refinements for more information. + * @see IStreamContentAccessor#getContents + * @since 3.0 + */ + String getCharset() throws CoreException; +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IModificationDate.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IModificationDate.java new file mode 100644 index 000000000..f55c92374 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IModificationDate.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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; + +/** + * Common interface for objects with a modification date. The modification date + * can be used in the UI to give the user a general idea of how old an object is. + * <p> + * Clients may implement this interface. + * </p> + */ +public interface IModificationDate { + + /** + * Returns the modification time of this object. + * <p> + * Note that this value should only be used to give the user a general idea of how + * old the object is. + * + * @return the time of last modification, in milliseconds since January 1, 1970, 00:00:00 GMT + */ + long getModificationDate(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/INavigatable.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/INavigatable.java new file mode 100644 index 000000000..8cfe6bd83 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/INavigatable.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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 org.eclipse.swt.widgets.Widget; + +/** + * Interface that allow clients to navigate through the changes shown in a compare pane. + * <p> + * This interface may be implemented by clients. + * </p> + * @since 3.3 + * @see ICompareNavigator + */ +public interface INavigatable { + + /** + * Property key that can be used to associate an instance of this interface with + * an SWT widget using {@link Widget#setData(String, Object)}. + */ + static final String NAVIGATOR_PROPERTY= "org.eclipse.compare.internal.Navigator"; //$NON-NLS-1$ + + /** + * Change flag used to navigate to the next change. + * @see #selectChange(int) + */ + static final int NEXT_CHANGE= 1; + + /** + * Change flag used to navigate to the previous change. + * @see #selectChange(int) + */ + static final int PREVIOUS_CHANGE= 2; + + /** + * Change flag used to navigate to the first change. + * @see #selectChange(int) + */ + static final int FIRST_CHANGE= 3; + + /** + * Change flag used to navigate to the last change. + * @see #selectChange(int) + */ + static final int LAST_CHANGE= 4; + + /** + * Return the input of the compare pane being navigated or <code>null</code> + * if the pane does not have an input. + * @return the input of the compare pane being navigated or <code>null</code> + */ + Object getInput(); + + /** + * Starting from the current selection <code>selectChange</code> selects and reveals the specified change. + * If the end (or beginning) is reached, the method returns <code>true</code>. + * + * @param changeFlag the change to be selected. One of <code>NEXT_CHANGE</code>, <code>PREVIOUS_CHANGE</code>, + * <code>FIRST_CHANGE</code> or <code>LAST_CHANGE</code>. + * @return returns <code>true</code> if end (beginning) is reached, <code>false</code> otherwise + */ + boolean selectChange(int changeFlag); + + /** + * Return whether a call to {@link #selectChange(int)} with the same parameter + * would succeed. + * @param changeFlag the change to be selected. One of <code>NEXT_CHANGE</code> or <code>PREVIOUS_CHANGE</code> + * @return whether a call to {@link #selectChange(int)} with the same parameter + * would succeed. + */ + boolean hasChange(int changeFlag); + + /** + * Request that the currently selected change be opened. Return <code>true</code> + * if the request resulted in the change being opened and <code>false</code> if the + * currently selected change could not be opened. + * @return whether the selected change was opened. + */ + boolean openSelectedChange(); + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IPropertyChangeNotifier.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IPropertyChangeNotifier.java new file mode 100644 index 000000000..52ad2d276 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IPropertyChangeNotifier.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 org.eclipse.jface.util.IPropertyChangeListener; + +/** + * Interface common to all objects that provide a means for registering + * for property change notification. + * <p> + * Clients may implement this interface. + * </p> + * + * @see org.eclipse.jface.util.IPropertyChangeListener + */ +public interface IPropertyChangeNotifier { + + /** + * Adds a listener for property changes to this notifier. + * Has no effect if an identical listener is already registered. + * + * @param listener a property change listener + */ + void addPropertyChangeListener(IPropertyChangeListener listener); + + /** + * Removes the given content change listener from this notifier. + * Has no effect if the identical listener is not registered. + * + * @param listener a property change listener + */ + void removePropertyChangeListener(IPropertyChangeListener listener); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IResourceProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IResourceProvider.java new file mode 100644 index 000000000..87f801db2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IResourceProvider.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2005 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 org.eclipse.core.resources.IResource; + +/** + * @since 3.1 + */ +public interface IResourceProvider { + + /** + * Returns the corresponding resource for this object or <code>null</code>. + * + * @return the corresponding resource or <code>null</code> + */ + IResource getResource(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ISharedDocumentAdapter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ISharedDocumentAdapter.java new file mode 100644 index 000000000..e1b10d930 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ISharedDocumentAdapter.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare; + +import org.eclipse.compare.structuremergeviewer.SharedDocumentAdapterWrapper; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.texteditor.IDocumentProvider; + +/** + * An <code>ISharedDocumentAdapter</code> is used to map an + * {@link ITypedElement} to a shared document for the purposes of editing. + * + * @noimplement Clients are not expected to implement this interface but instead + * should subclass {@link SharedDocumentAdapter} or + * {@link SharedDocumentAdapterWrapper}. + * @since 3.3 + */ +public interface ISharedDocumentAdapter { + + /** + * Return the object that is to be used as the key for retrieving the + * appropriate {@link IDocumentProvider} from the + * <code>DocumentProviderRegistry</code> and for obtaining the shared + * {@link IDocument} from the document provider. Returns <code>null</code> + * if the element does not have a shared document. + * + * @param element + * the element being queried for a shared document + * @return the object that acts as the key to obtain a document provider and + * document or <code>null</code> + */ + IEditorInput getDocumentKey(Object element); + + /** + * Connect the given element to its document provider. All connections must be performed + * through this adapter so that the adapter can track whether it is connected or not. + * @param provider the document provider + * @param documentKey the element's key returned from {@link #getDocumentKey(Object)} + * @throws CoreException if connection was not possible + * @see IDocumentProvider#connect(Object) + */ + void connect(IDocumentProvider provider, IEditorInput documentKey) throws CoreException; + + /** + * Disconnect the element from the document provider. All connects and + * disconnects must occur through the adapter so that the adapter can + * track whether it is connected or not. + * @param provider the document provider + * @param documentKey the element's key returned from {@link #getDocumentKey(Object)} + * @see IDocumentProvider#disconnect(Object) + */ + void disconnect(IDocumentProvider provider, IEditorInput documentKey); + + /** + * A helper disconnect method that looks up the appropriate key (using {@link #getDocumentKey(Object)} + * and the appropriate provider and calls {@link #disconnect(IDocumentProvider, IEditorInput)}. + * @param element the element that was used to previously connect to a document + * @see IDocumentProvider#disconnect(Object) + */ + void disconnect(Object element); + + /** + * Flush the contents of the given document into the typed element that provided the + * document. This method is invoked by the Compare framework classes + * when a request to flush the viewers has been made. It is up to the implementor to decide + * whether the changes in the buffer should be saved to disk at the time of the flush or + * buffered to be saved at a later time. + * + * @param provider the document provider + * @param documentKey the element's key returned from {@link #getDocumentKey(Object)} + * @param document the document + * @param overwrite indicates whether overwrite should be performed + * while saving the given element if necessary + * @exception CoreException if document could not be stored to the given element + * @see IDocumentProvider#saveDocument(IProgressMonitor, Object, IDocument, boolean) + */ + void flushDocument(IDocumentProvider provider, IEditorInput documentKey, IDocument document, boolean overwrite) throws CoreException; + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamContentAccessor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamContentAccessor.java new file mode 100644 index 000000000..04f25e9db --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamContentAccessor.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.io.InputStream; + +import org.eclipse.core.runtime.CoreException; + +/** + * An <code>IStreamContentAccessor</code> object represents a set of bytes which can be + * accessed by means of a stream. + * <p> + * Clients may implement this interface, or use the standard implementation, + * <code>BufferedContent</code>. + * + * @see BufferedContent + */ +public interface IStreamContentAccessor { + /** + * Returns an open <code>InputStream</code> for this object which can be used to retrieve the object's content. + * The client is responsible for closing the stream when finished. + * Returns <code>null</code> if this object has no streamable contents. + * + * @return an input stream containing the contents of this object + * @exception CoreException if the contents of this object could not be accessed + */ + InputStream getContents() throws CoreException; +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamMerger.java new file mode 100644 index 000000000..f5b6abebd --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamMerger.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; + +/** + * This interface defines a single operation for performing a three-way merge on three + * input streams. The merged result is written to an output stream. + * <p> + * Clients must implement this interface when contributing new mergers to the + * <code>org.eclipse.compare.streamMergers</code> extension point. + * </p> + * @deprecated Clients should use <code>org.eclipse.team.core.mapping.IStorageMerger</code> instead. + * @since 3.0 + */ +public interface IStreamMerger { + + /** + * Indicates the successful completion of the merge operation (value <code>IStatus.OK</code>) + */ + public static final int OK= IStatus.OK; + + /** + * Indicates that a change conflict prevented the merge from successful completion (value <code>1</code>) + */ + public static final int CONFLICT= 1; + + /** + * Status code describing an internal error (value <code>2</code>) + */ + public static final int INTERNAL_ERROR= 2; + + /** + * Performs a merge operation on the given input streams and writes the merge result to the output stream. + * On success a status <code>IStatus.OK</code> is returned, on error a status <code>IStatus.ERROR</code>. + * If the merge operation cannot deal with conflicts, the code of the error status has the value <code>IStreamMerger.CONFLICT</code>. + * For text oriented mergers the encoding for the input and output streams is honored. + * It is the responsibility of callers to close input and output streams. + * + * @param output the byte stream to which the merge result is written; the merger will not close the stream + * @param outputEncoding the encoding to use when writing to the output stream + * @param ancestor the byte stream from which the common ancestor is read + * @param ancestorEncoding the encoding of the ancestor input byte stream + * @param target the byte stream containing the target of the merge + * @param targetEncoding the encoding of the target input byte stream + * @param other the byte stream containing the target of the merge + * @param otherEncoding the encoding of the other input byte stream + * @param monitor reports progress of the merge operation + * @return returns the completion status of the operation + */ + IStatus merge(OutputStream output, String outputEncoding, + InputStream ancestor, String ancestorEncoding, + InputStream target, String targetEncoding, + InputStream other, String otherEncoding, + IProgressMonitor monitor); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ITypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ITypedElement.java new file mode 100644 index 000000000..27c0b2752 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ITypedElement.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 org.eclipse.swt.graphics.Image; + +/** + * Interface for getting the name, image, and type for an object. + * <p> + * These methods are typically used to present an input object in the compare UI + * (<code>getName</code> and <code>getImage</code>) + * and for finding a viewer for a given input type (<code>getType</code>). + * <p> + * Clients may implement this interface. + */ +public interface ITypedElement { + + /** + * Type for a folder input (value <code>"FOLDER"</code>). + * Folders are comparison elements that have no contents, only a name and children. + */ + public static final String FOLDER_TYPE= "FOLDER"; //$NON-NLS-1$ + + /** + * Type for an element whose actual type is text (value <code>"txt"</code>). + */ + public static final String TEXT_TYPE= "txt"; //$NON-NLS-1$ + + /** + * Type for an element whose actual type could not + * be determined. (value <code>"???"</code>). + */ + public static final String UNKNOWN_TYPE= "???"; //$NON-NLS-1$ + + /** + * Returns the name of this object. + * The name is used when displaying this object in the UI. + * + * @return the name of this object + */ + String getName(); + + /** + * Returns an image for this object. + * This image is used when displaying this object in the UI. + * + * @return the image of this object or <code>null</code> if this type of input has no image + */ + Image getImage(); + + /** + * Returns the type of this object. For objects with a file name + * this is typically the file extension. For folders its the constant + * <code>FOLDER_TYPE</code>. + * The type is used for determining a suitable viewer for this object. + * + * @return the type of this object + */ + String getType(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/IViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IViewerCreator.java new file mode 100644 index 000000000..2626ec85b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/IViewerCreator.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; + +/** + * A factory object for <code>Viewer</code>. + * <p> + * This interface is only required when creating a <code>Viewer</code> from a plugin.xml file. + * Since <code>Viewer</code>s have no default constructor they cannot be + * instantiated directly with <code>Class.forName</code>. + */ +public interface IViewerCreator { + + /** + * Creates a new viewer under the given SWT parent control. + * + * @param parent the SWT parent control under which to create the viewer's SWT control + * @param config a compare configuration the newly created viewer might want to use + * @return a new viewer + */ + Viewer createViewer(Composite parent, CompareConfiguration config); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/NavigationAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/NavigationAction.java new file mode 100644 index 000000000..81236f85f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/NavigationAction.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare; + +import java.util.ResourceBundle; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.Utilities; + + +/** + * A <code>NavigationAction</code> is used to navigate through the individual + * differences of a <code>CompareEditorInput</code>. + * <p> + * Clients may instantiate this class; it is not intended to be subclassed. + * </p> + * @since 2.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public class NavigationAction extends Action { + + private boolean fNext; + private CompareEditorInput fCompareEditorInput; + + + /** + * Creates a <code>NavigationAction</code>. + * + * @param next if <code>true</code> action goes to the next difference; otherwise to the previous difference. + */ + public NavigationAction(boolean next) { + this(CompareUI.getResourceBundle(), next); + } + + /** + * Creates a <code>NavigationAction</code> that initializes its attributes + * from the given <code>ResourceBundle</code>. + * + * @param bundle is used to initialize the action + * @param next if <code>true</code> action goes to the next difference; otherwise to the previous difference. + */ + public NavigationAction(ResourceBundle bundle, boolean next) { + Utilities.initAction(this, bundle, next ? "action.Next." : "action.Previous."); //$NON-NLS-2$ //$NON-NLS-1$ + fNext= next; + } + + public void run() { + if (fCompareEditorInput != null) { + Object adapter= fCompareEditorInput.getAdapter(ICompareNavigator.class); + if (adapter instanceof ICompareNavigator) { + boolean atEnd= ((ICompareNavigator)adapter).selectChange(fNext); + Shell shell= CompareUIPlugin.getShell(); + if (atEnd && shell != null) { + + Display display= shell.getDisplay(); + if (display != null) + display.beep(); + + String title; + String message; + if (fNext) { + title= CompareMessages.CompareNavigator_atEnd_title; + message= CompareMessages.CompareNavigator_atEnd_message; + } else { + title= CompareMessages.CompareNavigator_atBeginning_title; + message= CompareMessages.CompareNavigator_atBeginning_message; + } + MessageDialog.openInformation(shell, title, message); + } + } + } + } + + /** + * Sets the <code>CompareEditorInput</code> on which this action operates. + * + * @param input the <code>CompareEditorInput</code> on which this action operates; if <code>null</code> action does nothing + */ + public void setCompareEditorInput(CompareEditorInput input) { + fCompareEditorInput= input; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ResourceNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ResourceNode.java new file mode 100644 index 000000000..864424807 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ResourceNode.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare; + +import java.io.*; +import java.util.ArrayList; + +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Shell; + +/** + * A <code>ResourceNode</code> wraps an <code>IResources</code> so that it can be used + * as input for the differencing engine (interfaces <code>IStructureComparator</code> and <code>ITypedElement</code>) + * and the <code>ReplaceWithEditionDialog</code> (interfaces <code>ITypedElement</code> and <code>IModificationDate</code>). + * <p> + * Clients may instantiate this class; it is not intended to be subclassed. + * </p> + * @noextend This class is not intended to be subclassed by clients. + */ +public class ResourceNode extends BufferedContent + implements IEncodedStreamContentAccessor, IStructureComparator, ITypedElement, + IEditableContent, IModificationDate, IResourceProvider, IEditableContentExtension { + + private IResource fResource; + private ArrayList fChildren; + + + /** + * Creates a <code>ResourceNode</code> for the given resource. + * + * @param resource the resource + */ + public ResourceNode(IResource resource) { + fResource= resource; + Assert.isNotNull(resource); + } + + /** + * Returns the corresponding resource for this object. + * + * @return the corresponding resource + */ + public IResource getResource() { + return fResource; + } + + /* (non Javadoc) + * see IStreamContentAccessor.getContents + */ + public InputStream getContents() throws CoreException { + if (fResource instanceof IStorage) + return super.getContents(); + return null; + } + + /* (non Javadoc) + * see IModificationDate.getModificationDate + */ + public long getModificationDate() { + return fResource.getLocalTimeStamp(); + } + + /* (non Javadoc) + * see ITypedElement.getName + */ + public String getName() { + if (fResource != null) + return fResource.getName(); + return null; + } + + /* (non Javadoc) + * see ITypedElement.getType + */ + public String getType() { + if (fResource instanceof IContainer) + return ITypedElement.FOLDER_TYPE; + if (fResource != null) { + String s= fResource.getFileExtension(); + if (s != null) + return s; + } + return ITypedElement.UNKNOWN_TYPE; + } + + /* (non Javadoc) + * see ITypedElement.getImage + */ + public Image getImage() { + return CompareUI.getImage(fResource); + } + + /* + * Returns <code>true</code> if the other object is of type <code>ITypedElement</code> + * and their names are identical. The content is not considered. + */ + public boolean equals(Object other) { + if (other instanceof ITypedElement) { + String otherName= ((ITypedElement)other).getName(); + return getName().equals(otherName); + } + return super.equals(other); + } + + /** + * Returns the hash code of the name. + * @return a hash code value for this object. + */ + public int hashCode() { + return getName().hashCode(); + } + + /* (non Javadoc) + * see IStructureComparator.getChildren + */ + public Object[] getChildren() { + if (fChildren == null) { + fChildren= new ArrayList(); + if (fResource instanceof IContainer) { + try { + IResource members[]= ((IContainer)fResource).members(); + for (int i= 0; i < members.length; i++) { + IStructureComparator child= createChild(members[i]); + if (child != null) + fChildren.add(child); + } + } catch (CoreException ex) { + // NeedWork + } + } + } + return fChildren.toArray(); + } + + /** + * This hook method is called from <code>getChildren</code> once for every + * member of a container resource. This implementation + * creates a new <code>ResourceNode</code> for the given child resource. + * Clients may override this method to create a different type of + * <code>IStructureComparator</code> or to filter children by returning <code>null</code>. + * + * @param child the child resource for which a <code>IStructureComparator</code> must be returned + * @return a <code>ResourceNode</code> for the given child or <code>null</code> + */ + protected IStructureComparator createChild(IResource child) { + return new ResourceNode(child); + } + + /** + * Returns an open stream if the corresponding resource implements the + * <code>IStorage</code> interface. Otherwise the value <code>null</code> is returned. + * + * @return a buffered input stream containing the contents of this storage + * @exception CoreException if the contents of this storage could not be accessed + */ + protected InputStream createStream() throws CoreException { + if (fResource instanceof IStorage) { + InputStream is= null; + IStorage storage= (IStorage) fResource; + try { + is= storage.getContents(); + } catch (CoreException e) { + if (e.getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) { + fResource.refreshLocal(IResource.DEPTH_INFINITE, null); + is= storage.getContents(); + } else + throw e; + } + if (is != null) + return new BufferedInputStream(is); + } + return null; + } + + /* (non Javadoc) + * see IEditableContent.isEditable + */ + public boolean isEditable() { + return true; + } + + /* (non Javadoc) + * see IEditableContent.replace + */ + public ITypedElement replace(ITypedElement child, ITypedElement other) { + return child; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEncodedStreamContentAccessor#getCharset() + */ + public String getCharset() { + return Utilities.getCharset(fResource); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContentExtension#isReadOnly() + */ + public boolean isReadOnly() { + if (fResource.getType() == IResource.FILE) { + ResourceAttributes attrs = fResource.getResourceAttributes(); + if (attrs != null) { + return attrs.isReadOnly(); + } + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContentExtension#validateEdit(org.eclipse.swt.widgets.Shell) + */ + public IStatus validateEdit(Shell shell) { + if (isReadOnly()) + return ResourcesPlugin.getWorkspace().validateEdit(new IFile[] { (IFile)fResource}, shell); + return Status.OK_STATUS; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/SharedDocumentAdapter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/SharedDocumentAdapter.java new file mode 100644 index 000000000..cfae7ec7c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/SharedDocumentAdapter.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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 org.eclipse.compare.internal.Utilities; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.texteditor.DocumentProviderRegistry; +import org.eclipse.ui.texteditor.IDocumentProvider; + +/** + * An implementation of {@link ISharedDocumentAdapter} that provides default behavior for the + * methods of that interface. + * <p> + * Clients may subclass this class. + * </p> + * @since 3.3 + */ +public abstract class SharedDocumentAdapter implements ISharedDocumentAdapter { + + /** + * Return the document provider for the given editor input. + * @param input the editor input + * @return the document provider for the given editor input + */ + public static IDocumentProvider getDocumentProvider(IEditorInput input) { + return DocumentProviderRegistry.getDefault().getDocumentProvider(input); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#connect(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput) + */ + public void connect(IDocumentProvider provider, IEditorInput documentKey) + throws CoreException { + provider.connect(documentKey); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#disconnect(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput) + */ + public void disconnect(IDocumentProvider provider, IEditorInput documentKey) { + provider.disconnect(documentKey); + } + + /** + * Default implementation of {@link #getDocumentKey(Object)} that returns a + * {@link FileEditorInput} for the element if the element adapts to {@link IFile}. + * @see org.eclipse.compare.ISharedDocumentAdapter#getDocumentKey(java.lang.Object) + */ + public IEditorInput getDocumentKey(Object element) { + IFile file = getFile(element); + if (file != null && file.exists()) { + return new FileEditorInput(file); + } + return null; + } + + private IFile getFile(Object element) { + if (element instanceof IResourceProvider) { + IResourceProvider rp = (IResourceProvider) element; + IResource resource = rp.getResource(); + if (resource instanceof IFile) { + return (IFile) resource; + } + } + IFile file = (IFile)Utilities.getAdapter(element, IFile.class); + if (file != null) { + return file; + } + IResource resource = (IResource)Utilities.getAdapter(element, IResource.class); + if (resource instanceof IFile) { + return (IFile) resource; + } + return null; + } + + /** + * A helper method to save a document. + * + * @param provider the document provider + * @param documentKey the document key + * @param document the document + * @param overwrite indicates whether overwrite should be performed + * while saving the given element if necessary + * @param monitor a progress monitor + * @throws CoreException + */ + protected void saveDocument(IDocumentProvider provider, + IEditorInput documentKey, IDocument document, boolean overwrite, + IProgressMonitor monitor) throws CoreException { + try { + provider.aboutToChange(documentKey); + provider.saveDocument(monitor, documentKey, document, overwrite); + } finally { + provider.changed(documentKey); + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#disconnect(java.lang.Object) + */ + public void disconnect(Object element) { + IEditorInput input = getDocumentKey(element); + if (input == null) + return; + IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(input); + if (provider == null) + return; + disconnect(provider, input); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/Splitter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/Splitter.java new file mode 100644 index 000000000..6ee909416 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/Splitter.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 org.eclipse.swt.widgets.*; +import org.eclipse.swt.custom.SashForm; + +/** + * The Splitter adds support for nesting to a SashForm. + * <P> + * If Splitters are nested directly: + * <UL> + * <LI>changing the visibility of a child may propagate upward to the parent Splitter if the child + * is the last child to become invisible or the first to become visible.</LI> + * <LI>maximizing a child makes it as large as the topmost enclosing Splitter</LI> + * </UL> + * + * @since 2.1 + */ +public class Splitter extends SashForm { + + private static final String VISIBILITY= "org.eclipse.compare.internal.visibility"; //$NON-NLS-1$ + + /** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception org.eclipse.swt.SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + */ + public Splitter(Composite parent, int style) { + super(parent, style); + } + + /** + * Sets the visibility of the given child in this Splitter. If this change + * affects the visibility state of the whole Splitter, and if the Splitter + * is directly nested in one or more Splitters, this method recursively + * propagates the new state upward. + * + * @param child the child control for which the visibility is changed + * @param visible the new visibility state + */ + public void setVisible(Control child, boolean visible) { + + boolean wasEmpty= isEmpty(); + + child.setVisible(visible); + child.setData(VISIBILITY, new Boolean(visible)); + + if (wasEmpty != isEmpty()) { + // recursively walk up + Composite parent= getParent(); + if (parent instanceof Splitter) { + Splitter sp= (Splitter) parent; + sp.setVisible(this, visible); + sp.layout(); + } + } else { + layout(); + } + } + + /* (non-Javadoc) + * Recursively calls setMaximizedControl for all direct parents that are + * itself Splitters. + */ + public void setMaximizedControl(Control control) { + if (control == null || control == getMaximizedControl()) + super.setMaximizedControl(null); + else + super.setMaximizedControl(control); + + // recursively walk upward + Composite parent= getParent(); + if (parent instanceof Splitter) + ((Splitter) parent).setMaximizedControl(this); + else + layout(true); + } + + /* (non-Javadoc) + * Returns true if Splitter has no children or if all children are invisible. + */ + private boolean isEmpty() { + Control[] controls= getChildren(); + for (int i= 0; i < controls.length; i++) + if (isVisible(controls[i])) + return false; + return true; + } + + /* (non-Javadoc) + * Returns the visibility state of the given child control. If the + * control is a Sash, this method always returns false. + */ + private boolean isVisible(Control child) { + if (child instanceof Sash) + return false; + Object data= child.getData(VISIBILITY); + if (data instanceof Boolean) + return ((Boolean)data).booleanValue(); + return true; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/ZipFileStructureCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ZipFileStructureCreator.java new file mode 100644 index 000000000..573fc4e24 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/ZipFileStructureCreator.java @@ -0,0 +1,326 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.compare.structuremergeviewer.IStructureCreator; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.swt.graphics.Image; + +/** + * This implementation of the <code>IStructureCreator</code> interface + * makes the contents of a zip archive available as a + * hierarchical structure of <code>IStructureComparator</code>s. + * <p> + * It is used when comparing the internal structure of a zip archive. + * + * @since 2.0 + */ +public class ZipFileStructureCreator implements IStructureCreator { + + /** + * Common base class for ZipFolder and ZipFile + */ + static abstract class ZipResource implements IStructureComparator, ITypedElement { + + private String fName; + + ZipResource(String name) { + fName= name; + } + + public String getName() { + return fName; + } + + public Image getImage() { + return CompareUI.getImage(getType()); + } + + /* + * Returns true if other is ITypedElement and names are equal. + * @see IComparator#equals + */ + public boolean equals(Object other) { + if (other instanceof ITypedElement) + return fName.equals(((ITypedElement) other).getName()); + return super.equals(other); + } + + public int hashCode() { + return fName.hashCode(); + } + } + + static class ZipFolder extends ZipResource { + + private HashMap fChildren= new HashMap(10); + + ZipFolder(String name) { + super(name); + } + + public String getType() { + return ITypedElement.FOLDER_TYPE; + } + + public Object[] getChildren() { + Object[] children= new Object[fChildren.size()]; + Iterator iter= fChildren.values().iterator(); + for (int i= 0; iter.hasNext(); i++) + children[i]= iter.next(); + return children; + } + + ZipFile createContainer(String path) { + String entry= path; + int pos= path.indexOf('/'); + if (pos < 0) + pos= path.indexOf('\\'); + if (pos >= 0) { + entry= path.substring(0, pos); + path= path.substring(pos + 1); + } else if (entry.length() > 0) { + if (CompareUIPlugin.getDefault().filter(path, false, true)) + return null; + ZipFile ze= new ZipFile(entry); + fChildren.put(entry, ze); + return ze; + } else + return null; + + ZipFolder folder= null; + if (fChildren != null) { + Object o= fChildren.get(entry); + if (o instanceof ZipFolder) + folder= (ZipFolder) o; + } + + if (folder == null) { + if (path.length() > 0 && CompareUIPlugin.getDefault().filter(path, true, true)) + return null; + folder= new ZipFolder(entry); + fChildren.put(entry, folder); + } + + return folder.createContainer(path); + } + } + + static class ZipFile extends ZipResource implements IStreamContentAccessor { + + private byte[] fContents; + + ZipFile(String name) { + super(name); + } + + public String getType() { + String s= this.getName(); + int pos= s.lastIndexOf('.'); + if (pos >= 0) + return s.substring(pos + 1); + return ITypedElement.UNKNOWN_TYPE; + } + + public Object[] getChildren() { + return null; + } + + public InputStream getContents() { + if (fContents == null) + fContents= new byte[0]; + return new ByteArrayInputStream(fContents); + } + + byte[] getBytes() { + return fContents; + } + + void setBytes(byte[] buffer) { + fContents= buffer; + } + + void appendBytes(byte[] buffer, int length) { + if (length > 0) { + int oldLen= 0; + if (fContents != null) + oldLen= fContents.length; + byte[] newBuf= new byte[oldLen + length]; + if (oldLen > 0) + System.arraycopy(fContents, 0, newBuf, 0, oldLen); + System.arraycopy(buffer, 0, newBuf, oldLen, length); + fContents= newBuf; + } + } + } + + private String fTitle; + + /** + * Create a new ZipFileStructureCreator. + */ + public ZipFileStructureCreator() { + this(Utilities.getString("ZipStructureCreator.name")); //$NON-NLS-1$ + } + + /** + * Create a new ZipFileStructureCreator with the given title. + * The title is returned by the method <code>getName()</code>. + * @param title the title of this structure creator + */ + public ZipFileStructureCreator(String title) { + fTitle= title; + } + + public String getName() { + return fTitle; + } + + public IStructureComparator getStructure(Object input) { + + InputStream is= null; + + if (input instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) input; + try { + is= sca.getContents(); + } catch (CoreException ex) { + // NeedWork + } + } + + if (is == null) + return null; + + ZipInputStream zip= new ZipInputStream(is); + ZipFolder root= new ZipFolder(""); //$NON-NLS-1$ + try { + for (;;) { + ZipEntry entry= zip.getNextEntry(); + if (entry == null) + break; + + ZipFile ze= root.createContainer(entry.getName()); + if (ze != null) { + int length= (int) entry.getSize(); + if (length >= 0) { + byte[] buffer= new byte[length]; + int offset= 0; + + do { + int n= zip.read(buffer, offset, length); + offset += n; + length -= n; + } while (length > 0); + + ze.setBytes(buffer); + } else { + byte[] buffer= new byte[1024]; + int n; + do { + n= zip.read(buffer, 0, 1024); + ze.appendBytes(buffer, n); + } while (n >= 0); + } + } + zip.closeEntry(); + } + } catch (IOException ex) { + return null; + } finally { + try { + zip.close(); + } catch (IOException ex) { + // silently ignored + } + } + + if (root.fChildren.size() == 1) { + Iterator iter= root.fChildren.values().iterator(); + return (IStructureComparator) iter.next(); + } + return root; + } + + public String getContents(Object o, boolean ignoreWhitespace) { + if (o instanceof ZipFile) { + byte[] bytes= ((ZipFile)o).getBytes(); + if (bytes != null) + return new String(bytes); + return ""; //$NON-NLS-1$ + } + return null; + } + + /** + * Returns <code>false</code> since we cannot update a zip archive. + * @return <code>false</code> + */ + public boolean canSave() { + return false; + } + + /** + * Called whenever a copy operation has been performed on a tree node. + * This implementation throws an <code>AssertionFailedException</code> + * since we cannot update a zip archive. + * + * @param structure the node for which to save the new content + * @param input the object from which the structure tree was created in <code>getStructure</code> + */ + public void save(IStructureComparator structure, Object input) { + Assert.isTrue(false); // Cannot update zip archive + } + + public IStructureComparator locate(Object path, Object source) { + return null; + } + + /** + * Returns <code>false</code> since this <code>IStructureCreator</code> + * cannot rewrite the diff tree in order to fold certain combinations of + * additions and deletions. + * <p> + * Note: this method is for internal use only. Clients should not call this method. + * @return <code>false</code> + */ + public boolean canRewriteTree() { + return false; + } + + /** + * Empty implementation since this <code>IStructureCreator</code> + * cannot rewrite the diff tree in order to fold certain combinations of + * additions and deletions. + * <p> + * Note: this method is for internal use only. Clients should not call this method. + * @param differencer + * @param root + */ + public void rewriteTree(Differencer differencer, IDiffContainer root) { + // empty default implementation + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java new file mode 100644 index 000000000..b47931690 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java @@ -0,0 +1,1343 @@ +/******************************************************************************* + * 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.contentmergeviewer; + +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.CompareViewerPane; +import org.eclipse.compare.ICompareContainer; +import org.eclipse.compare.ICompareInputLabelProvider; +import org.eclipse.compare.IPropertyChangeNotifier; +import org.eclipse.compare.internal.ChangePropertyAction; +import org.eclipse.compare.internal.CompareEditor; +import org.eclipse.compare.internal.CompareHandlerService; +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.IFlushable2; +import org.eclipse.compare.internal.ISavingSaveable; +import org.eclipse.compare.internal.MergeViewerContentProvider; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.ViewerSwitchingCancelled; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.LegacyActionTools; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.LabelProviderChangedEvent; +import org.eclipse.jface.window.Window; +import org.eclipse.osgi.util.TextProcessor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Sash; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.ISaveablesSource; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.Saveable; + +/** + * An abstract compare and merge viewer with two side-by-side content areas + * and an optional content area for the ancestor. The implementation makes no + * assumptions about the content type. + * <p> + * <code>ContentMergeViewer</code> + * <ul> + * <li>implements the overall layout and defines hooks so that subclasses + * can easily provide an implementation for a specific content type, + * <li>implements the UI for making the areas resizable, + * <li>has an action for controlling whether the ancestor area is visible or not, + * <li>has actions for copying one side of the input to the other side, + * <li>tracks the dirty state of the left and right sides and send out notification + * on state changes. + * </ul> + * A <code>ContentMergeViewer</code> accesses its + * model by means of a content provider which must implement the + * <code>IMergeViewerContentProvider</code> interface. + * </p> + * <p> + * Clients may wish to use the standard concrete subclass <code>TextMergeViewer</code>, + * or define their own subclass. + * + * @see IMergeViewerContentProvider + * @see TextMergeViewer + */ +public abstract class ContentMergeViewer extends ContentViewer + implements IPropertyChangeNotifier, IFlushable, IFlushable2 { + + /* package */ static final int HORIZONTAL= 1; + /* package */ static final int VERTICAL= 2; + + static final double HSPLIT= 0.5; + static final double VSPLIT= 0.3; + + private class ContentMergeViewerLayout extends Layout { + + public Point computeSize(Composite c, int w, int h, boolean force) { + return new Point(100, 100); + } + + public void layout(Composite composite, boolean force) { + + // determine some derived sizes + int headerHeight= fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y; + Rectangle r= composite.getClientArea(); + + int centerWidth= getCenterWidth(); + int width1= (int)((r.width-centerWidth)*getHorizontalSplitRatio()); + int width2= r.width-width1-centerWidth; + + int height1= 0; + int height2= 0; + if (fIsThreeWay && fAncestorVisible) { + height1= (int)((r.height-(2*headerHeight))*fVSplit); + height2= r.height-(2*headerHeight)-height1; + } else { + height1= 0; + height2= r.height-headerHeight; + } + + int y= 0; + + if (fIsThreeWay && fAncestorVisible) { + fAncestorLabel.setBounds(0, y, r.width, headerHeight); + fAncestorLabel.setVisible(true); + y+= headerHeight; + handleResizeAncestor(0, y, r.width, height1); + y+= height1; + } else { + fAncestorLabel.setVisible(false); + handleResizeAncestor(0, 0, 0, 0); + } + + fLeftLabel.getSize(); // without this resizing would not always work + + if (centerWidth > 3) { + fLeftLabel.setBounds(0, y, width1+1, headerHeight); + fDirectionLabel.setVisible(true); + fDirectionLabel.setBounds(width1+1, y, centerWidth-1, headerHeight); + fRightLabel.setBounds(width1+centerWidth, y, width2, headerHeight); + } else { + fLeftLabel.setBounds(0, y, width1, headerHeight); + fDirectionLabel.setVisible(false); + fRightLabel.setBounds(width1, y, r.width-width1, headerHeight); + } + + y+= headerHeight; + + if (fCenter != null && !fCenter.isDisposed()) + fCenter.setBounds(width1, y, centerWidth, height2); + + handleResizeLeftRight(0, y, width1, centerWidth, width2, height2); + } + + private double getHorizontalSplitRatio() { + if (fHSplit < 0) { + Object input = getInput(); + if (input instanceof ICompareInput) { + ICompareInput ci = (ICompareInput) input; + if (ci.getLeft() == null) + return 0.1; + if (ci.getRight() == null) + return 0.9; + } + return HSPLIT; + } + return fHSplit; + } + } + + class Resizer extends MouseAdapter implements MouseMoveListener { + + Control fControl; + int fX, fY; + int fWidth1, fWidth2; + int fHeight1, fHeight2; + int fDirection; + boolean fLiveResize; + boolean fIsDown; + + public Resizer(Control c, int dir) { + fDirection= dir; + fControl= c; + fLiveResize= !(fControl instanceof Sash); + updateCursor(c, dir); + fControl.addMouseListener(this); + fControl.addMouseMoveListener(this); + fControl.addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + fControl= null; + } + } + ); + } + + public void mouseDoubleClick(MouseEvent e) { + if ((fDirection & HORIZONTAL) != 0) + fHSplit= -1; + if ((fDirection & VERTICAL) != 0) + fVSplit= VSPLIT; + fComposite.layout(true); + } + + public void mouseDown(MouseEvent e) { + Composite parent= fControl.getParent(); + + Point s= parent.getSize(); + Point as= fAncestorLabel.getSize(); + Point ys= fLeftLabel.getSize(); + Point ms= fRightLabel.getSize(); + + fWidth1= ys.x; + fWidth2= ms.x; + fHeight1= fLeftLabel.getLocation().y-as.y; + fHeight2= s.y-(fLeftLabel.getLocation().y+ys.y); + + fX= e.x; + fY= e.y; + fIsDown= true; + } + + public void mouseUp(MouseEvent e) { + fIsDown= false; + if (!fLiveResize) + resize(e); + } + + public void mouseMove(MouseEvent e) { + if (fIsDown && fLiveResize) + resize(e); + } + + private void resize(MouseEvent e) { + int dx= e.x-fX; + int dy= e.y-fY; + + int centerWidth= fCenter.getSize().x; + + if (fWidth1 + dx > centerWidth && fWidth2 - dx > centerWidth) { + fWidth1+= dx; + fWidth2-= dx; + if ((fDirection & HORIZONTAL) != 0) + fHSplit= (double)fWidth1/(double)(fWidth1+fWidth2); + } + if (fHeight1 + dy > centerWidth && fHeight2 - dy > centerWidth) { + fHeight1+= dy; + fHeight2-= dy; + if ((fDirection & VERTICAL) != 0) + fVSplit= (double)fHeight1/(double)(fHeight1+fHeight2); + } + + fComposite.layout(true); + fControl.getDisplay().update(); + } + } + + /** Style bits for top level composite */ + private int fStyles; + private ResourceBundle fBundle; + private final CompareConfiguration fCompareConfiguration; + private IPropertyChangeListener fPropertyChangeListener; + private ICompareInputChangeListener fCompareInputChangeListener; + private ListenerList fListenerList; + boolean fConfirmSave= true; + + private double fHSplit= -1; // width ratio of left and right panes + private double fVSplit= VSPLIT; // height ratio of ancestor and bottom panes + + private boolean fIsThreeWay; // whether their is an ancestor + private boolean fAncestorVisible; // whether the ancestor pane is visible + private ActionContributionItem fAncestorItem; + + private Action fCopyLeftToRightAction; // copy from left to right + private Action fCopyRightToLeftAction; // copy from right to left + + private boolean fIsLeftDirty; + private boolean fIsRightDirty; + + private CompareHandlerService fHandlerService; + + // SWT widgets + /* package */ Composite fComposite; + private CLabel fAncestorLabel; + private CLabel fLeftLabel; + private CLabel fRightLabel; + /* package */ CLabel fDirectionLabel; + /* package */ Control fCenter; + + //---- SWT resources to be disposed + private Image fRightArrow; + private Image fLeftArrow; + private Image fBothArrow; + Cursor fNormalCursor; + private Cursor fHSashCursor; + private Cursor fVSashCursor; + private Cursor fHVSashCursor; + + private ILabelProviderListener labelChangeListener = new ILabelProviderListener() { + public void labelProviderChanged(LabelProviderChangedEvent event) { + Object[] elements = event.getElements(); + for (int i = 0; i < elements.length; i++) { + Object object = elements[i]; + if (object == getInput()) + updateHeader(); + } + } + }; + + //---- end + + /** + * Creates a new content merge viewer and initializes with a resource bundle and a + * configuration. + * + * @param style SWT style bits + * @param bundle the resource bundle + * @param cc the configuration object + */ + protected ContentMergeViewer(int style, ResourceBundle bundle, CompareConfiguration cc) { + + fStyles= style & ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT); // remove BIDI direction bits + fBundle= bundle; + + fAncestorVisible= Utilities.getBoolean(cc, ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible); + fConfirmSave= Utilities.getBoolean(cc, CompareEditor.CONFIRM_SAVE_PROPERTY, fConfirmSave); + + setContentProvider(new MergeViewerContentProvider(cc)); + + fCompareInputChangeListener= new ICompareInputChangeListener() { + public void compareInputChanged(ICompareInput input) { + if (input == getInput()) { + handleCompareInputChange(); + } + } + }; + + // Make sure the compare configuration is not null + if (cc == null) + fCompareConfiguration = new CompareConfiguration(); + else + fCompareConfiguration= cc; + fPropertyChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + ContentMergeViewer.this.handlePropertyChangeEvent(event); + } + }; + fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener); + + fIsLeftDirty = false; + fIsRightDirty = false; + } + + //---- hooks --------------------- + + /** + * Returns the viewer's name. + * + * @return the viewer's name + */ + public String getTitle() { + return Utilities.getString(getResourceBundle(), "title"); //$NON-NLS-1$ + } + + /** + * Creates the SWT controls for the ancestor, left, and right + * content areas of this compare viewer. + * Implementations typically hold onto the controls + * so that they can be initialized with the input objects in method + * <code>updateContent</code>. + * + * @param composite the container for the three areas + */ + abstract protected void createControls(Composite composite); + + /** + * Lays out the ancestor area of the compare viewer. + * It is called whenever the viewer is resized or when the sashes between + * the areas are moved to adjust the size of the areas. + * + * @param x the horizontal position of the ancestor area within its container + * @param y the vertical position of the ancestor area within its container + * @param width the width of the ancestor area + * @param height the height of the ancestor area + */ + abstract protected void handleResizeAncestor(int x, int y, int width, int height); + + /** + * Lays out the left and right areas of the compare viewer. + * It is called whenever the viewer is resized or when the sashes between + * the areas are moved to adjust the size of the areas. + * + * @param x the horizontal position of the left area within its container + * @param y the vertical position of the left and right area within its container + * @param leftWidth the width of the left area + * @param centerWidth the width of the gap between the left and right areas + * @param rightWidth the width of the right area + * @param height the height of the left and right areas + */ + abstract protected void handleResizeLeftRight(int x, int y, int leftWidth, int centerWidth, + int rightWidth, int height); + + /** + * Contributes items to the given <code>ToolBarManager</code>. + * It is called when this viewer is installed in its container and if the container + * has a <code>ToolBarManager</code>. + * The <code>ContentMergeViewer</code> implementation of this method does nothing. + * Subclasses may reimplement. + * + * @param toolBarManager the toolbar manager to contribute to + */ + protected void createToolItems(ToolBarManager toolBarManager) { + // empty implementation + } + + /** + * Initializes the controls of the three content areas with the given input objects. + * + * @param ancestor the input for the ancestor area + * @param left the input for the left area + * @param right the input for the right area + */ + abstract protected void updateContent(Object ancestor, Object left, Object right); + + /** + * Copies the content of one side to the other side. + * Called from the (internal) actions for copying the sides of the viewer's input object. + * + * @param leftToRight if <code>true</code>, the left side is copied to the right side; + * if <code>false</code>, the right side is copied to the left side + */ + abstract protected void copy(boolean leftToRight); + + /** + * Returns the byte contents of the left or right side. If the viewer + * has no editable content <code>null</code> can be returned. + * + * @param left if <code>true</code>, the byte contents of the left area is returned; + * if <code>false</code>, the byte contents of the right area + * @return the content as an array of bytes, or <code>null</code> + */ + abstract protected byte[] getContents(boolean left); + + //---------------------------- + + /** + * Returns the resource bundle of this viewer. + * + * @return the resource bundle + */ + protected ResourceBundle getResourceBundle() { + return fBundle; + } + + /** + * Returns the compare configuration of this viewer, + * or <code>null</code> if this viewer does not yet have a configuration. + * + * @return the compare configuration, or <code>null</code> if none + */ + protected CompareConfiguration getCompareConfiguration() { + return fCompareConfiguration; + } + + /** + * The <code>ContentMergeViewer</code> implementation of this + * <code>ContentViewer</code> method + * checks to ensure that the content provider is an <code>IMergeViewerContentProvider</code>. + * @param contentProvider the content provider to set. Must implement IMergeViewerContentProvider. + */ + public void setContentProvider(IContentProvider contentProvider) { + Assert.isTrue(contentProvider instanceof IMergeViewerContentProvider); + super.setContentProvider(contentProvider); + } + + /* package */ IMergeViewerContentProvider getMergeContentProvider() { + return (IMergeViewerContentProvider) getContentProvider(); + } + + /** + * The <code>ContentMergeViewer</code> implementation of this + * <code>Viewer</code> method returns the empty selection. Subclasses may override. + * @return empty selection. + */ + public ISelection getSelection() { + return new ISelection() { + public boolean isEmpty() { + return true; + } + }; + } + + /** + * The <code>ContentMergeViewer</code> implementation of this + * <code>Viewer</code> method does nothing. Subclasses may reimplement. + * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean) + */ + public void setSelection(ISelection selection, boolean reveal) { + // empty implementation + } + + /** + * Callback that is invoked when a property in the compare configuration + * ({@link #getCompareConfiguration()} changes. + * @param event the property change event + * @since 3.3 + */ + protected void handlePropertyChangeEvent(PropertyChangeEvent event) { + + String key= event.getProperty(); + + if (key.equals(ICompareUIConstants.PROP_ANCESTOR_VISIBLE)) { + fAncestorVisible= Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible); + fComposite.layout(true); + + updateCursor(fLeftLabel, VERTICAL); + updateCursor(fDirectionLabel, HORIZONTAL | VERTICAL); + updateCursor(fRightLabel, VERTICAL); + + return; + } + + if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) { + setAncestorVisibility(false, !Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false)); + return; + } + } + + void updateCursor(Control c, int dir) { + if (!(c instanceof Sash)) { + Cursor cursor= null; + switch (dir) { + case VERTICAL: + if (fAncestorVisible) { + if (fVSashCursor == null) fVSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZENS); + cursor= fVSashCursor; + } else { + if (fNormalCursor == null) fNormalCursor= new Cursor(c.getDisplay(), SWT.CURSOR_ARROW); + cursor= fNormalCursor; + } + break; + case HORIZONTAL: + if (fHSashCursor == null) fHSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE); + cursor= fHSashCursor; + break; + case VERTICAL + HORIZONTAL: + if (fAncestorVisible) { + if (fHVSashCursor == null) fHVSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEALL); + cursor= fHVSashCursor; + } else { + if (fHSashCursor == null) fHSashCursor= new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE); + cursor= fHSashCursor; + } + break; + } + if (cursor != null) + c.setCursor(cursor); + } + } + + private void setAncestorVisibility(boolean visible, boolean enabled) { + if (fAncestorItem != null) { + Action action= (Action) fAncestorItem.getAction(); + if (action != null) { + action.setChecked(visible); + action.setEnabled(enabled); + } + } + getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, new Boolean(visible)); + } + + //---- input + + /** + * Return whether the input is a three-way comparison. + * @return whether the input is a three-way comparison + * @since 3.3 + */ + protected boolean isThreeWay() { + return fIsThreeWay; + } + + /** + * Internal hook method called when the input to this viewer is + * initially set or subsequently changed. + * <p> + * The <code>ContentMergeViewer</code> implementation of this <code>Viewer</code> + * method tries to save the old input by calling <code>doSave(...)</code> and + * then calls <code>internalRefresh(...)</code>. + * + * @param input the new input of this viewer, or <code>null</code> if there is no new input + * @param oldInput the old input element, or <code>null</code> if there was previously no input + */ + protected final void inputChanged(Object input, Object oldInput) { + + if (input != oldInput && oldInput != null) { + ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider(); + if (lp != null) + lp.removeListener(labelChangeListener); + } + + if (input != oldInput && oldInput instanceof ICompareInput) { + ICompareContainer container = getCompareConfiguration().getContainer(); + container.removeCompareInputChangeListener((ICompareInput)oldInput, fCompareInputChangeListener); + } + + boolean success= doSave(input, oldInput); + + if (input != oldInput && input instanceof ICompareInput) { + ICompareContainer container = getCompareConfiguration().getContainer(); + container.addCompareInputChangeListener((ICompareInput)input, fCompareInputChangeListener); + } + + if (input != oldInput && input != null) { + ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider(); + if (lp != null) + lp.addListener(labelChangeListener); + } + + if (success) { + setLeftDirty(false); + setRightDirty(false); + } + + if (input != oldInput) + internalRefresh(input); + } + + /** + * This method is called from the <code>Viewer</code> method <code>inputChanged</code> + * to save any unsaved changes of the old input. + * <p> + * The <code>ContentMergeViewer</code> implementation of this + * method calls <code>saveContent(...)</code>. If confirmation has been turned on + * with <code>setConfirmSave(true)</code>, a confirmation alert is posted before saving. + * </p> + * Clients can override this method and are free to decide whether + * they want to call the inherited method. + * @param newInput the new input of this viewer, or <code>null</code> if there is no new input + * @param oldInput the old input element, or <code>null</code> if there was previously no input + * @return <code>true</code> if saving was successful, or if the user didn't want to save (by pressing 'NO' in the confirmation dialog). + * @since 2.0 + */ + protected boolean doSave(Object newInput, Object oldInput) { + + // before setting the new input we have to save the old + if (isLeftDirty() || isRightDirty()) { + + + if (Utilities.RUNNING_TESTS) { + if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) { + flushContent(oldInput, null); + } + } else if (fConfirmSave) { + // post alert + Shell shell= fComposite.getShell(); + + MessageDialog dialog= new MessageDialog(shell, + Utilities.getString(getResourceBundle(), "saveDialog.title"), //$NON-NLS-1$ + null, // accept the default window icon + Utilities.getString(getResourceBundle(), "saveDialog.message"), //$NON-NLS-1$ + MessageDialog.QUESTION, + new String[] { + IDialogConstants.YES_LABEL, + IDialogConstants.NO_LABEL, + }, + 0); // default button index + + switch (dialog.open()) { // open returns index of pressed button + case 0: + flushContent(oldInput, null); + break; + case 1: + setLeftDirty(false); + setRightDirty(false); + break; + case 2: + throw new ViewerSwitchingCancelled(); + } + } else + flushContent(oldInput, null); + return true; + } + return false; + } + + /** + * Controls whether <code>doSave(Object, Object)</code> asks for confirmation before saving + * the old input with <code>saveContent(Object)</code>. + * @param enable a value of <code>true</code> enables confirmation + * @since 2.0 + */ + public void setConfirmSave(boolean enable) { + fConfirmSave= enable; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.Viewer#refresh() + */ + public void refresh() { + internalRefresh(getInput()); + } + + private void internalRefresh(Object input) { + + IMergeViewerContentProvider content= getMergeContentProvider(); + if (content != null) { + Object ancestor= content.getAncestorContent(input); + boolean oldFlag = fIsThreeWay; + if (Utilities.isHunk(input)) { + fIsThreeWay = true; + } else if (input instanceof ICompareInput) + fIsThreeWay= (((ICompareInput)input).getKind() & Differencer.DIRECTION_MASK) != 0; + else + fIsThreeWay= ancestor != null; + + if (fAncestorItem != null) + fAncestorItem.setVisible(fIsThreeWay); + + if (fAncestorVisible && oldFlag != fIsThreeWay) + fComposite.layout(true); + + + Object left= content.getLeftContent(input); + Object right= content.getRightContent(input); + updateContent(ancestor, left, right); + + updateHeader(); + if (Utilities.okToUse(fComposite) && Utilities.okToUse(fComposite.getParent())) { + ToolBarManager tbm = (ToolBarManager) getToolBarManager(fComposite.getParent()); + if (tbm != null ) { + updateToolItems(); + tbm.update(true); + tbm.getControl().getParent().layout(true); + } + } + } + } + + //---- layout & SWT control creation + + /** + * Builds the SWT controls for the three areas of a compare/merge viewer. + * <p> + * Calls the hooks <code>createControls</code> and <code>createToolItems</code> + * to let subclasses build the specific content areas and to add items to + * an enclosing toolbar. + * <p> + * This method must only be called in the constructor of subclasses. + * + * @param parent the parent control + * @return the new control + */ + protected final Control buildControl(Composite parent) { + + fComposite= new Composite(parent, fStyles | SWT.LEFT_TO_RIGHT) { // we force a specific direction + public boolean setFocus() { + return ContentMergeViewer.this.handleSetFocus(); + } + }; + fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle()); + + hookControl(fComposite); // hook help & dispose listener + + fComposite.setLayout(new ContentMergeViewerLayout()); + + int style= SWT.SHADOW_OUT; + fAncestorLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); + + fLeftLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); + new Resizer(fLeftLabel, VERTICAL); + + fDirectionLabel= new CLabel(fComposite, style); + fDirectionLabel.setAlignment(SWT.CENTER); + new Resizer(fDirectionLabel, HORIZONTAL | VERTICAL); + + fRightLabel= new CLabel(fComposite, style | Window.getDefaultOrientation()); + new Resizer(fRightLabel, VERTICAL); + + if (fCenter == null || fCenter.isDisposed()) + fCenter= createCenterControl(fComposite); + + createControls(fComposite); + + fHandlerService= CompareHandlerService.createFor(getCompareConfiguration().getContainer(), fComposite.getShell()); + + initializeToolbars(parent); + + return fComposite; + } + + /** + * Returns the toolbar manager for this viewer. + * + * Subclasses may extend this method and use either the toolbar manager + * provided by the inherited method by calling + * super.getToolBarManager(parent) or provide an alternate toolbar manager. + * + * @param parent + * a <code>Composite</code> or <code>null</code> + * @return a <code>IToolBarManager</code> + * @since 3.4 + */ + protected IToolBarManager getToolBarManager(Composite parent) { + return CompareViewerPane.getToolBarManager(parent); + } + + private void initializeToolbars(Composite parent) { + ToolBarManager tbm = (ToolBarManager) getToolBarManager(parent); + if (tbm != null) { + tbm.removeAll(); + + // define groups + tbm.add(new Separator("modes")); //$NON-NLS-1$ + tbm.add(new Separator("merge")); //$NON-NLS-1$ + tbm.add(new Separator("navigation")); //$NON-NLS-1$ + + CompareConfiguration cc= getCompareConfiguration(); + + if (cc.isRightEditable()) { + fCopyLeftToRightAction= + new Action() { + public void run() { + copy(true); + } + }; + Utilities.initAction(fCopyLeftToRightAction, getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$ + tbm.appendToGroup("merge", fCopyLeftToRightAction); //$NON-NLS-1$ + fHandlerService.registerAction(fCopyLeftToRightAction, "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$ + } + + if (cc.isLeftEditable()) { + fCopyRightToLeftAction= + new Action() { + public void run() { + copy(false); + } + }; + Utilities.initAction(fCopyRightToLeftAction, getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$ + tbm.appendToGroup("merge", fCopyRightToLeftAction); //$NON-NLS-1$ + fHandlerService.registerAction(fCopyRightToLeftAction, "org.eclipse.compare.copyAllRightToLeft"); //$NON-NLS-1$ + } + + final ChangePropertyAction a= new ChangePropertyAction(fBundle, getCompareConfiguration(), "action.EnableAncestor.", ICompareUIConstants.PROP_ANCESTOR_VISIBLE); //$NON-NLS-1$ + a.setChecked(fAncestorVisible); + fAncestorItem= new ActionContributionItem(a); + fAncestorItem.setVisible(false); + tbm.appendToGroup("modes", fAncestorItem); //$NON-NLS-1$ + tbm.getControl().addDisposeListener(a); + + createToolItems(tbm); + updateToolItems(); + + tbm.update(true); + } + } + + /** + * Callback that is invoked when the control of this merge viewer is given focus. + * This method should return <code>true</code> if a particular widget was given focus + * and false otherwise. By default, <code>false</code> is returned. Subclasses may override. + * @return whether particular widget was given focus + * @since 3.3 + */ + protected boolean handleSetFocus() { + return false; + } + + /** + * Return the desired width of the center control. This width is used + * to calculate the values used to layout the ancestor, left and right sides. + * @return the desired width of the center control + * @see #handleResizeLeftRight(int, int, int, int, int, int) + * @see #handleResizeAncestor(int, int, int, int) + * @since 3.3 + */ + protected int getCenterWidth() { + return 3; + } + + /** + * Return whether the ancestor pane is visible or not. + * @return whether the ancestor pane is visible or not + * @since 3.3 + */ + protected boolean isAncestorVisible() { + return fAncestorVisible; + } + + /** + * Create the control that divides the left and right sides of the merge viewer. + * @param parent the parent composite + * @return the center control + * @since 3.3 + */ + protected Control createCenterControl(Composite parent) { + Sash sash= new Sash(parent, SWT.VERTICAL); + new Resizer(sash, HORIZONTAL); + return sash; + } + + /** + * Return the center control that divides the left and right sides of the merge viewer. + * This method returns the control that was created by calling {@link #createCenterControl(Composite)}. + * @see #createCenterControl(Composite) + * @return the center control + * @since 3.3 + */ + protected Control getCenterControl() { + return fCenter; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.Viewer#getControl() + */ + public Control getControl() { + return fComposite; + } + + /** + * Called on the viewer disposal. + * Unregisters from the compare configuration. + * Clients may extend if they have to do additional cleanup. + * @see org.eclipse.jface.viewers.ContentViewer#handleDispose(org.eclipse.swt.events.DisposeEvent) + */ + protected void handleDispose(DisposeEvent event) { + + if (fHandlerService != null) + fHandlerService.dispose(); + + Object input= getInput(); + if (input instanceof ICompareInput) { + ICompareContainer container = getCompareConfiguration().getContainer(); + container.removeCompareInputChangeListener((ICompareInput)input, fCompareInputChangeListener); + } + if (input != null) { + ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider(); + if (lp != null) + lp.removeListener(labelChangeListener); + } + + if (fPropertyChangeListener != null) { + fCompareConfiguration.removePropertyChangeListener(fPropertyChangeListener); + fPropertyChangeListener= null; + } + + fAncestorLabel= null; + fLeftLabel= null; + fDirectionLabel= null; + fRightLabel= null; + fCenter= null; + + if (fRightArrow != null) { + fRightArrow.dispose(); + fRightArrow= null; + } + if (fLeftArrow != null) { + fLeftArrow.dispose(); + fLeftArrow= null; + } + if (fBothArrow != null) { + fBothArrow.dispose(); + fBothArrow= null; + } + + if (fNormalCursor != null) { + fNormalCursor.dispose(); + fNormalCursor= null; + } + if (fHSashCursor != null) { + fHSashCursor.dispose(); + fHSashCursor= null; + } + if (fVSashCursor != null) { + fVSashCursor.dispose(); + fVSashCursor= null; + } + if (fHVSashCursor != null) { + fHVSashCursor.dispose(); + fHVSashCursor= null; + } + + super.handleDispose(event); + } + + /** + * Updates the enabled state of the toolbar items. + * <p> + * This method is called whenever the state of the items needs updating. + * <p> + * Subclasses may extend this method, although this is generally not required. + */ + protected void updateToolItems() { + + IMergeViewerContentProvider content= getMergeContentProvider(); + + Object input= getInput(); + + if (fCopyLeftToRightAction != null) { + boolean enable= content.isRightEditable(input); +// if (enable && input instanceof ICompareInput) { +// ITypedElement e= ((ICompareInput) input).getLeft(); +// if (e == null) +// enable= false; +// } + fCopyLeftToRightAction.setEnabled(enable); + } + + if (fCopyRightToLeftAction != null) { + boolean enable= content.isLeftEditable(input); +// if (enable && input instanceof ICompareInput) { +// ITypedElement e= ((ICompareInput) input).getRight(); +// if (e == null) +// enable= false; +// } + fCopyRightToLeftAction.setEnabled(enable); + } + } + + /** + * Updates the headers of the three areas + * by querying the content provider for a name and image for + * the three sides of the input object. + * <p> + * This method is called whenever the header must be updated. + * <p> + * Subclasses may extend this method, although this is generally not required. + */ + protected void updateHeader() { + + IMergeViewerContentProvider content= getMergeContentProvider(); + Object input= getInput(); + + // Only change a label if there is a new label available + if (fAncestorLabel != null) { + Image ancestorImage = content.getAncestorImage(input); + if (ancestorImage != null) + fAncestorLabel.setImage(ancestorImage); + String ancestorLabel = content.getAncestorLabel(input); + if (ancestorLabel != null) + fAncestorLabel.setText(LegacyActionTools.escapeMnemonics(TextProcessor.process(ancestorLabel))); + } + if (fLeftLabel != null) { + Image leftImage = content.getLeftImage(input); + if (leftImage != null) + fLeftLabel.setImage(leftImage); + String leftLabel = content.getLeftLabel(input); + if (leftLabel != null) + fLeftLabel.setText(LegacyActionTools.escapeMnemonics(leftLabel)); + } + if (fRightLabel != null) { + Image rightImage = content.getRightImage(input); + if (rightImage != null) + fRightLabel.setImage(rightImage); + String rightLabel = content.getRightLabel(input); + if (rightLabel != null) + fRightLabel.setText(LegacyActionTools.escapeMnemonics(rightLabel)); + } + } + + /* + * Calculates the height of the header. + */ + /* package */ int getHeaderHeight() { + int headerHeight= fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y; + headerHeight= Math.max(headerHeight, fDirectionLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y); + return headerHeight; + } + + //---- dirty state & saving state + + /* (non-Javadoc) + * @see org.eclipse.compare.IPropertyChangeNotifier#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener) + */ + public void addPropertyChangeListener(IPropertyChangeListener listener) { + if (fListenerList == null) + fListenerList= new ListenerList(); + fListenerList.add(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IPropertyChangeNotifier#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener) + */ + public void removePropertyChangeListener(IPropertyChangeListener listener) { + if (fListenerList != null) { + fListenerList.remove(listener); + if (fListenerList.isEmpty()) + fListenerList= null; + } + } + + private void fireDirtyState(boolean state) { + Utilities.firePropertyChange(fListenerList, this, CompareEditorInput.DIRTY_STATE, null, new Boolean(state)); + } + + /** + * Sets the dirty state of the left side of this viewer. + * If the new value differs from the old + * all registered listener are notified with + * a <code>PropertyChangeEvent</code> with the + * property name <code>CompareEditorInput.DIRTY_STATE</code>. + * + * @param dirty the state of the left side dirty flag + */ + protected void setLeftDirty(boolean dirty) { + if (isLeftDirty() != dirty) { + fIsLeftDirty = dirty; + // Always fire the event if the dirty state has changed + fireDirtyState(dirty); + } + } + + /** + * Sets the dirty state of the right side of this viewer. + * If the new value differs from the old + * all registered listener are notified with + * a <code>PropertyChangeEvent</code> with the + * property name <code>CompareEditorInput.DIRTY_STATE</code>. + * + * @param dirty the state of the right side dirty flag + */ + protected void setRightDirty(boolean dirty) { + if (isRightDirty() != dirty) { + fIsRightDirty = dirty; + // Always fire the event if the dirty state has changed + fireDirtyState(dirty); + } + } + + /** + * Method from the old internal <code>ISavable</code> interface + * Save the viewers's content. + * Note: this method is for internal use only. Clients should not call this method. + * @param monitor a progress monitor + * @throws CoreException + * @deprecated use {@link IFlushable#flush(IProgressMonitor)}. + */ + public void save(IProgressMonitor monitor) throws CoreException { + flush(monitor); + } + + /** + * Flush any modifications made in the viewer into the compare input. This method + * calls {@link #flushContent(Object, IProgressMonitor)} with the compare input + * of the viewer as the first parameter. + * @param monitor a progress monitor + * @see org.eclipse.compare.contentmergeviewer.IFlushable#flush(org.eclipse.core.runtime.IProgressMonitor) + * @since 3.3 + */ + public final void flush(IProgressMonitor monitor) { + flushContent(getInput(), monitor); + } + + /** + * Flush the modified content back to input elements via the content provider. + * The provided input may be the current input of the viewer or it may be + * the previous input (i.e. this method may be called to flush modified content + * during an input change). + * @param input the compare input + * @param monitor a progress monitor or <code>null</code> if the method + * was call from a place where a progress monitor was not available. + * @since 3.3 + */ + protected void flushContent(Object input, IProgressMonitor monitor) { + flushLeftSide(input, monitor); + flushRightSide(input, monitor); + } + + + void flushLeftSide(Object input, IProgressMonitor monitor) { + IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider(); + + boolean rightEmpty = content.getRightContent(input) == null; + + if (getCompareConfiguration().isLeftEditable() && isLeftDirty()) { + byte[] bytes = getContents(true); + if (rightEmpty && bytes != null && bytes.length == 0) + bytes = null; + setLeftDirty(false); + content.saveLeftContent(input, bytes); + } + } + + void flushRightSide(Object input, IProgressMonitor monitor) { + IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider(); + + boolean leftEmpty = content.getLeftContent(input) == null; + + if (getCompareConfiguration().isRightEditable() && isRightDirty()) { + byte[] bytes = getContents(false); + if (leftEmpty && bytes != null && bytes.length == 0) + bytes = null; + setRightDirty(false); + content.saveRightContent(input, bytes); + } + } + + /** + * @param monitor + * @noreference This method is not intended to be referenced by clients. + */ + public void flushLeft(IProgressMonitor monitor) { + flushLeftSide(getInput(), monitor); + } + + /** + * @param monitor + * @noreference This method is not intended to be referenced by clients. + */ + public void flushRight(IProgressMonitor monitor) { + flushRightSide(getInput(), monitor); + } + + /** + * Return the dirty state of the right side of this viewer. + * @return the dirty state of the right side of this viewer + * @since 3.3 + */ + protected boolean isRightDirty() { + return fIsRightDirty; + } + + /** + * @return the dirty state of the right side of this viewer + * @since 3.7 + * @noreference This method is not intended to be referenced by clients. + */ + public boolean internalIsRightDirty() { + return isRightDirty(); + } + + /** + * Return the dirty state of the left side of this viewer. + * @return the dirty state of the left side of this viewer + * @since 3.3 + */ + protected boolean isLeftDirty() { + return fIsLeftDirty; + } + + /** + * @return the dirty state of the left side of this viewer + * @since 3.7 + * @noreference This method is not intended to be referenced by clients. + */ + public boolean internalIsLeftDirty() { + return isLeftDirty(); + } + + /** + * Handle a change to the given input reported from an {@link org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener}. + * This class registers a listener with its input and reports any change events through + * this method. By default, this method prompts for any unsaved changes and then refreshes + * the viewer. Subclasses may override. + * @since 3.3 + */ + protected void handleCompareInputChange() { + // before setting the new input we have to save the old + Object input = getInput(); + if (!isSaving() && (isLeftDirty() || isRightDirty())) { + + if (Utilities.RUNNING_TESTS) { + if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) { + flushContent(input, null); + } + } else { + // post alert + Shell shell= fComposite.getShell(); + + MessageDialog dialog= new MessageDialog(shell, + CompareMessages.ContentMergeViewer_resource_changed_title, + null, // accept the default window icon + CompareMessages.ContentMergeViewer_resource_changed_description, + MessageDialog.QUESTION, + new String[] { + IDialogConstants.YES_LABEL, // 0 + IDialogConstants.NO_LABEL, // 1 + }, + 0); // default button index + + switch (dialog.open()) { // open returns index of pressed button + case 0: + flushContent(input, null); + break; + case 1: + setLeftDirty(false); + setRightDirty(false); + break; + } + } + } + refresh(); + } + + CompareHandlerService getCompareHandlerService() { + return fHandlerService; + } + + /** + * @return true if any of the Saveables is being saved + */ + private boolean isSaving() { + ICompareContainer container = fCompareConfiguration.getContainer(); + ISaveablesSource source = null; + if (container instanceof ISaveablesSource) { + source = (ISaveablesSource) container; + } else { + IWorkbenchPart part = container.getWorkbenchPart(); + if (part instanceof ISaveablesSource) { + source = (ISaveablesSource) part; + } + } + if (source != null) { + Saveable[] saveables = source.getSaveables(); + for (int i = 0; i < saveables.length; i++) { + if (saveables[i] instanceof ISavingSaveable) { + ISavingSaveable saveable = (ISavingSaveable) saveables[i]; + if (saveable.isSaving()) + return true; + } + } + } + return false; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IDocumentRange.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IDocumentRange.java new file mode 100644 index 000000000..f682388a2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IDocumentRange.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.contentmergeviewer; + +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.IDocument; + + +/** + * Defines a subrange in a document. + * <p> + * It is used by text viewers that can work on a subrange of a document. For example, + * a text viewer for Java compilation units might use this to restrict the view + * to a single method. + * </p> + * <p> + * Clients may implement this interface. + * </p> + * + * @see TextMergeViewer + * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode + */ +public interface IDocumentRange { + + /** + * The position category typically used for an <code>IDocumentRange</code> position + * (value <code>"DocumentRangeCategory"</code>). + * @since 2.0 + */ + public static final String RANGE_CATEGORY= "DocumentRangeCategory"; //$NON-NLS-1$ + + /** + * Returns the underlying document. + * + * @return the underlying document + */ + IDocument getDocument(); + + /** + * Returns a position that specifies a subrange in the underlying document, + * or <code>null</code> if this document range spans the whole underlying document. + * + * @return a position that specifies a subrange in the underlying document, or <code>null</code> + */ + Position getRange(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IFlushable.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IFlushable.java new file mode 100644 index 000000000..440d31ef5 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IFlushable.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.contentmergeviewer; + +import org.eclipse.compare.IEditableContent; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.IDocument; + +/** + * Interface which provides the ability to flush the contents from the viewer + * model (for example, an {@link IDocument} for text based content) into the + * underlying compare model ( most likely an instance of {@link IEditableContent}). + * <p> + * This interface may be implemented by clients. + * </p> + * + * @since 3.3 + */ +public interface IFlushable { + + /** + * Request that the view contents be flushed to the underlying compare input. + * Depending on the type of input, this may result in the contents being written + * into the underlying model (e.g. file) as well. + * @param monitor a progress monitor or <code>null</code> if progress reporting is not desired + */ + void flush(IProgressMonitor monitor); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IMergeViewerContentProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IMergeViewerContentProvider.java new file mode 100644 index 000000000..86222edd7 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IMergeViewerContentProvider.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.contentmergeviewer; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.jface.viewers.IContentProvider; + + +/** + * A content provider that mediates between a <code>ContentMergeViewer</code>'s model + * and the viewer itself. + * <p> + * Clients may implement this interface. + * </p> + * + * @see ContentMergeViewer + */ +public interface IMergeViewerContentProvider extends IContentProvider { + + //---- ancestor side + + /** + * Returns the label for the ancestor side of a <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the label for the ancestor side of a <code>ContentMergeViewer</code> + */ + String getAncestorLabel(Object input); + + /** + * Returns an optional image for the ancestor side of a <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the image for the ancestor side of a <code>ContentMergeViewer</code>, + * or <code>null</code> if none + */ + Image getAncestorImage(Object input); + + /** + * Returns the contents for the ancestor side of a <code>ContentMergeViewer</code>. + * The interpretation of the returned object depends on the concrete <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the content for the ancestor side of a <code>ContentMergeViewer</code>, + * or <code>null</code> if none + */ + Object getAncestorContent(Object input); + + /** + * Returns whether the ancestor side of the given input element should be shown. + * @param input the merge viewer's input + * @return <code>true</code> if the ancestor side of the given input element should be shown + */ + boolean showAncestor(Object input); + + //---- left side + + /** + * Returns the label for the left side of a <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the label for the left side of a <code>ContentMergeViewer</code> + */ + String getLeftLabel(Object input); + + /** + * Returns an optional image for the left side of a <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the image for the left side of a <code>ContentMergeViewer</code>, + * or <code>null</code> if none + */ + Image getLeftImage(Object input); + + /** + * Returns the contents for the left side of a <code>ContentMergeViewer</code>. + * The interpretation of the returned object depends on the concrete <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the content for the left side of a <code>ContentMergeViewer</code>, + * or <code>null</code> if none + */ + Object getLeftContent(Object input); + + /** + * Returns whether the left side is editable. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return <code>true</code> if the left side of a <code>ContentMergeViewer</code> is editable + */ + boolean isLeftEditable(Object input); + + /** + * Saves new contents for the left side of the <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @param bytes the new contents to save for the left side + */ + void saveLeftContent(Object input, byte[] bytes); + + //---- right side + + /** + * Returns the label for the right side of a <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the label for the right side of a <code>ContentMergeViewer</code> + */ + String getRightLabel(Object input); + + /** + * Returns an optional image for the right side of a <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the image for the right side of a <code>ContentMergeViewer</code>, + * or <code>null</code> if none + */ + Image getRightImage(Object input); + + /** + * Returns the contents for the right side of a <code>ContentMergeViewer</code>. + * The interpretation of the returned object depends on the concrete <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return the content for the right side of a <code>ContentMergeViewer</code>, + * or <code>null</code> if none + */ + Object getRightContent(Object input); + + /** + * Returns whether the right side is editable. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @return <code>true</code> if the right side of a <code>ContentMergeViewer</code> is editable + */ + boolean isRightEditable(Object input); + + /** + * Saves new contents for the right side of the <code>ContentMergeViewer</code>. + * + * @param input the input object of the <code>ContentMergeViewer</code> + * @param bytes the new contents to save for the right side + */ + void saveRightContent(Object input, byte[] bytes); +} + + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ITokenComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ITokenComparator.java new file mode 100644 index 000000000..08815e449 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ITokenComparator.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.contentmergeviewer; + +import org.eclipse.compare.rangedifferencer.IRangeComparator; + + +/** + * For performing a so-called "token compare" on a line of text. + * This interface extends the <code>IRangeComparator</code> interface + * so that it can be used by the <code>TextMergeViewer</code>. + * <p> + * <code>TextMergeViewer</code> activates the token compare when navigating into + * a range of differing lines. At first the lines are selected as a block. + * When navigating into this block the token compare shows for every line + * the differing token by selecting them. + * <p> + * <code>TextMergeViewer</code>'s default token comparator works on characters separated + * by whitespace. If a different strategy is needed (for example, to use Java tokens in + * a Java-aware merge viewer), clients may create their own token + * comparators by implementing this interface (and overriding the + * <code>TextMergeViewer.createTokenComparator</code> factory method). + * </p> + * + * @see TextMergeViewer + */ +public interface ITokenComparator extends IRangeComparator { + + /** + * Returns the start character position of the token with the given index. + * If the index is out of range (but not negative) the character position + * behind the last character (the length of the input string) is returned. + * + * @param index index of the token for which to return the start position + * @return the start position of the token with the given index + * @throws java.lang.IndexOutOfBoundsException if index is negative + */ + int getTokenStart(int index); + + /** + * Returns the character length of the token with the given index. + * If the index is out of range (but not negative) the value 0 is returned. + * + * @param index index of the token for which to return the start position + * @return the character length of the token with the given index + * @throws java.lang.IndexOutOfBoundsException if index is negative + */ + int getTokenLength(int index); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java new file mode 100644 index 000000000..77dd1a511 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java @@ -0,0 +1,5327 @@ +/******************************************************************************* + * 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 + * channingwalton@mac.com - curved line code + * gilles.querret@free.fr - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=72995 + * Max Weninger (max.weninger@windriver.com) - Bug 131895 [Edit] Undo in compare + * Max Weninger (max.weninger@windriver.com) - Bug 72936 [Viewers] Show line numbers in comparision + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 178968 [Viewers] Lines scrambled and different font size in compare + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 191524 [Viewers] Synchronize horizontal scrolling by # characters, not % of longest line + * Stephan Herrmann (stephan@cs.tu-berlin.de) - Bug 291695: Element compare fails to use source range + *******************************************************************************/ +package org.eclipse.compare.contentmergeviewer; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareNavigator; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.ICompareNavigator; +import org.eclipse.compare.IEditableContentExtension; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.INavigatable; +import org.eclipse.compare.ISharedDocumentAdapter; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.SharedDocumentAdapter; +import org.eclipse.compare.internal.BufferedCanvas; +import org.eclipse.compare.internal.ChangePropertyAction; +import org.eclipse.compare.internal.CompareEditor; +import org.eclipse.compare.internal.CompareEditorSelectionProvider; +import org.eclipse.compare.internal.CompareHandlerService; +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.DocumentManager; +import org.eclipse.compare.internal.ICompareContextIds; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.IMergeViewerTestAdapter; +import org.eclipse.compare.internal.MergeSourceViewer; +import org.eclipse.compare.internal.MergeViewerContentProvider; +import org.eclipse.compare.internal.NavigationEndDialog; +import org.eclipse.compare.internal.OutlineViewerCreator; +import org.eclipse.compare.internal.ShowWhitespaceAction; +import org.eclipse.compare.internal.TextEditorPropertyAction; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.merge.DocumentMerger; +import org.eclipse.compare.internal.merge.DocumentMerger.Diff; +import org.eclipse.compare.internal.merge.DocumentMerger.IDocumentMergerInput; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.rangedifferencer.RangeDifference; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DocumentRangeNode; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.commands.operations.IOperationHistoryListener; +import org.eclipse.core.commands.operations.IUndoableOperation; +import org.eclipse.core.commands.operations.OperationHistoryEvent; +import org.eclipse.core.commands.operations.OperationHistoryFactory; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +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.MessageDialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.resource.ColorRegistry; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.CursorLinePainter; +import org.eclipse.jface.text.DefaultPositionUpdater; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentExtension3; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IDocumentPartitioner; +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.text.IPositionUpdater; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.IRewriteTarget; +import org.eclipse.jface.text.ITextPresentationListener; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.IViewportListener; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.text.TextViewer; +import org.eclipse.jface.text.source.CompositeRuler; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.util.Util; +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.SelectionChangedEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.custom.LineBackgroundEvent; +import org.eclipse.swt.custom.LineBackgroundListener; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.TypedListener; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IKeyBindingService; +import org.eclipse.ui.IPropertyListener; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.editors.text.IEncodingSupport; +import org.eclipse.ui.editors.text.IStorageDocumentProvider; +import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; +import org.eclipse.ui.texteditor.AbstractTextEditor; +import org.eclipse.ui.texteditor.ChainedPreferenceStore; +import org.eclipse.ui.texteditor.ChangeEncodingAction; +import org.eclipse.ui.texteditor.FindReplaceAction; +import org.eclipse.ui.texteditor.GotoLineAction; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProviderExtension; +import org.eclipse.ui.texteditor.IElementStateListener; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; +import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; + +import com.ibm.icu.text.MessageFormat; + +/** + * A text merge viewer uses the <code>RangeDifferencer</code> to perform a + * textual, line-by-line comparison of two (or three) input documents. + * It is based on the <code>ContentMergeViewer</code> and uses <code>TextViewer</code>s + * to implement the ancestor, left, and right content areas. + * <p> + * In the three-way compare case ranges of differing lines are highlighted and framed + * with different colors to show whether the difference is an incoming, outgoing, or conflicting change. + * The <code>TextMergeViewer</code> supports the notion of a current "differing range" + * and provides toolbar buttons to navigate from one range to the next (or previous). + * <p> + * If there is a current "differing range" and the underlying document is editable + * the <code>TextMergeViewer</code> enables actions in context menu and toolbar to + * copy a range from one side to the other side, thereby performing a merge operation. + * <p> + * In addition to a line-by-line comparison the <code>TextMergeViewer</code> + * uses a token based compare on differing lines. + * The token compare is activated when navigating into + * a range of differing lines. At first the lines are selected as a block. + * When navigating into this block the token compare shows for every line + * the differing token by selecting them. + * <p> + * The <code>TextMergeViewer</code>'s default token compare works on characters separated + * by whitespace. If a different strategy is needed (for example, Java tokens in + * a Java-aware merge viewer), clients can create their own token + * comparators by implementing the <code>ITokenComparator</code> interface and overriding the + * <code>TextMergeViewer.createTokenComparator</code> factory method). + * <p> + * Access to the <code>TextMergeViewer</code>'s model is by means of an + * <code>IMergeViewerContentProvider</code>. Its <code>get<it>X</it></code>Content</code> methods must return + * either an <code>IDocument</code>, an <code>IDocumentRange</code>, or an <code>IStreamContentAccessor</code>. + * In the <code>IDocumentRange</code> case the <code>TextMergeViewer</code> + * works on a subrange of a document. In the <code>IStreamContentAccessor</code> case + * a document is created internally and initialized from the stream. + * <p> + * A <code>TextMergeViewer</code> can be used as is. However clients may subclass + * to customize the behavior. For example a <code>MergeTextViewer</code> for Java would override + * the <code>configureTextViewer</code> method to configure the <code>TextViewer</code> for Java source code, + * the <code>createTokenComparator</code> method to create a Java specific tokenizer. + * <p> + * In 3.5 a new API has been introduced to let clients provide their own source + * viewers implementation with an option to configure them basing on a + * corresponding editor input. + * + * @see org.eclipse.compare.rangedifferencer.RangeDifferencer + * @see org.eclipse.jface.text.TextViewer + * @see ITokenComparator + * @see IDocumentRange + * @see org.eclipse.compare.IStreamContentAccessor + */ +public class TextMergeViewer extends ContentMergeViewer implements IAdaptable { + + private static final String COPY_LEFT_TO_RIGHT_INDICATOR = ">"; //$NON-NLS-1$ + private static final String COPY_RIGHT_TO_LEFT_INDICATOR = "<"; //$NON-NLS-1$ + private static final char ANCESTOR_CONTRIBUTOR = MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR; + private static final char RIGHT_CONTRIBUTOR = MergeViewerContentProvider.RIGHT_CONTRIBUTOR; + private static final char LEFT_CONTRIBUTOR = MergeViewerContentProvider.LEFT_CONTRIBUTOR; + + private static final String DIFF_RANGE_CATEGORY = CompareUIPlugin.PLUGIN_ID + ".DIFF_RANGE_CATEGORY"; //$NON-NLS-1$ + + static final boolean DEBUG= false; + + private static final boolean FIX_47640= true; + + private static final String[] GLOBAL_ACTIONS= { + ActionFactory.UNDO.getId(), + ActionFactory.REDO.getId(), + ActionFactory.CUT.getId(), + ActionFactory.COPY.getId(), + ActionFactory.PASTE.getId(), + ActionFactory.DELETE.getId(), + ActionFactory.SELECT_ALL.getId(), + ActionFactory.FIND.getId(), + ITextEditorActionDefinitionIds.LINE_GOTO + }; + private static final String[] TEXT_ACTIONS= { + MergeSourceViewer.UNDO_ID, + MergeSourceViewer.REDO_ID, + MergeSourceViewer.CUT_ID, + MergeSourceViewer.COPY_ID, + MergeSourceViewer.PASTE_ID, + MergeSourceViewer.DELETE_ID, + MergeSourceViewer.SELECT_ALL_ID, + MergeSourceViewer.FIND_ID, + MergeSourceViewer.GOTO_LINE_ID + }; + + private static final String BUNDLE_NAME= "org.eclipse.compare.contentmergeviewer.TextMergeViewerResources"; //$NON-NLS-1$ + + // the following symbolic constants must match the IDs in Compare's plugin.xml + private static final String INCOMING_COLOR= "INCOMING_COLOR"; //$NON-NLS-1$ + private static final String OUTGOING_COLOR= "OUTGOING_COLOR"; //$NON-NLS-1$ + private static final String CONFLICTING_COLOR= "CONFLICTING_COLOR"; //$NON-NLS-1$ + private static final String RESOLVED_COLOR= "RESOLVED_COLOR"; //$NON-NLS-1$ + + // constants + /** Width of left and right vertical bar */ + private static final int MARGIN_WIDTH= 6; + /** Width of center bar */ + private static final int CENTER_WIDTH= 34; + /** Width of birds eye view */ + private static final int BIRDS_EYE_VIEW_WIDTH= 12; + /** Width of birds eye view */ + private static final int BIRDS_EYE_VIEW_INSET= 2; + /** */ + private static final int RESOLVE_SIZE= 5; + + /** line width of change borders */ + private static final int LW= 1; + + // determines whether a change between left and right is considered incoming or outgoing + private boolean fLeftIsLocal; + private boolean fShowCurrentOnly= false; + private boolean fShowCurrentOnly2= false; + private int fMarginWidth= MARGIN_WIDTH; + private int fTopInset; + + // Colors + private RGB fBackground; + private RGB fForeground; + + private boolean fIsUsingSystemForeground= true; + private boolean fIsUsingSystemBackground= true; + + private RGB SELECTED_INCOMING; + private RGB INCOMING; + private RGB INCOMING_FILL; + private RGB INCOMING_TEXT_FILL; + + private RGB SELECTED_CONFLICT; + private RGB CONFLICT; + private RGB CONFLICT_FILL; + private RGB CONFLICT_TEXT_FILL; + + private RGB SELECTED_OUTGOING; + private RGB OUTGOING; + private RGB OUTGOING_FILL; + private RGB OUTGOING_TEXT_FILL; + + private RGB RESOLVED; + + private IPreferenceStore fPreferenceStore; + private IPropertyChangeListener fPreferenceChangeListener; + + private HashMap fNewAncestorRanges= new HashMap(); + private HashMap fNewLeftRanges= new HashMap(); + private HashMap fNewRightRanges= new HashMap(); + + private MergeSourceViewer fAncestor; + private MergeSourceViewer fLeft; + private MergeSourceViewer fRight; + + private int fLeftLineCount; + private int fRightLineCount; + + private boolean fInScrolling; + + private int fPts[]= new int[8]; // scratch area for polygon drawing + + private int fInheritedDirection; // inherited direction + private int fTextDirection; // requested direction for embedded SourceViewer + + private ActionContributionItem fIgnoreAncestorItem; + private boolean fHighlightRanges; + + private boolean fShowPseudoConflicts= false; + + private boolean fUseSplines= true; + private boolean fUseSingleLine= true; + private boolean fUseResolveUI= true; + private boolean fHighlightTokenChanges = false; + + private String fSymbolicFontName; + + private ActionContributionItem fNextDiff; // goto next difference + private ActionContributionItem fPreviousDiff; // goto previous difference + private ActionContributionItem fCopyDiffLeftToRightItem; + private ActionContributionItem fCopyDiffRightToLeftItem; + + private CompareHandlerService fHandlerService; + + private boolean fSynchronizedScrolling= true; + + private MergeSourceViewer fFocusPart; + + private boolean fSubDoc= true; + private IPositionUpdater fPositionUpdater; + private boolean fIsMotif; + private boolean fIsCarbon; + private boolean fIsMac; + + private boolean fHasErrors; + + + // SWT widgets + private BufferedCanvas fAncestorCanvas; + private BufferedCanvas fLeftCanvas; + private BufferedCanvas fRightCanvas; + private Canvas fScrollCanvas; + private ScrollBar fVScrollBar; + private Canvas fBirdsEyeCanvas; + private Canvas fSummaryHeader; + private HeaderPainter fHeaderPainter; + + // SWT resources to be disposed + private Map fColors; + private Cursor fBirdsEyeCursor; + + // points for center curves + private double[] fBasicCenterCurve; + + private Button fCenterButton; + private Diff fButtonDiff; + + private ContributorInfo fLeftContributor; + private ContributorInfo fRightContributor; + private ContributorInfo fAncestorContributor; + private int isRefreshing = 0; + private int fSynchronziedScrollPosition; + private ActionContributionItem fNextChange; + private ActionContributionItem fPreviousChange; + private ShowWhitespaceAction showWhitespaceAction; + private InternalOutlineViewerCreator fOutlineViewerCreator; + private TextEditorPropertyAction toggleLineNumbersAction; + private IFindReplaceTarget fFindReplaceTarget; + private ChangePropertyAction fIgnoreWhitespace; + private DocumentMerger fMerger; + /** The current diff */ + private Diff fCurrentDiff; + + // Bug 259362 - Update diffs after undo + private boolean copyOperationInProgress = false; + private IUndoableOperation copyUndoable = null; + private IOperationHistoryListener operationHistoryListener; + + /** + * Preference key for highlighting current line. + */ + private final static String CURRENT_LINE= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE; + /** + * Preference key for highlight color of current line. + */ + private final static String CURRENT_LINE_COLOR= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR; + + private List fSourceViewerDecorationSupport = new ArrayList(3); + // whether enhanced viewer configuration has been done + private boolean isConfigured = false; + private boolean fRedoDiff = false; + + private final class InternalOutlineViewerCreator extends OutlineViewerCreator implements ISelectionChangedListener { + public Viewer findStructureViewer(Viewer oldViewer, + ICompareInput input, Composite parent, + CompareConfiguration configuration) { + if (input != getInput()) + return null; + final Viewer v = CompareUI.findStructureViewer(oldViewer, input, parent, configuration); + if (v != null) { + v.getControl().addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + v.removeSelectionChangedListener(InternalOutlineViewerCreator.this); + } + }); + v.addSelectionChangedListener(this); + } + + return v; + } + + public boolean hasViewerFor(Object input) { + return true; + } + + public void selectionChanged(SelectionChangedEvent event) { + ISelection s = event.getSelection(); + if (s instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) s; + Object element = ss.getFirstElement(); + Diff diff = findDiff(element); + if (diff != null) + setCurrentDiff(diff, true); + } + } + + private Diff findDiff(Object element) { + if (element instanceof ICompareInput) { + ICompareInput ci = (ICompareInput) element; + Position p = getPosition(ci.getLeft()); + if (p != null) + return findDiff(p, true); + p = getPosition(ci.getRight()); + if (p != null) + return findDiff(p, false); + } + return null; + } + + private Diff findDiff(Position p, boolean left) { + for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + Position diffPos; + if (left) { + diffPos = diff.getPosition(LEFT_CONTRIBUTOR); + } else { + diffPos = diff.getPosition(RIGHT_CONTRIBUTOR); + } + // If the element falls within a diff, highlight that diff + if (diffPos.offset + diffPos.length >= p.offset && diff.getKind() != RangeDifference.NOCHANGE) + return diff; + // Otherwise, highlight the first diff after the elements position + if (diffPos.offset >= p.offset) + return diff; + } + return null; + } + + private Position getPosition(ITypedElement left) { + if (left instanceof DocumentRangeNode) { + DocumentRangeNode drn = (DocumentRangeNode) left; + return drn.getRange(); + } + return null; + } + + public Object getInput() { + return TextMergeViewer.this.getInput(); + } + } + + class ContributorInfo implements IElementStateListener, VerifyListener, IDocumentListener, IEncodingSupport { + private final TextMergeViewer fViewer; + private final Object fElement; + private char fLeg; + private String fEncoding; + private IDocumentProvider fDocumentProvider; + private IEditorInput fDocumentKey; + private ISelection fSelection; + private int fTopIndex = -1; + private boolean fNeedsValidation = false; + private MergeSourceViewer fSourceViewer; + + public ContributorInfo(TextMergeViewer viewer, Object element, char leg) { + fViewer = viewer; + fElement = element; + fLeg = leg; + if (fElement instanceof IEncodedStreamContentAccessor) { + try { + fEncoding = ((IEncodedStreamContentAccessor)fElement).getCharset(); + } catch (CoreException e) { + // silently ignored + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.editors.text.IEncodingSupport#setEncoding(java.lang.String) + */ + public void setEncoding(String encoding) { + if (fDocumentKey == null || fDocumentProvider == null) { + return; + } + if (fDocumentProvider instanceof IStorageDocumentProvider) { + IStorageDocumentProvider provider = (IStorageDocumentProvider) fDocumentProvider; + String current = provider.getEncoding(fDocumentKey); + boolean dirty = fDocumentProvider.canSaveDocument(fDocumentKey); + if (!dirty) { + String internal = encoding == null ? "" : encoding; //$NON-NLS-1$ + if (!internal.equals(current)) { + provider.setEncoding(fDocumentKey, encoding); + try { + fDocumentProvider.resetDocument(fDocumentKey); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } finally { + update(true); + updateStructure(fLeg); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.editors.text.IEncodingSupport#getEncoding() + */ + public String getEncoding() { + if (fDocumentProvider != null && fDocumentKey != null + && fDocumentProvider instanceof IStorageDocumentProvider) { + IStorageDocumentProvider provider = (IStorageDocumentProvider) fDocumentProvider; + return provider.getEncoding(fDocumentKey); + } + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.editors.text.IEncodingSupport#getDefaultEncoding() + */ + public String getDefaultEncoding() { + if (fDocumentProvider != null && fDocumentKey != null + && fDocumentProvider instanceof IStorageDocumentProvider) { + IStorageDocumentProvider provider = (IStorageDocumentProvider) fDocumentProvider; + return provider.getDefaultEncoding(); + } + return null; + } + + private String internalGetEncoding() { + if (fElement instanceof IEncodedStreamContentAccessor) { + try { + fEncoding = ((IEncodedStreamContentAccessor) fElement) + .getCharset(); + } catch (CoreException e) { + // silently ignored + } + } + if (fEncoding != null) { + return fEncoding; + } + return ResourcesPlugin.getEncoding(); + } + + public void setEncodingIfAbsent(ContributorInfo otherContributor) { + if (fEncoding == null) + fEncoding = otherContributor.fEncoding; + } + + public IDocument getDocument() { + if (fDocumentProvider != null) { + IDocument document = fDocumentProvider.getDocument(getDocumentKey()); + if (document != null) + return document; + } + if (fElement instanceof IDocument) + return (IDocument) fElement; + if (fElement instanceof IDocumentRange) + return ((IDocumentRange) fElement).getDocument(); + if (fElement instanceof IStreamContentAccessor) + return DocumentManager.get(fElement); + return null; + } + + public void setDocument(MergeSourceViewer viewer, boolean isEditable) { + // Ensure that this method is only called once + Assert.isTrue(fSourceViewer == null); + fSourceViewer = viewer; + try { + internalSetDocument(viewer); + } catch (RuntimeException e) { + // The error may be due to a stale entry in the DocumentManager (see bug 184489) + clearCachedDocument(); + throw e; + } + setEditable(viewer.getSourceViewer(), isEditable); + // Verify changes if the document is editable + if (isEditable) { + fNeedsValidation = true; + viewer.getSourceViewer().getTextWidget().addVerifyListener(this); + } + } + + /* + * Returns true if a new Document could be installed. + */ + private boolean internalSetDocument(MergeSourceViewer tp) { + + if (tp == null) + return false; + + IDocument newDocument = null; + Position range= null; + + if (fElement instanceof IDocumentRange) { + newDocument= ((IDocumentRange)fElement).getDocument(); + range= ((IDocumentRange)fElement).getRange(); + connectToSharedDocument(); + + } else if (fElement instanceof IDocument) { + newDocument= (IDocument) fElement; + + } else if (fElement instanceof IStreamContentAccessor) { + newDocument= DocumentManager.get(fElement); + if (newDocument == null) { + newDocument = createDocument(); + DocumentManager.put(fElement, newDocument); + setupDocument(newDocument); + } else if (fDocumentProvider == null) { + // Connect to a shared document so we can get the proper save synchronization + connectToSharedDocument(); + } + } else if (fElement == null) { // deletion on one side + + ITypedElement parent= this.fViewer.getParent(fLeg); // we try to find an insertion position within the deletion's parent + + if (parent instanceof IDocumentRange) { + newDocument= ((IDocumentRange)parent).getDocument(); + newDocument.addPositionCategory(DIFF_RANGE_CATEGORY); + Object input= this.fViewer.getInput(); + range= this.fViewer.getNewRange(fLeg, input); + if (range == null) { + int pos= 0; + if (input instanceof ICompareInput) + pos= this.fViewer.findInsertionPosition(fLeg, (ICompareInput)input); + range= new Position(pos, 0); + try { + newDocument.addPosition(DIFF_RANGE_CATEGORY, range); + } catch (BadPositionCategoryException ex) { + // silently ignored + if (TextMergeViewer.DEBUG) System.out.println("BadPositionCategoryException: " + ex); //$NON-NLS-1$ + } catch (BadLocationException ex) { + // silently ignored + if (TextMergeViewer.DEBUG) System.out.println("BadLocationException: " + ex); //$NON-NLS-1$ + } + this.fViewer.addNewRange(fLeg, input, range); + } + } else if (parent instanceof IDocument) { + newDocument= ((IDocumentRange)fElement).getDocument(); + } + } + + boolean enabled= true; + if (newDocument == null) { + newDocument= new Document(""); //$NON-NLS-1$ + enabled= false; + } + + // Update the viewer document or range + IDocument oldDoc= tp.getSourceViewer().getDocument(); + if (newDocument != oldDoc) { + updateViewerDocument(tp, newDocument, range); + } else { // same document but different range + updateViewerDocumentRange(tp, range); + } + newDocument.addDocumentListener(this); + + tp.setEnabled(enabled); + + return enabled; + } + + /* + * The viewer document is the same but the range has changed + */ + private void updateViewerDocumentRange(MergeSourceViewer tp, Position range) { + tp.setRegion(range); + if (this.fViewer.fSubDoc) { + if (range != null) { + IRegion r= this.fViewer.normalizeDocumentRegion(tp.getSourceViewer().getDocument(), TextMergeViewer.toRegion(range)); + tp.getSourceViewer().setVisibleRegion(r.getOffset(), r.getLength()); + } else + tp.getSourceViewer().resetVisibleRegion(); + } else + tp.getSourceViewer().resetVisibleRegion(); + } + + /* + * The viewer has a new document + */ + private void updateViewerDocument(MergeSourceViewer tp, IDocument document, Position range) { + unsetDocument(tp); + if (document == null) + return; + + connectPositionUpdater(document); + + // install new document + tp.setRegion(range); + if (this.fViewer.fSubDoc) { + if (range != null) { + IRegion r= this.fViewer.normalizeDocumentRegion(document, TextMergeViewer.toRegion(range)); + tp.getSourceViewer().setDocument(document, r.getOffset(), r.getLength()); + } else + tp.getSourceViewer().setDocument(document); + } else + tp.getSourceViewer().setDocument(document); + + tp.rememberDocument(document); + } + + void connectPositionUpdater(IDocument document) { + document.addPositionCategory(DIFF_RANGE_CATEGORY); + if (this.fViewer.fPositionUpdater == null) + this.fViewer.fPositionUpdater= this.fViewer.new ChildPositionUpdater(DIFF_RANGE_CATEGORY); + else + document.removePositionUpdater(this.fViewer.fPositionUpdater); + document.addPositionUpdater(this.fViewer.fPositionUpdater); + } + + private void unsetDocument(MergeSourceViewer tp) { + IDocument oldDoc= internalGetDocument(tp); + if (oldDoc != null) { + tp.rememberDocument(null); + try { + oldDoc.removePositionCategory(DIFF_RANGE_CATEGORY); + } catch (BadPositionCategoryException ex) { + // Ignore + } + if (fPositionUpdater != null) + oldDoc.removePositionUpdater(fPositionUpdater); + oldDoc.removeDocumentListener(this); + } + } + + private IDocument createDocument() { + // If the content provider is a text content provider, attempt to obtain + // a shared document (i.e. file buffer) + IDocument newDoc = connectToSharedDocument(); + + if (newDoc == null) { + IStreamContentAccessor sca= (IStreamContentAccessor) fElement; + String s= null; + + try { + String encoding = internalGetEncoding(); + s = Utilities.readString(sca, encoding); + } catch (CoreException ex) { + this.fViewer.setError(fLeg, ex.getMessage()); + } + + newDoc= new Document(s != null ? s : ""); //$NON-NLS-1$ + } + return newDoc; + } + + /** + * Connect to a shared document if possible. Return <code>null</code> + * if the connection was not possible. + * @return the shared document or <code>null</code> if connection to a + * shared document was not possible + */ + private IDocument connectToSharedDocument() { + IEditorInput key = getDocumentKey(); + if (key != null) { + if (fDocumentProvider != null) { + // We've already connected and setup the document + return fDocumentProvider.getDocument(key); + } + IDocumentProvider documentProvider = getDocumentProvider(); + if (documentProvider != null) { + try { + connect(documentProvider, key); + setCachedDocumentProvider(key, + documentProvider); + IDocument newDoc = documentProvider.getDocument(key); + this.fViewer.updateDirtyState(key, documentProvider, fLeg); + return newDoc; + } catch (CoreException e) { + // Connection failed. Log the error and continue without a shared document + CompareUIPlugin.log(e); + } + } + } + return null; + } + + private void connect(IDocumentProvider documentProvider, IEditorInput input) throws CoreException { + final ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement, ISharedDocumentAdapter.class); + if (sda != null) { + sda.connect(documentProvider, input); + } else { + documentProvider.connect(input); + } + } + + private void disconnect(IDocumentProvider provider, IEditorInput input) { + final ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement, ISharedDocumentAdapter.class); + if (sda != null) { + sda.disconnect(provider, input); + } else { + provider.disconnect(input); + } + } + + private void setCachedDocumentProvider(IEditorInput key, + IDocumentProvider documentProvider) { + fDocumentKey = key; + fDocumentProvider = documentProvider; + documentProvider.addElementStateListener(this); + } + + public void disconnect() { + IDocumentProvider provider = null; + IEditorInput input = getDocumentKey(); + synchronized(this) { + if (fDocumentProvider != null) { + provider = fDocumentProvider; + fDocumentProvider = null; + fDocumentKey = null; + } + } + if (provider != null) { + disconnect(provider, input); + provider.removeElementStateListener(this); + } + // If we have a listener registered with the widget, remove it + if (fSourceViewer != null && !fSourceViewer.getSourceViewer().getTextWidget().isDisposed()) { + if (fNeedsValidation) { + fSourceViewer.getSourceViewer().getTextWidget().removeVerifyListener(this); + fNeedsValidation = false; + } + IDocument oldDoc= internalGetDocument(fSourceViewer); + if (oldDoc != null) { + oldDoc.removeDocumentListener(this); + } + } + clearCachedDocument(); + } + + private void clearCachedDocument() { + // Finally, remove the document from the document manager + IDocument doc = DocumentManager.get(fElement); + if (doc != null) + DocumentManager.remove(doc); + } + + private IDocument internalGetDocument(MergeSourceViewer tp) { + IDocument oldDoc= tp.getSourceViewer().getDocument(); + if (oldDoc == null) { + oldDoc= tp.getRememberedDocument(); + } + return oldDoc; + } + + /** + * Return the document key used to obtain a shared document. A <code>null</code> + * is returned in the following cases: + * <ol> + * <li>This contributor does not have a shared document adapter.</li> + * <li>This text merge viewer has a document partitioner but uses the default partitioning.</li> + * <li>This text merge viewer does not use he default content provider.</li> + * </ol> + * @return the document key used to obtain a shared document or <code>null</code> + */ + private IEditorInput getDocumentKey() { + if (fDocumentKey != null) + return fDocumentKey; + if (isUsingDefaultContentProvider() && fElement != null && canHaveSharedDocument()) { + ISharedDocumentAdapter sda = (ISharedDocumentAdapter)Utilities.getAdapter(fElement, ISharedDocumentAdapter.class, true); + if (sda != null) { + return sda.getDocumentKey(fElement); + } + } + return null; + } + + private IDocumentProvider getDocumentProvider() { + if (fDocumentProvider != null) + return fDocumentProvider; + // We will only use document providers if the content provider is the + // default content provider + if (isUsingDefaultContentProvider()) { + IEditorInput input = getDocumentKey(); + if (input != null) + return SharedDocumentAdapter.getDocumentProvider(input); + } + return null; + } + + private boolean isUsingDefaultContentProvider() { + return fViewer.isUsingDefaultContentProvider(); + } + + private boolean canHaveSharedDocument() { + return fViewer.canHaveSharedDocument(); + } + + boolean hasSharedDocument(Object object) { + return (fElement == object && + fDocumentProvider != null + && fDocumentProvider.getDocument(getDocumentKey()) != null); + } + + public boolean flush() throws CoreException { + if (fDocumentProvider != null) { + IEditorInput input = getDocumentKey(); + IDocument document = fDocumentProvider.getDocument(input); + if (document != null) { + final ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement, ISharedDocumentAdapter.class); + if (sda != null) { + sda.flushDocument(fDocumentProvider, input, document, false); + return true; + } + try { + fDocumentProvider.aboutToChange(input); + fDocumentProvider.saveDocument(new NullProgressMonitor(), input, document, false); + return true; + } finally { + fDocumentProvider.changed(input); + } + } + } + return false; + } + + public void elementMoved(Object originalElement, Object movedElement) { + IEditorInput input = getDocumentKey(); + if (input != null && input.equals(originalElement)) { + // This method will only get called if the buffer is not dirty + resetDocument(); + } + } + public void elementDirtyStateChanged(Object element, boolean isDirty) { + if (!checkState()) + return; + IEditorInput input = getDocumentKey(); + if (input != null && input.equals(element)) { + this.fViewer.updateDirtyState(input, getDocumentProvider(), fLeg); + } + } + + public void elementDeleted(Object element) { + IEditorInput input = getDocumentKey(); + if (input != null && input.equals(element)) { + // This method will only get called if the buffer is not dirty + resetDocument(); + } + } + private void resetDocument() { + // Need to remove the document from the manager before refreshing + // or the old document will still be found + clearCachedDocument(); + // TODO: This is fine for now but may need to be revisited if a refresh is performed + // higher up as well (e.g. perhaps a refresh request that waits until after all parties + // have been notified). + if (checkState()) + fViewer.refresh(); + } + + private boolean checkState() { + if (fViewer == null) + return false; + Control control = fViewer.getControl(); + if (control == null) + return false; + return !control.isDisposed(); + } + + public void elementContentReplaced(Object element) { + if (!checkState()) + return; + IEditorInput input = getDocumentKey(); + if (input != null && input.equals(element)) { + this.fViewer.updateDirtyState(input, getDocumentProvider(), fLeg); + + // recalculate diffs and update controls + new UIJob(CompareMessages.DocumentMerger_0) { + public IStatus runInUIThread(IProgressMonitor monitor) { + update(true); + updateStructure(fLeg); + return Status.OK_STATUS; + } + }.schedule(); + } + } + public void elementContentAboutToBeReplaced(Object element) { + // Nothing to do + } + + public Object getElement() { + return fElement; + } + + public void cacheSelection(MergeSourceViewer viewer) { + if (viewer == null) { + this.fSelection = null; + this.fTopIndex = -1; + } else { + this.fSelection = viewer.getSourceViewer().getSelection(); + this.fTopIndex = viewer.getSourceViewer().getTopIndex(); + } + } + + public void updateSelection(MergeSourceViewer viewer, boolean includeScroll) { + if (fSelection != null) + viewer.getSourceViewer().setSelection(fSelection); + if (includeScroll && fTopIndex != -1) { + viewer.getSourceViewer().setTopIndex(fTopIndex); + } + } + + public void transferContributorStateFrom( + ContributorInfo oldContributor) { + if (oldContributor != null) { + fSelection = oldContributor.fSelection; + fTopIndex = oldContributor.fTopIndex; + fEncoding = oldContributor.fEncoding; + } + + } + + public boolean validateChange() { + if (fElement == null) + return true; + if (fDocumentProvider instanceof IDocumentProviderExtension) { + IDocumentProviderExtension ext = (IDocumentProviderExtension)fDocumentProvider; + if (ext.isReadOnly(fDocumentKey)) { + try { + ext.validateState(fDocumentKey, getControl().getShell()); + ext.updateStateCache(fDocumentKey); + } catch (CoreException e) { + ErrorDialog.openError(getControl().getShell(), CompareMessages.TextMergeViewer_12, CompareMessages.TextMergeViewer_13, e.getStatus()); + return false; + } + } + return !ext.isReadOnly(fDocumentKey); + } + IEditableContentExtension ext = (IEditableContentExtension)Utilities.getAdapter(fElement, IEditableContentExtension.class); + if (ext != null) { + if (ext.isReadOnly()) { + IStatus status = ext.validateEdit(getControl().getShell()); + if (!status.isOK()) { + if (status.getSeverity() == IStatus.ERROR) { + ErrorDialog.openError(getControl().getShell(), CompareMessages.TextMergeViewer_14, CompareMessages.TextMergeViewer_15, status); + return false; + } + if (status.getSeverity() == IStatus.CANCEL) + return false; + } + } + } + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.swt.events.VerifyListener#verifyText(org.eclipse.swt.events.VerifyEvent) + */ + public void verifyText(VerifyEvent e) { + if (!validateChange()) { + e.doit= false; + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent) + */ + public void documentAboutToBeChanged(DocumentEvent e) { + // nothing to do + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent) + */ + public void documentChanged(DocumentEvent e) { + boolean dirty = true; + if (fDocumentProvider != null && fDocumentKey != null) { + dirty = fDocumentProvider.canSaveDocument(fDocumentKey); + } + TextMergeViewer.this.documentChanged(e, dirty); + // Remove our verify listener since the document is now dirty + if (fNeedsValidation && fSourceViewer != null && !fSourceViewer.getSourceViewer().getTextWidget().isDisposed()) { + fSourceViewer.getSourceViewer().getTextWidget().removeVerifyListener(this); + fNeedsValidation = false; + } + } + } + + class HeaderPainter implements PaintListener { + + private static final int INSET= BIRDS_EYE_VIEW_INSET; + + private RGB fIndicatorColor; + private Color fSeparatorColor; + + public HeaderPainter() { + fSeparatorColor= fSummaryHeader.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + } + + /* + * Returns true on color change + */ + public boolean setColor(RGB color) { + RGB oldColor= fIndicatorColor; + fIndicatorColor= color; + if (color == null) + return oldColor != null; + if (oldColor != null) + return !color.equals(oldColor); + return true; + } + + private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topLeft, Color bottomRight) { + gc.setForeground(topLeft); + gc.drawLine(x, y, x + w -1, y); + gc.drawLine(x, y, x, y + h -1); + + gc.setForeground(bottomRight); + gc.drawLine(x + w, y, x + w, y + h); + gc.drawLine(x, y + h, x + w, y + h); + } + + public void paintControl(PaintEvent e) { + + Point s= fSummaryHeader.getSize(); + + if (fIndicatorColor != null) { + Display d= fSummaryHeader.getDisplay(); + e.gc.setBackground(getColor(d, fIndicatorColor)); + int min= Math.min(s.x, s.y)-2*INSET; + Rectangle r= new Rectangle((s.x-min)/2, (s.y-min)/2, min, min); + e.gc.fillRectangle(r); + if (d != null) + drawBevelRect(e.gc, r.x, r.y, r.width -1, r.height -1, d.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW), d.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + + e.gc.setForeground(fSeparatorColor); + e.gc.setLineWidth(0 /* 1 */); + e.gc.drawLine(0+1, s.y-1, s.x-1-1, s.y-1); + } + } + } + + /* + * The position updater used to adapt the positions representing + * the child document ranges to changes of the parent document. + */ + class ChildPositionUpdater extends DefaultPositionUpdater { + + /* + * Creates the position updated. + */ + protected ChildPositionUpdater(String category) { + super(category); + } + + /* + * Child document ranges cannot be deleted other then by calling + * freeChildDocument. + */ + protected boolean notDeleted() { + return true; + } + + /* + * If an insertion happens at a child document's start offset, the + * position is extended rather than shifted. Also, if something is added + * right behind the end of the position, the position is extended rather + * than kept stable. + */ + protected void adaptToInsert() { + + if (fPosition == fLeft.getRegion() || fPosition == fRight.getRegion()) { + int myStart= fPosition.offset; + int myEnd= fPosition.offset + fPosition.length; + myEnd= Math.max(myStart, myEnd); + + int yoursStart= fOffset; + int yoursEnd= fOffset + fReplaceLength -1; + yoursEnd= Math.max(yoursStart, yoursEnd); + + if (myEnd < yoursStart) + return; + + if (myStart <= yoursStart) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + } else { + super.adaptToInsert(); + } + } + } + + private class ChangeHighlighter implements ITextPresentationListener { + + private final MergeSourceViewer viewer; + + public ChangeHighlighter(MergeSourceViewer viewer) { + this.viewer = viewer; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation) + */ + public void applyTextPresentation(TextPresentation textPresentation) { + if (!fHighlightTokenChanges) + return; + IRegion region= textPresentation.getExtent(); + Diff[] changeDiffs = fMerger.getChangeDiffs(getLeg(viewer), region); + for (int i = 0; i < changeDiffs.length; i++) { + Diff diff = changeDiffs[i]; + StyleRange range = getStyleRange(diff, region); + if (range != null) + textPresentation.mergeStyleRange(range); + } + } + + private StyleRange getStyleRange(Diff diff, IRegion region) { + //Color cText = getColor(null, getTextColor()); + Color cTextFill = getColor(null, getTextFillColor(diff)); + if (cTextFill == null) + return null; + Position p = diff.getPosition(getLeg(viewer)); + int start = p.getOffset(); + int length = p.getLength(); + // Don't start before the region + if (start < region.getOffset()) { + length = length - (region.getOffset() - start); + start = region.getOffset(); + } + // Don't go past the end of the region + int regionEnd = region.getOffset() + region.getLength(); + if (start + length > regionEnd) { + length = regionEnd - start; + } + if (length < 0) + return null; + + return new StyleRange(start, length, null, cTextFill); + } + + private RGB getTextFillColor(Diff diff) { + if (isThreeWay() && !isIgnoreAncestor()) { + switch (diff.getKind()) { + case RangeDifference.RIGHT: + if (fLeftIsLocal) + return INCOMING_TEXT_FILL; + return OUTGOING_TEXT_FILL; + case RangeDifference.ANCESTOR: + return CONFLICT_TEXT_FILL; + case RangeDifference.LEFT: + if (fLeftIsLocal) + return OUTGOING_TEXT_FILL; + return INCOMING_TEXT_FILL; + case RangeDifference.CONFLICT: + return CONFLICT_TEXT_FILL; + } + return null; + } + return OUTGOING_TEXT_FILL; + } + + } + + private class FindReplaceTarget implements IFindReplaceTarget { + + public boolean canPerformFind() { + return fFocusPart != null; + } + + public int findAndSelect(int widgetOffset, String findString, + boolean searchForward, boolean caseSensitive, boolean wholeWord) { + return fFocusPart.getSourceViewer().getFindReplaceTarget().findAndSelect(widgetOffset, findString, searchForward, caseSensitive, wholeWord); + } + + public Point getSelection() { + return fFocusPart.getSourceViewer().getFindReplaceTarget().getSelection(); + } + + public String getSelectionText() { + return fFocusPart.getSourceViewer().getFindReplaceTarget().getSelectionText(); + } + + public boolean isEditable() { + return fFocusPart.getSourceViewer().getFindReplaceTarget().isEditable(); + } + + public void replaceSelection(String text) { + fFocusPart.getSourceViewer().getFindReplaceTarget().replaceSelection(text); + } + + } + + //---- MergeTextViewer + + /** + * Creates a text merge viewer under the given parent control. + * + * @param parent the parent control + * @param configuration the configuration object + */ + public TextMergeViewer(Composite parent, CompareConfiguration configuration) { + this(parent, SWT.NULL, configuration); + } + + /** + * Creates a text merge viewer under the given parent control. + * + * @param parent the parent control + * @param style SWT style bits for top level composite of this viewer + * @param configuration the configuration object + */ + public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) { + super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration); + + operationHistoryListener = new IOperationHistoryListener() { + public void historyNotification(OperationHistoryEvent event) { + TextMergeViewer.this.historyNotification(event); + } + }; + OperationHistoryFactory.getOperationHistory() + .addOperationHistoryListener(operationHistoryListener); + + fMerger = new DocumentMerger(new IDocumentMergerInput() { + public ITokenComparator createTokenComparator(String line) { + return TextMergeViewer.this.createTokenComparator(line); + } + public CompareConfiguration getCompareConfiguration() { + return TextMergeViewer.this.getCompareConfiguration(); + } + public IDocument getDocument(char contributor) { + switch (contributor) { + case LEFT_CONTRIBUTOR: + return fLeft.getSourceViewer().getDocument(); + case RIGHT_CONTRIBUTOR: + return fRight.getSourceViewer().getDocument(); + case ANCESTOR_CONTRIBUTOR: + return fAncestor.getSourceViewer().getDocument(); + } + return null; + } + public int getHunkStart() { + return TextMergeViewer.this.getHunkStart(); + } + public Position getRegion(char contributor) { + switch (contributor) { + case LEFT_CONTRIBUTOR: + return fLeft.getRegion(); + case RIGHT_CONTRIBUTOR: + return fRight.getRegion(); + case ANCESTOR_CONTRIBUTOR: + return fAncestor.getRegion(); + } + return null; + } + public boolean isHunkOnLeft() { + ITypedElement left = ((ICompareInput)getInput()).getRight(); + return left != null && Utilities.getAdapter(left, IHunk.class) != null; + } + public boolean isIgnoreAncestor() { + return TextMergeViewer.this.isIgnoreAncestor(); + } + public boolean isPatchHunk() { + return TextMergeViewer.this.isPatchHunk(); + } + + public boolean isShowPseudoConflicts() { + return fShowPseudoConflicts; + } + public boolean isThreeWay() { + return TextMergeViewer.this.isThreeWay(); + } + public boolean isPatchHunkOk() { + return TextMergeViewer.this.isPatchHunkOk(); + } + + }); + + int inheritedStyle= parent.getStyle(); + if ((inheritedStyle & SWT.LEFT_TO_RIGHT) != 0) + fInheritedDirection= SWT.LEFT_TO_RIGHT; + else if ((inheritedStyle & SWT.RIGHT_TO_LEFT) != 0) + fInheritedDirection= SWT.RIGHT_TO_LEFT; + else + fInheritedDirection= SWT.NONE; + + if ((style & SWT.LEFT_TO_RIGHT) != 0) + fTextDirection= SWT.LEFT_TO_RIGHT; + else if ((style & SWT.RIGHT_TO_LEFT) != 0) + fTextDirection= SWT.RIGHT_TO_LEFT; + else + fTextDirection= SWT.NONE; + + fSymbolicFontName= getSymbolicFontName(); + + fIsMotif= Util.isMotif(); + fIsCarbon= Util.isCarbon(); + fIsMac= Util.isMac(); + + if (fIsMotif) + fMarginWidth= 0; + + fPreferenceChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + TextMergeViewer.this.handlePropertyChangeEvent(event); + } + }; + + fPreferenceStore= createChainedPreferenceStore(); + if (fPreferenceStore != null) { + fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener); + + fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$ + fSynchronizedScrolling= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING); + fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS); + //fUseSplines= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES); + fUseSingleLine= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE); + fHighlightTokenChanges= fPreferenceStore.getBoolean(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES); + //fUseResolveUI= fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI); + } + + buildControl(parent); + + setColors(); + + INavigatable nav= new INavigatable() { + public boolean selectChange(int flag) { + if (flag == INavigatable.FIRST_CHANGE || flag == INavigatable.LAST_CHANGE) { + selectFirstDiff(flag == INavigatable.FIRST_CHANGE); + return false; + } + return navigate(flag == INavigatable.NEXT_CHANGE, false, false); + } + public Object getInput() { + return TextMergeViewer.this.getInput(); + } + public boolean openSelectedChange() { + return false; + } + public boolean hasChange(int flag) { + return getNextVisibleDiff(flag == INavigatable.NEXT_CHANGE, false) != null; + } + }; + fComposite.setData(INavigatable.NAVIGATOR_PROPERTY, nav); + + fBirdsEyeCursor= new Cursor(parent.getDisplay(), SWT.CURSOR_HAND); + + JFaceResources.getFontRegistry().addListener(fPreferenceChangeListener); + JFaceResources.getColorRegistry().addListener(fPreferenceChangeListener); + updateFont(); + } + + private ChainedPreferenceStore createChainedPreferenceStore() { + ArrayList stores= new ArrayList(2); + stores.add(getCompareConfiguration().getPreferenceStore()); + stores.add(EditorsUI.getPreferenceStore()); + return new ChainedPreferenceStore((IPreferenceStore[]) stores.toArray(new IPreferenceStore[stores.size()])); + } + + /** + * Creates a color from the information stored in the given preference store. + * Returns <code>null</code> if there is no such information available. + * @param store preference store + * @param key preference key + * @return the color or <code>null</code> + */ + private static RGB createColor(IPreferenceStore store, String key) { + if (!store.contains(key)) + return null; + if (store.isDefault(key)) + return PreferenceConverter.getDefaultColor(store, key); + return PreferenceConverter.getColor(store, key); + } + + private void setColors() { + fIsUsingSystemBackground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT); + if (!fIsUsingSystemBackground) + fBackground= createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND); + + fIsUsingSystemForeground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT); + if (!fIsUsingSystemForeground) + fForeground= createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND); + + updateColors(null); + } + + private String getSymbolicFontName() { + Class clazz= getClass(); + do { + String fontName= clazz.getName(); + if (JFaceResources.getFontRegistry().hasValueFor(fontName)) + return fontName; + clazz= clazz.getSuperclass(); + } while (clazz != null); + // use text compare font if no font has been registered for subclass + return getClass().getName(); + } + + private void updateFont() { + Font f= JFaceResources.getFont(fSymbolicFontName); + if (f != null) { + if (fAncestor != null) + fAncestor.setFont(f); + if (fLeft != null) + fLeft.setFont(f); + if (fRight != null) + fRight.setFont(f); + } + } + + private void checkForColorUpdate(Display display) { + if (fIsUsingSystemBackground) { + RGB bg= display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB(); + if (!bg.equals(getBackground(display))) { + updateColors(display); + } + } + } + + /** + * Sets the viewer's background color to the given RGB value. + * If the value is <code>null</code> the system's default background color is used. + * @param background the background color or <code>null</code> to use the system's default background color + * @since 2.0 + */ + public void setBackgroundColor(RGB background) { + fIsUsingSystemBackground= (background == null); + fBackground= background; + updateColors(null); + } + + private RGB getBackground(Display display) { + if (fBackground != null) + return fBackground; + + if (display == null) + display= fComposite.getDisplay(); + + return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB(); + } + + /** + * Sets the viewer's foreground color to the given RGB value. + * If the value is <code>null</code> the system's default foreground color is used. + * @param foreground the foreground color or <code>null</code> to use the system's default foreground color + * @since 2.0 + */ + public void setForegroundColor(RGB foreground) { + fIsUsingSystemForeground= (foreground == null); + fForeground= foreground; + updateColors(null); + } + + private void updateColors(Display display) { + + if (display == null) + display = fComposite.getDisplay(); + + Color bgColor = null; + if (fBackground != null) + bgColor = getColor(display, fBackground); + + if (fAncestor != null) + fAncestor.setBackgroundColor(bgColor); + if (fLeft != null) + fLeft.setBackgroundColor(bgColor); + if (fRight != null) + fRight.setBackgroundColor(bgColor); + + Color fgColor = null; + if (fForeground != null) + fgColor = getColor(display, fForeground); + + if (fAncestor != null) + fAncestor.setForegroundColor(fgColor); + if (fLeft != null) + fLeft.setForegroundColor(fgColor); + if (fRight != null) + fRight.setForegroundColor(fgColor); + + ColorRegistry registry= JFaceResources.getColorRegistry(); + + RGB bg= getBackground(display); + SELECTED_INCOMING= registry.getRGB(INCOMING_COLOR); + if (SELECTED_INCOMING == null) + SELECTED_INCOMING= new RGB(0, 0, 255); // BLUE + INCOMING= interpolate(SELECTED_INCOMING, bg, 0.6); + INCOMING_FILL= interpolate(SELECTED_INCOMING, bg, 0.97); + INCOMING_TEXT_FILL= interpolate(SELECTED_INCOMING, bg, 0.85); + + SELECTED_OUTGOING= registry.getRGB(OUTGOING_COLOR); + if (SELECTED_OUTGOING == null) + SELECTED_OUTGOING= new RGB(0, 0, 0); // BLACK + OUTGOING= interpolate(SELECTED_OUTGOING, bg, 0.6); + OUTGOING_FILL= interpolate(SELECTED_OUTGOING, bg, 0.97); + OUTGOING_TEXT_FILL= interpolate(SELECTED_OUTGOING, bg, 0.85); + + SELECTED_CONFLICT= registry.getRGB(CONFLICTING_COLOR); + if (SELECTED_CONFLICT == null) + SELECTED_CONFLICT= new RGB(255, 0, 0); // RED + CONFLICT= interpolate(SELECTED_CONFLICT, bg, 0.6); + CONFLICT_FILL= interpolate(SELECTED_CONFLICT, bg, 0.97); + CONFLICT_TEXT_FILL= interpolate(SELECTED_CONFLICT, bg, 0.85); + + RESOLVED= registry.getRGB(RESOLVED_COLOR); + if (RESOLVED == null) + RESOLVED= new RGB(0, 255, 0); // GREEN + + updatePresentation(); + } + + private void updatePresentation() { + refreshBirdsEyeView(); + invalidateLines(); + invalidateTextPresentation(); + } + + /** + * Invalidates the current presentation by invalidating the three text viewers. + * @since 2.0 + */ + public void invalidateTextPresentation() { + if (fAncestor != null) + fAncestor.getSourceViewer().invalidateTextPresentation(); + if (fLeft != null) + fLeft.getSourceViewer().invalidateTextPresentation(); + if (fRight != null) + fRight.getSourceViewer().invalidateTextPresentation(); + } + + /** + * Configures the passed text viewer. This method is called after the three + * text viewers have been created for the content areas. The + * <code>TextMergeViewer</code> implementation of this method will + * configure the viewer with a {@link SourceViewerConfiguration}. + * Subclasses may reimplement to provide a specific configuration for the + * text viewer. + * + * @param textViewer + * the text viewer to configure + */ + protected void configureTextViewer(TextViewer textViewer) { + // to get undo for all text files, bugzilla 131895, 33665 + if(textViewer instanceof ISourceViewer){ + SourceViewerConfiguration configuration= new SourceViewerConfiguration(); + ((ISourceViewer)textViewer).configure(configuration); + } + } + + /** + * Creates an <code>ITokenComparator</code> which is used to show the + * intra line differences. + * The <code>TextMergeViewer</code> implementation of this method returns a + * tokenizer that breaks a line into words separated by whitespace. + * Subclasses may reimplement to provide a specific tokenizer. + * @param line the line for which to create the <code>ITokenComparator</code> + * @return a ITokenComparator which is used for a second level token compare. + */ + protected ITokenComparator createTokenComparator(String line) { + return new TokenComparator(line); + } + + /** + * Setup the given document for use with this viewer. By default, + * the partitioner returned from {@link #getDocumentPartitioner()} + * is registered as the default partitioner for the document. + * Subclasses that return a partitioner must also override + * {@link #getDocumentPartitioning()} if they wish to be able to use shared + * documents (i.e. file buffers). + * @param document the document to be set up + * + * @since 3.3 + */ + protected void setupDocument(IDocument document) { + String partitioning = getDocumentPartitioning(); + if (partitioning == null || !(document instanceof IDocumentExtension3)) { + if (document.getDocumentPartitioner() == null) { + IDocumentPartitioner partitioner= getDocumentPartitioner(); + if (partitioner != null) { + document.setDocumentPartitioner(partitioner); + partitioner.connect(document); + } + } + } else { + IDocumentExtension3 ex3 = (IDocumentExtension3) document; + if (ex3.getDocumentPartitioner(partitioning) == null) { + IDocumentPartitioner partitioner= getDocumentPartitioner(); + if (partitioner != null) { + ex3.setDocumentPartitioner(partitioning, partitioner); + partitioner.connect(document); + } + } + } + } + + /** + * Returns a document partitioner which is suitable for the underlying content type. + * This method is only called if the input provided by the content provider is a + * <code>IStreamContentAccessor</code> and an internal document must be obtained. This + * document is initialized with the partitioner returned from this method. + * <p> + * The <code>TextMergeViewer</code> implementation of this method returns + * <code>null</code>. Subclasses may reimplement to create a partitioner for a + * specific content type. Subclasses that do return a partitioner should also + * return a partitioning from {@link #getDocumentPartitioning()} in order to make + * use of shared documents (e.g. file buffers). + * + * @return a document partitioner, or <code>null</code> + */ + protected IDocumentPartitioner getDocumentPartitioner() { + return null; + } + + /** + * Return the partitioning to which the partitioner returned from + * {@link #getDocumentPartitioner()} is to be associated. Return <code>null</code> + * only if partitioning is not needed or if the subclass + * overrode {@link #setupDocument(IDocument)} directly. + * By default, <code>null</code> is returned which means that shared + * documents that return a partitioner from {@link #getDocumentPartitioner()} + * will not be able to use shared documents. + * @see IDocumentExtension3 + * @return a partitioning + * + * @since 3.3 + */ + protected String getDocumentPartitioning() { + return null; + } + + /** + * Called on the viewer disposal. + * Unregisters from the compare configuration. + * Clients may extend if they have to do additional cleanup. + * @param event + */ + protected void handleDispose(DisposeEvent event) { + OperationHistoryFactory.getOperationHistory() + .removeOperationHistoryListener(operationHistoryListener); + + if (fHandlerService != null) + fHandlerService.dispose(); + + Object input= getInput(); + removeFromDocumentManager(ANCESTOR_CONTRIBUTOR, input); + removeFromDocumentManager(LEFT_CONTRIBUTOR, input); + removeFromDocumentManager(RIGHT_CONTRIBUTOR, input); + + if (DEBUG) + DocumentManager.dump(); + + if (fPreferenceChangeListener != null) { + JFaceResources.getFontRegistry().removeListener(fPreferenceChangeListener); + JFaceResources.getColorRegistry().removeListener(fPreferenceChangeListener); + if (fPreferenceStore != null) + fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener); + fPreferenceChangeListener= null; + } + + fLeftCanvas= null; + fRightCanvas= null; + fVScrollBar= null; + fBirdsEyeCanvas= null; + fSummaryHeader= null; + + fAncestorContributor.unsetDocument(fAncestor); + fLeftContributor.unsetDocument(fLeft); + fRightContributor.unsetDocument(fRight); + + disconnect(fLeftContributor); + disconnect(fRightContributor); + disconnect(fAncestorContributor); + + if (fBirdsEyeCursor != null) { + fBirdsEyeCursor.dispose(); + fBirdsEyeCursor= null; + } + + if (showWhitespaceAction != null) + showWhitespaceAction.dispose(); + + if (toggleLineNumbersAction != null) + toggleLineNumbersAction.dispose(); + + if (fIgnoreWhitespace != null) + fIgnoreWhitespace.dispose(); + + if (fSourceViewerDecorationSupport != null) { + for (Iterator iterator = fSourceViewerDecorationSupport.iterator(); iterator.hasNext();) { + ((SourceViewerDecorationSupport) iterator.next()).dispose(); + } + fSourceViewerDecorationSupport = null; + } + + + if (fAncestor != null) + fAncestor.dispose(); + fAncestor = null; + if (fLeft != null) + fLeft.dispose(); + fLeft = null; + if (fRight != null) + fRight.dispose(); + fRight = null; + + if (fColors != null) { + Iterator i= fColors.values().iterator(); + while (i.hasNext()) { + Color color= (Color) i.next(); + if (!color.isDisposed()) + color.dispose(); + } + fColors= null; + } + // don't add anything here, disposing colors should be done last + super.handleDispose(event); + } + + private void disconnect(ContributorInfo legInfo) { + if (legInfo != null) + legInfo.disconnect(); + } + + //------------------------------------------------------------------------------------------------------------- + //--- internal ------------------------------------------------------------------------------------------------ + //------------------------------------------------------------------------------------------------------------- + + /* + * Creates the specific SWT controls for the content areas. + * Clients must not call or override this method. + */ + protected void createControls(Composite composite) { + + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.TEXT_MERGE_VIEW); + + // 1st row + if (fMarginWidth > 0) { + fAncestorCanvas= new BufferedCanvas(composite, SWT.NONE) { + public void doPaint(GC gc) { + paintSides(gc, fAncestor, fAncestorCanvas, false); + } + }; + fAncestorCanvas.addMouseListener( + new MouseAdapter() { + public void mouseDown(MouseEvent e) { + setCurrentDiff2(handleMouseInSides(fAncestorCanvas, fAncestor, e.y), false); + } + } + ); + } + + fAncestor= createPart(composite); + setEditable(fAncestor.getSourceViewer(), false); + fAncestor.getSourceViewer().getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_ancestor, getCompareConfiguration().getAncestorLabel(getInput())); + } + }); + fAncestor.getSourceViewer().addTextPresentationListener(new ChangeHighlighter(fAncestor)); + + fSummaryHeader= new Canvas(composite, SWT.NONE); + fHeaderPainter= new HeaderPainter(); + fSummaryHeader.addPaintListener(fHeaderPainter); + updateResolveStatus(); + + // 2nd row + if (fMarginWidth > 0) { + fLeftCanvas= new BufferedCanvas(composite, SWT.NONE) { + public void doPaint(GC gc) { + paintSides(gc, fLeft, fLeftCanvas, false); + } + }; + fLeftCanvas.addMouseListener( + new MouseAdapter() { + public void mouseDown(MouseEvent e) { + setCurrentDiff2(handleMouseInSides(fLeftCanvas, fLeft, e.y), false); + } + } + ); + } + + fLeft= createPart(composite); + fLeft.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling); + fLeft.getSourceViewer().getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_left, getCompareConfiguration().getLeftLabel(getInput())); + } + }); + fLeft.getSourceViewer().addTextPresentationListener(new ChangeHighlighter(fLeft)); + + fRight= createPart(composite); + fRight.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling); + fRight.getSourceViewer().getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_right, getCompareConfiguration().getRightLabel(getInput())); + } + }); + fRight.getSourceViewer().addTextPresentationListener(new ChangeHighlighter(fRight)); + + IWorkbenchPart part = getCompareConfiguration().getContainer().getWorkbenchPart(); + // part is not available for contexts different than editor + if (part != null) { + ISelectionProvider selectionProvider = part.getSite().getSelectionProvider(); + if (selectionProvider instanceof CompareEditorSelectionProvider) { + CompareEditorSelectionProvider cesp = (CompareEditorSelectionProvider) selectionProvider; + SourceViewer focusSourceViewer = fFocusPart == null ? null : fFocusPart.getSourceViewer(); + cesp.setViewers(new SourceViewer[] { fLeft.getSourceViewer(), fRight.getSourceViewer(), fAncestor.getSourceViewer() }, focusSourceViewer); + } + } + + hsynchViewport(fAncestor.getSourceViewer(), fLeft.getSourceViewer(), fRight.getSourceViewer()); + hsynchViewport(fLeft.getSourceViewer(), fAncestor.getSourceViewer(), fRight.getSourceViewer()); + hsynchViewport(fRight.getSourceViewer(), fAncestor.getSourceViewer(), fLeft.getSourceViewer()); + + if (fMarginWidth > 0) { + fRightCanvas= new BufferedCanvas(composite, SWT.NONE) { + public void doPaint(GC gc) { + paintSides(gc, fRight, fRightCanvas, fSynchronizedScrolling); + } + }; + fRightCanvas.addMouseListener( + new MouseAdapter() { + public void mouseDown(MouseEvent e) { + setCurrentDiff2(handleMouseInSides(fRightCanvas, fRight, e.y), false); + } + } + ); + } + + fScrollCanvas= new Canvas(composite, SWT.V_SCROLL); + Rectangle trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0); + fTopInset= trim.y; + + fVScrollBar= fScrollCanvas.getVerticalBar(); + fVScrollBar.setIncrement(1); + fVScrollBar.setVisible(true); + fVScrollBar.addListener(SWT.Selection, + new Listener() { + public void handleEvent(Event e) { + int vpos= ((ScrollBar)e.widget).getSelection(); + synchronizedScrollVertical(vpos); + } + } + ); + + fBirdsEyeCanvas= new BufferedCanvas(composite, SWT.NONE) { + public void doPaint(GC gc) { + paintBirdsEyeView(this, gc); + } + }; + fBirdsEyeCanvas.addMouseListener( + new MouseAdapter() { + public void mouseDown(MouseEvent e) { + setCurrentDiff2(handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y), true); + } + } + ); + fBirdsEyeCanvas.addMouseMoveListener( + new MouseMoveListener() { + + private Cursor fLastCursor; + + public void mouseMove(MouseEvent e) { + Cursor cursor= null; + Diff diff= handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y); + if (diff != null && diff.getKind() != RangeDifference.NOCHANGE) + cursor= fBirdsEyeCursor; + if (fLastCursor != cursor) { + fBirdsEyeCanvas.setCursor(cursor); + fLastCursor= cursor; + } + } + } + ); + + IWorkbenchPart workbenchPart = getCompareConfiguration().getContainer().getWorkbenchPart(); + if (workbenchPart != null) { + IContextService service = (IContextService)workbenchPart.getSite().getService(IContextService.class); + if (service != null) { + service.activateContext("org.eclipse.ui.textEditorScope"); //$NON-NLS-1$ + } + } + } + + private void hsynchViewport(final TextViewer tv1, final TextViewer tv2, final TextViewer tv3) { + final StyledText st1= tv1.getTextWidget(); + final StyledText st2= tv2.getTextWidget(); + final StyledText st3= tv3.getTextWidget(); + final ScrollBar sb1= st1.getHorizontalBar(); + sb1.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (fSynchronizedScrolling) { + int v= sb1.getSelection(); + if (st2.isVisible()) + st2.setHorizontalPixel(v); + if (st3.isVisible()) + st3.setHorizontalPixel(v); + } + } + }); + } + + private void setCurrentDiff2(Diff diff, boolean reveal) { + if (diff != null && diff.getKind() != RangeDifference.NOCHANGE) { + //fCurrentDiff= null; + setCurrentDiff(diff, reveal); + } + } + + private Diff handleMouseInSides(Canvas canvas, MergeSourceViewer tp, int my) { + + int lineHeight= tp.getSourceViewer().getTextWidget().getLineHeight(); + int visibleHeight= tp.getViewportHeight(); + + if (! fHighlightRanges) + return null; + + if (fMerger.hasChanges()) { + int shift= tp.getVerticalScrollOffset() + (2-LW); + + Point region= new Point(0, 0); + char leg = getLeg(tp); + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted()) + continue; + + if (fShowCurrentOnly2 && !isCurrentDiff(diff)) + continue; + + tp.getLineRange(diff.getPosition(leg), region); + int y= (region.x * lineHeight) + shift; + int h= region.y * lineHeight; + + if (y+h < 0) + continue; + if (y >= visibleHeight) + break; + + if (my >= y && my < y+h) + return diff; + } + } + return null; + } + + private Diff getDiffUnderMouse(Canvas canvas, int mx, int my, Rectangle r) { + + if (! fSynchronizedScrolling) + return null; + + int lineHeight= fLeft.getSourceViewer().getTextWidget().getLineHeight(); + int visibleHeight= fRight.getViewportHeight(); + + Point size= canvas.getSize(); + int w= size.x; + + if (! fHighlightRanges) + return null; + + if (fMerger.hasChanges()) { + int lshift= fLeft.getVerticalScrollOffset(); + int rshift= fRight.getVerticalScrollOffset(); + + Point region= new Point(0, 0); + + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted()) + continue; + + if (fShowCurrentOnly2 && !isCurrentDiff(diff)) + continue; + + fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region); + int ly= (region.x * lineHeight) + lshift; + int lh= region.y * lineHeight; + + fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region); + int ry= (region.x * lineHeight) + rshift; + int rh= region.y * lineHeight; + + if (Math.max(ly+lh, ry+rh) < 0) + continue; + if (Math.min(ly, ry) >= visibleHeight) + break; + + int cx= (w-RESOLVE_SIZE)/2; + int cy= ((ly+lh/2) + (ry+rh/2) - RESOLVE_SIZE)/2; + if (my >= cy && my < cy+RESOLVE_SIZE && mx >= cx && mx < cx+RESOLVE_SIZE) { + if (r != null) { + int SIZE= fIsCarbon ? 30 : 20; + r.x= cx+(RESOLVE_SIZE-SIZE)/2; + r.y= cy+(RESOLVE_SIZE-SIZE)/2; + r.width= SIZE; + r.height= SIZE; + } + return diff; + } + } + } + return null; + } + + private Diff handlemouseInBirdsEyeView(Canvas canvas, int my) { + return fMerger.findDiff(getViewportHeight(), fSynchronizedScrolling, canvas.getSize(), my); + } + + private void paintBirdsEyeView(Canvas canvas, GC gc) { + + Color c; + Rectangle r= new Rectangle(0, 0, 0, 0); + int yy, hh; + + Point size= canvas.getSize(); + + int virtualHeight= fSynchronizedScrolling ? fMerger.getVirtualHeight() : fMerger.getRightHeight(); + if (virtualHeight < getViewportHeight()) + return; + + Display display= canvas.getDisplay(); + int y= 0; + for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + int h= fSynchronizedScrolling ? diff.getMaxDiffHeight() + : diff.getRightHeight(); + + if (fMerger.useChange(diff)) { + + yy= (y*size.y)/virtualHeight; + hh= (h*size.y)/virtualHeight; + if (hh < 3) + hh= 3; + + c= getColor(display, getFillColor(diff)); + if (c != null) { + gc.setBackground(c); + gc.fillRectangle(BIRDS_EYE_VIEW_INSET, yy, size.x-(2*BIRDS_EYE_VIEW_INSET),hh); + } + c= getColor(display, getStrokeColor(diff)); + if (c != null) { + gc.setForeground(c); + r.x= BIRDS_EYE_VIEW_INSET; + r.y= yy; + r.width= size.x-(2*BIRDS_EYE_VIEW_INSET)-1; + r.height= hh; + if (isCurrentDiff(diff)) { + gc.setLineWidth(2); + r.x++; + r.y++; + r.width--; + r.height--; + } else { + gc.setLineWidth(0 /* 1 */); + } + gc.drawRectangle(r); + } + } + + y+= h; + } + } + + private void refreshBirdsEyeView() { + if (fBirdsEyeCanvas != null) + fBirdsEyeCanvas.redraw(); + } + + /** + * Override to give focus to the pane that previously had focus or to a suitable + * default pane. + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleSetFocus() + * @since 3.3 + */ + protected boolean handleSetFocus() { + if (fRedoDiff) { + new UIJob(CompareMessages.DocumentMerger_0) { + public IStatus runInUIThread(IProgressMonitor monitor) { + update(true); + updateStructure(); + return Status.OK_STATUS; + } + }.schedule(); + fRedoDiff = false; + } + if (fFocusPart == null) { + if (fLeft != null && fLeft.getEnabled()) { + fFocusPart= fLeft; + } else if (fRight != null && fRight.getEnabled()) { + fFocusPart= fRight; + } else if (fAncestor != null && fAncestor.getEnabled()) { + fFocusPart= fAncestor; + } + } + if (fFocusPart != null) { + StyledText st= fFocusPart.getSourceViewer().getTextWidget(); + if (st != null) + return st.setFocus(); + } + return false; // could not set focus + } + + + class HoverResizer extends Resizer { + Canvas fCanvas; + public HoverResizer(Canvas c, int dir) { + super(c, dir); + fCanvas= c; + } + public void mouseMove(MouseEvent e) { + if (!fIsDown && fUseSingleLine && showResolveUI() && handleMouseMoveOverCenter(fCanvas, e.x, e.y)) + return; + super.mouseMove(e); + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#createCenterControl(org.eclipse.swt.widgets.Composite) + */ + protected final Control createCenterControl(Composite parent) { + if (fSynchronizedScrolling) { + final Canvas canvas= new BufferedCanvas(parent, SWT.NONE) { + public void doPaint(GC gc) { + paintCenter(this, gc); + } + }; + if (fUseResolveUI) { + + new HoverResizer(canvas, HORIZONTAL); + + fCenterButton= new Button(canvas, fIsMac ? SWT.FLAT : SWT.PUSH); + if (fNormalCursor == null) fNormalCursor= new Cursor(canvas.getDisplay(), SWT.CURSOR_ARROW); + fCenterButton.setCursor(fNormalCursor); + fCenterButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR); + fCenterButton.pack(); + fCenterButton.setVisible(false); + fCenterButton.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + fCenterButton.setVisible(false); + if (fButtonDiff != null) { + setCurrentDiff(fButtonDiff, false); + copy(fCurrentDiff, fCenterButton.getText().equals( + COPY_LEFT_TO_RIGHT_INDICATOR), false); + } + } + } + ); + } else { + new Resizer(canvas, HORIZONTAL); + } + + return canvas; + } + return super.createCenterControl(parent); + } + + private boolean handleMouseMoveOverCenter(Canvas canvas, int x, int y) { + Rectangle r= new Rectangle(0, 0, 0, 0); + Diff diff= getDiffUnderMouse(canvas, x, y, r); + if (diff != null && !diff.isUnresolvedIncomingOrConflicting()) + diff= null; + if (diff != fButtonDiff) { + if (diff != null) { + if (fLeft.getSourceViewer().isEditable()) { + fButtonDiff= diff; + fCenterButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR); + String tt= fCopyDiffRightToLeftItem.getAction().getToolTipText(); + fCenterButton.setToolTipText(tt); + fCenterButton.setBounds(r); + fCenterButton.setVisible(true); + } else if (fRight.getSourceViewer().isEditable()) { + fButtonDiff= diff; + fCenterButton.setText(COPY_LEFT_TO_RIGHT_INDICATOR); + String tt= fCopyDiffLeftToRightItem.getAction().getToolTipText(); + fCenterButton.setToolTipText(tt); + fCenterButton.setBounds(r); + fCenterButton.setVisible(true); + } else + fButtonDiff= null; + } else { + fCenterButton.setVisible(false); + fButtonDiff= null; + } + } + return fButtonDiff != null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#getCenterWidth() + */ + protected final int getCenterWidth() { + if (fSynchronizedScrolling) + return CENTER_WIDTH; + return super.getCenterWidth(); + } + + private int getDirection() { + switch (fTextDirection) { + case SWT.LEFT_TO_RIGHT: + case SWT.RIGHT_TO_LEFT: + if (fInheritedDirection == fTextDirection) + return SWT.NONE; + return fTextDirection; + } + return fInheritedDirection; + } + + /** + * Creates a new source viewer. This method is called when creating and + * initializing the content areas of the merge viewer (see + * {@link #createControls(Composite)}). It is called three + * times for each text part of the comparison: ancestor, left, right. + * Clients may implement to provide their own type of source viewers. The + * viewer is not expected to be configured with a source viewer + * configuration. + * + * @param parent + * the parent of the viewer's control + * @param textOrientation + * style constant bit for text orientation + * @return Default implementation returns an instance of + * {@link SourceViewer}. + * @since 3.5 + */ + protected SourceViewer createSourceViewer(Composite parent, int textOrientation) { + return new SourceViewer(parent, new CompositeRuler(), textOrientation | SWT.H_SCROLL | SWT.V_SCROLL); + } + + /** + * Tells whether the given text viewer is backed by an editor. + * + * @param textViewer the text viewer to check + * @return <code>true</code> if the viewer is backed by an editor + * @since 3.5 + */ + protected boolean isEditorBacked(ITextViewer textViewer) { + return false; + } + + /** + * Returns an editor input for the given source viewer. The method returns + * <code>null</code> when no input is available, for example when the input + * for the merge viewer has not been set yet. + * + * @param sourceViewer + * the source viewer to get input for + * @return input for the given viewer or <code>null</code> when no input is + * available + * + * @since 3.5 + */ + protected IEditorInput getEditorInput(ISourceViewer sourceViewer) { + if (fLeft != null && sourceViewer == fLeft.getSourceViewer()) + if (fLeftContributor != null) + return fLeftContributor.getDocumentKey(); + if (fRight != null && sourceViewer == fRight.getSourceViewer()) + if (fRightContributor != null) + return fRightContributor.getDocumentKey(); + if (fAncestor != null + && sourceViewer == fAncestor.getSourceViewer()) + if (fAncestorContributor != null) + return fAncestorContributor.getDocumentKey(); + return null; + } + + /* + * Creates and initializes a text part. + */ + private MergeSourceViewer createPart(Composite parent) { + final MergeSourceViewer viewer = new MergeSourceViewer( + createSourceViewer(parent, getDirection()), + getResourceBundle(), getCompareConfiguration().getContainer()); + final StyledText te= viewer.getSourceViewer().getTextWidget(); + + if (!fConfirmSave) + viewer.hideSaveAction(); + + te.addPaintListener( + new PaintListener() { + public void paintControl(PaintEvent e) { + paint(e, viewer); + } + } + ); + te.addKeyListener( + new KeyAdapter() { + public void keyPressed(KeyEvent e) { + handleSelectionChanged(viewer); + } + } + ); + te.addMouseListener( + new MouseAdapter() { + public void mouseDown(MouseEvent e) { + //syncViewport(part); + handleSelectionChanged(viewer); + } + } + ); + + te.addFocusListener( + new FocusAdapter() { + public void focusGained(FocusEvent fe) { + setActiveViewer(viewer, true); + } + public void focusLost(FocusEvent fe) { + setActiveViewer(viewer, false); + } + } + ); + + viewer.getSourceViewer().addViewportListener( + new IViewportListener() { + public void viewportChanged(int verticalPosition) { + syncViewport(viewer); + } + } + ); + + Font font= JFaceResources.getFont(fSymbolicFontName); + if (font != null) + te.setFont(font); + + if (fBackground != null) // not default + te.setBackground(getColor(parent.getDisplay(), fBackground)); + + // Add the find action to the popup menu of the viewer + contributeFindAction(viewer); + + contributeGotoLineAction(viewer); + + contributeChangeEncodingAction(viewer); + + contributeDiffBackgroundListener(viewer); + + return viewer; + } + + private void setActiveViewer(MergeSourceViewer viewer, boolean activate) { + connectContributedActions(viewer, activate); + if (activate) { + fFocusPart= viewer; + connectGlobalActions(fFocusPart); + } else { + connectGlobalActions(null); + } + } + + private SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) { + SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors()); + support.setCursorLinePainterPreferenceKeys(CURRENT_LINE, CURRENT_LINE_COLOR); + fSourceViewerDecorationSupport.add(support); + return support; + } + + private void contributeFindAction(MergeSourceViewer viewer) { + IAction action; + IWorkbenchPart wp = getCompareConfiguration().getContainer().getWorkbenchPart(); + if (wp != null) + action = new FindReplaceAction(getResourceBundle(), "Editor.FindReplace.", wp); //$NON-NLS-1$ + else + action = new FindReplaceAction(getResourceBundle(), "Editor.FindReplace.", viewer.getSourceViewer().getControl().getShell(), getFindReplaceTarget()); //$NON-NLS-1$ + action.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_FIND_AND_REPLACE); + viewer.addAction(MergeSourceViewer.FIND_ID, action); + } + + private void contributeGotoLineAction(MergeSourceViewer viewer) { + IAction action = new GotoLineAction((ITextEditor) viewer.getAdapter(ITextEditor.class)); + action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_GOTO); + viewer.addAction(MergeSourceViewer.GOTO_LINE_ID, action); + } + + private void contributeChangeEncodingAction(MergeSourceViewer viewer) { + IAction action = new ChangeEncodingAction(getTextEditorAdapter()); + viewer.addAction(MergeSourceViewer.CHANGE_ENCODING_ID, action); + } + + private void contributeDiffBackgroundListener(final MergeSourceViewer viewer) { + viewer.getSourceViewer().getTextWidget().addLineBackgroundListener( + new LineBackgroundListener() { + public void lineGetBackground(LineBackgroundEvent event) { + StyledText textWidget = viewer.getSourceViewer() + .getTextWidget(); + if (textWidget != null) { + + int caret = textWidget.getCaretOffset(); + int length = event.lineText.length(); + + if (event.lineOffset <= caret + && caret <= event.lineOffset + length) { + // current line, do nothing + // decorated by CursorLinePainter + } else { + // find diff for the event line + Diff diff = findDiff(viewer, event.lineOffset, + event.lineOffset + length); + if (diff != null && updateDiffBackground(diff)) { + // highlights only the event line, not the + // whole diff + event.lineBackground = getColor(fComposite + .getDisplay(), getFillColor(diff)); + } + } + } + } + }); + } + + private void connectGlobalActions(final MergeSourceViewer part) { + if (fHandlerService != null) { + if (part != null) + part.updateActions(); + fHandlerService.updatePaneActionHandlers(new Runnable() { + public void run() { + for (int i= 0; i < GLOBAL_ACTIONS.length; i++) { + IAction action= null; + if (part != null) { + action= part.getAction(TEXT_ACTIONS[i]); + } + fHandlerService.setGlobalActionHandler(GLOBAL_ACTIONS[i], action); + } + } + }); + } + } + + private void connectContributedActions(final MergeSourceViewer viewer, final boolean connect) { + if (fHandlerService != null) { + fHandlerService.updatePaneActionHandlers(new Runnable() { + public void run() { + if (viewer != null) { + setActionsActivated(viewer.getSourceViewer(), connect); + if (isEditorBacked(viewer.getSourceViewer()) && connect) { + /* + * If editor backed, activating contributed actions + * might have disconnected actions provided in + * CompareEditorContributor => when connecting, + * refresh active editor in the contributor, when + * disconnecting do nothing. See bug 261229. + */ + IWorkbenchPart part = getCompareConfiguration().getContainer().getWorkbenchPart(); + if (part instanceof CompareEditor) { + ((CompareEditor) part).refreshActionBarsContributor(); + } + } + } + } + }); + } + } + + /** + * Activates or deactivates actions of the given source viewer. + * <p> + * The default implementation does nothing, but clients should override to properly react to + * viewers switching. + * </p> + * + * @param sourceViewer the source viewer + * @param state <code>true</code> if activated + * @since 3.5 + */ + protected void setActionsActivated(SourceViewer sourceViewer, boolean state) { + // default implementation does nothing + } + + private IDocument getElementDocument(char type, Object element) { + if (element instanceof IDocument) { + return (IDocument) element; + } + ITypedElement te= Utilities.getLeg(type, element); + // First check the contributors for the document + IDocument document = null; + switch (type) { + case ANCESTOR_CONTRIBUTOR: + document = getDocument(te, fAncestorContributor); + break; + case LEFT_CONTRIBUTOR: + document = getDocument(te, fLeftContributor); + break; + case RIGHT_CONTRIBUTOR: + document = getDocument(te, fRightContributor); + break; + } + if (document != null) + return document; + // The document is not associated with the input of the viewer so try to find the document + return Utilities.getDocument(type, element, isUsingDefaultContentProvider(), canHaveSharedDocument()); + } + + private boolean isUsingDefaultContentProvider() { + return getContentProvider() instanceof MergeViewerContentProvider; + } + + private boolean canHaveSharedDocument() { + return getDocumentPartitioning() != null + || getDocumentPartitioner() == null; + } + + private IDocument getDocument(ITypedElement te, ContributorInfo info) { + if (info != null && info.getElement() == te) + return info.getDocument(); + return null; + } + + IDocument getDocument(char type, Object input) { + IDocument doc= getElementDocument(type, input); + if (doc != null) + return doc; + + if (input instanceof IDiffElement) { + IDiffContainer parent= ((IDiffElement)input).getParent(); + return getElementDocument(type, parent); + } + return null; + } + + /* + * Returns true if the given inputs map to the same documents + */ + boolean sameDoc(char type, Object newInput, Object oldInput) { + IDocument newDoc= getDocument(type, newInput); + IDocument oldDoc= getDocument(type, oldInput); + return newDoc == oldDoc; + } + + /** + * Overridden to prevent save confirmation if new input is sub document of current input. + * @param newInput the new input of this viewer, or <code>null</code> if there is no new input + * @param oldInput the old input element, or <code>null</code> if there was previously no input + * @return <code>true</code> if saving was successful, or if the user didn't want to save (by pressing 'NO' in the confirmation dialog). + * @since 2.0 + */ + protected boolean doSave(Object newInput, Object oldInput) { + // TODO: Would be good if this could be restated in terms of Saveables and moved up + if (oldInput != null && newInput != null) { + // check whether underlying documents have changed. + if (sameDoc(ANCESTOR_CONTRIBUTOR, newInput, oldInput) && + sameDoc(LEFT_CONTRIBUTOR, newInput, oldInput) && + sameDoc(RIGHT_CONTRIBUTOR, newInput, oldInput)) { + if (DEBUG) System.out.println("----- Same docs !!!!"); //$NON-NLS-1$ + return false; + } + } + + if (DEBUG) System.out.println("***** New docs !!!!"); //$NON-NLS-1$ + + removeFromDocumentManager(ANCESTOR_CONTRIBUTOR, oldInput); + removeFromDocumentManager(LEFT_CONTRIBUTOR, oldInput); + removeFromDocumentManager(RIGHT_CONTRIBUTOR, oldInput); + + if (DEBUG) + DocumentManager.dump(); + + return super.doSave(newInput, oldInput); + } + + private void removeFromDocumentManager(char leg, Object oldInput) { + IDocument document= getDocument(leg, oldInput); + if (document != null) + DocumentManager.remove(document); + } + + private ITypedElement getParent(char type) { + Object input= getInput(); + if (input instanceof IDiffElement) { + IDiffContainer parent= ((IDiffElement)input).getParent(); + return Utilities.getLeg(type, parent); + } + return null; + } + + /* + * Initializes the text viewers of the three content areas with the given input objects. + * Subclasses may extend. + */ + protected void updateContent(Object ancestor, Object left, Object right) { + + boolean emptyInput= (ancestor == null && left == null && right == null); + + Object input= getInput(); + + Position leftRange= null; + Position rightRange= null; + + // if one side is empty use container + if (FIX_47640 && !emptyInput && (left == null || right == null)) { + if (input instanceof IDiffElement) { + IDiffContainer parent= ((IDiffElement)input).getParent(); + if (parent instanceof ICompareInput) { + ICompareInput ci= (ICompareInput) parent; + + if (ci.getAncestor() instanceof IDocumentRange + || ci.getLeft() instanceof IDocumentRange + || ci.getRight() instanceof IDocumentRange) { + + if (left instanceof IDocumentRange) + leftRange= ((IDocumentRange)left).getRange(); + if (right instanceof IDocumentRange) + rightRange= ((IDocumentRange)right).getRange(); + + ancestor= ci.getAncestor(); + left= ci.getLeft(); + right= ci.getRight(); + } + } + } + } + + int n= 0; + if (left != null) + n++; + if (right != null) + n++; + fHighlightRanges= n > 1; + + resetDiffs(); + fHasErrors= false; // start with no errors + + CompareConfiguration cc= getCompareConfiguration(); + IMergeViewerContentProvider cp= getMergeContentProvider(); + + if (cp instanceof MergeViewerContentProvider) { + MergeViewerContentProvider mcp= (MergeViewerContentProvider) cp; + mcp.setAncestorError(null); + mcp.setLeftError(null); + mcp.setRightError(null); + } + + // Record current contributors so we disconnect after creating the new ones. + // This is done in case the old and new use the same document. + ContributorInfo oldLeftContributor = fLeftContributor; + ContributorInfo oldRightContributor = fRightContributor; + ContributorInfo oldAncestorContributor = fAncestorContributor; + + // Create the new contributor + fLeftContributor = createLegInfoFor(left, LEFT_CONTRIBUTOR); + fRightContributor = createLegInfoFor(right, RIGHT_CONTRIBUTOR); + fAncestorContributor = createLegInfoFor(ancestor, ANCESTOR_CONTRIBUTOR); + + fLeftContributor.transferContributorStateFrom(oldLeftContributor); + fRightContributor.transferContributorStateFrom(oldRightContributor); + fAncestorContributor.transferContributorStateFrom(oldAncestorContributor); + + // Now disconnect the old ones + disconnect(oldLeftContributor); + disconnect(oldRightContributor); + disconnect(oldAncestorContributor); + + // Get encodings from streams. If an encoding is null, abide by the other one + // Defaults to workbench encoding only if both encodings are null + fLeftContributor.setEncodingIfAbsent(fRightContributor); + fRightContributor.setEncodingIfAbsent(fLeftContributor); + fAncestorContributor.setEncodingIfAbsent(fLeftContributor); + + if (!isConfigured) { + configureSourceViewer(fAncestor.getSourceViewer(), false, null); + configureSourceViewer(fLeft.getSourceViewer(), cc.isLeftEditable() && cp.isLeftEditable(input), fLeftContributor); + configureSourceViewer(fRight.getSourceViewer(), cc.isRightEditable() && cp.isRightEditable(input), fRightContributor); + isConfigured = true; // configure once + } + + // set new documents + fLeftContributor.setDocument(fLeft, cc.isLeftEditable() && cp.isLeftEditable(input)); + fLeftLineCount= fLeft.getLineCount(); + + fRightContributor.setDocument(fRight, cc.isRightEditable() && cp.isRightEditable(input)); + fRightLineCount= fRight.getLineCount(); + + fAncestorContributor.setDocument(fAncestor, false); + + //if the input is part of a patch hunk, toggle synchronized scrolling + /*if (isPatchHunk()){ + setSyncScrolling(false); + } else { + setSyncScrolling(fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING)); + }*/ + setSyncScrolling(fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING)); + + update(false); + + if (!fHasErrors && !emptyInput && !fComposite.isDisposed()) { + if (isRefreshing()) { + fLeftContributor.updateSelection(fLeft, !fSynchronizedScrolling); + fRightContributor.updateSelection(fRight, !fSynchronizedScrolling); + fAncestorContributor.updateSelection(fAncestor, !fSynchronizedScrolling); + if (fSynchronizedScrolling && fSynchronziedScrollPosition != -1) { + synchronizedScrollVertical(fSynchronziedScrollPosition); + } + } else { + if (isPatchHunk()) { + if (right != null && Utilities.getAdapter(right, IHunk.class) != null) + fLeft.getSourceViewer().setTopIndex(getHunkStart()); + else + fRight.getSourceViewer().setTopIndex(getHunkStart()); + } else { + Diff selectDiff= null; + if (FIX_47640) { + if (leftRange != null) + selectDiff= fMerger.findDiff(LEFT_CONTRIBUTOR, leftRange); + else if (rightRange != null) + selectDiff= fMerger.findDiff(RIGHT_CONTRIBUTOR, rightRange); + } + if (selectDiff != null) + setCurrentDiff(selectDiff, true); + else + selectFirstDiff(true); + } + } + } + + } + + private void configureSourceViewer(SourceViewer sourceViewer, boolean editable, ContributorInfo contributor) { + setEditable(sourceViewer, editable); + configureTextViewer(sourceViewer); + if (editable && contributor != null) { + IDocument document = sourceViewer.getDocument(); + if (document != null) + contributor.connectPositionUpdater(document); + } + if (!isCursorLinePainterInstalled(sourceViewer)) + getSourceViewerDecorationSupport(sourceViewer).install(fPreferenceStore); + } + + private boolean isCursorLinePainterInstalled(SourceViewer viewer) { + Listener[] listeners = viewer.getTextWidget().getListeners(3001/*StyledText.LineGetBackground*/); + for (int i = 0; i < listeners.length; i++) { + if (listeners[i] instanceof TypedListener) { + TypedListener listener = (TypedListener) listeners[i]; + if (listener.getEventListener() instanceof CursorLinePainter) + return true; + } + } + return false; + } + + /** + * Sets the editable state of the given source viewer. + * + * @param sourceViewer + * the source viewer + * @param state + * the state + * @since 3.5 + */ + protected void setEditable(ISourceViewer sourceViewer, boolean state) { + sourceViewer.setEditable(state); + } + + private boolean isRefreshing() { + return isRefreshing > 0; + } + + private ContributorInfo createLegInfoFor(Object element, char leg) { + return new ContributorInfo(this, element, leg); + } + + private boolean updateDiffBackground(Diff diff) { + + if (!fHighlightRanges) + return false; + + if (diff == null || diff.isToken()) + return false; + + if (fShowCurrentOnly && !isCurrentDiff(diff)) + return false; + + return true; + } + + /* + * Called whenever one of the documents changes. + * Sets the dirty state of this viewer and updates the lines. + * Implements IDocumentListener. + */ + private void documentChanged(DocumentEvent e, boolean dirty) { + + IDocument doc= e.getDocument(); + + if (doc == fLeft.getSourceViewer().getDocument()) { + setLeftDirty(dirty); + } else if (doc == fRight.getSourceViewer().getDocument()) { + setRightDirty(dirty); + } + if (!isLeftDirty() && !isRightDirty()) { + fRedoDiff = false; + } + updateLines(doc); + } + + /* + * This method is called if a range of text on one side is copied into an empty sub-document + * on the other side. The method returns the position where the sub-document is placed into the base document. + * This default implementation determines the position by using the text range differencer. + * However this position is not always optimal for specific types of text. + * So subclasses (which are aware of the type of text they are dealing with) + * may override this method to find a better position where to insert a newly added + * piece of text. + * @param type the side for which the insertion position should be determined: 'A' for ancestor, 'L' for left hand side, 'R' for right hand side. + * @param input the current input object of this viewer + * @since 2.0 + */ + protected int findInsertionPosition(char type, ICompareInput input) { + + ITypedElement other= null; + char otherType= 0; + + switch (type) { + case ANCESTOR_CONTRIBUTOR: + other= input.getLeft(); + otherType= LEFT_CONTRIBUTOR; + if (other == null) { + other= input.getRight(); + otherType= RIGHT_CONTRIBUTOR; + } + break; + case LEFT_CONTRIBUTOR: + other= input.getRight(); + otherType= RIGHT_CONTRIBUTOR; + if (other == null) { + other= input.getAncestor(); + otherType= ANCESTOR_CONTRIBUTOR; + } + break; + case RIGHT_CONTRIBUTOR: + other= input.getLeft(); + otherType= LEFT_CONTRIBUTOR; + if (other == null) { + other= input.getAncestor(); + otherType= ANCESTOR_CONTRIBUTOR; + } + break; + } + + if (other instanceof IDocumentRange) { + IDocumentRange dr= (IDocumentRange) other; + Position p= dr.getRange(); + Diff diff= findDiff(otherType, p.offset); + return fMerger.findInsertionPoint(diff, type); + } + return 0; + } + + private void setError(char type, String message) { + IMergeViewerContentProvider cp= getMergeContentProvider(); + if (cp instanceof MergeViewerContentProvider) { + MergeViewerContentProvider mcp= (MergeViewerContentProvider) cp; + switch (type) { + case ANCESTOR_CONTRIBUTOR: + mcp.setAncestorError(message); + break; + case LEFT_CONTRIBUTOR: + mcp.setLeftError(message); + break; + case RIGHT_CONTRIBUTOR: + mcp.setRightError(message); + break; + } + } + fHasErrors= true; + } + + private void updateDirtyState(IEditorInput key, + IDocumentProvider documentProvider, char type) { + boolean dirty = documentProvider.canSaveDocument(key); + boolean oldLeftDirty = isLeftDirty(); + boolean oldRightDirty = isRightDirty(); + if (type == LEFT_CONTRIBUTOR) + setLeftDirty(dirty); + else if (type == RIGHT_CONTRIBUTOR) + setRightDirty(dirty); + if ((oldLeftDirty && !isLeftDirty()) + || (oldRightDirty && !isRightDirty())) { + /* + * Dirty state has changed from true to false, combined dirty state + * is false. _In most cases_ this means that save has taken place + * outside compare editor. Ask to redo diff calculation when the + * editor gets focus. + * + * However, undoing all the changes made in another editor would + * result in asking for redo diff as well. In this case, we set the + * flag back to false, see + * TextMergeViewer.documentChanged(DocumentEvent, boolean) + */ + fRedoDiff = true; + } + } + + private Position getNewRange(char type, Object input) { + switch (type) { + case ANCESTOR_CONTRIBUTOR: + return (Position) fNewAncestorRanges.get(input); + case LEFT_CONTRIBUTOR: + return (Position) fNewLeftRanges.get(input); + case RIGHT_CONTRIBUTOR: + return (Position) fNewRightRanges.get(input); + } + return null; + } + + private void addNewRange(char type, Object input, Position range) { + switch (type) { + case ANCESTOR_CONTRIBUTOR: + fNewAncestorRanges.put(input, range); + break; + case LEFT_CONTRIBUTOR: + fNewLeftRanges.put(input, range); + break; + case RIGHT_CONTRIBUTOR: + fNewRightRanges.put(input, range); + break; + } + } + + /** + * Returns the contents of the underlying document as an array of bytes using the current workbench encoding. + * + * @param left if <code>true</code> the contents of the left side is returned; otherwise the right side + * @return the contents of the left or right document or null + */ + protected byte[] getContents(boolean left) { + MergeSourceViewer v= left ? fLeft : fRight; + if (v != null) { + IDocument d= v.getSourceViewer().getDocument(); + if (d != null) { + String contents= d.get(); + if (contents != null) { + byte[] bytes; + try { + bytes= contents.getBytes(left ? fLeftContributor.internalGetEncoding() : fRightContributor.internalGetEncoding()); + } catch(UnsupportedEncodingException ex) { + // use default encoding + bytes= contents.getBytes(); + } + return bytes; + } + } + } + return null; + } + + private IRegion normalizeDocumentRegion(IDocument doc, IRegion region) { + + if (region == null || doc == null) + return region; + + int maxLength= doc.getLength(); + + int start= region.getOffset(); + if (start < 0) + start= 0; + else if (start > maxLength) + start= maxLength; + + int length= region.getLength(); + if (length < 0) + length= 0; + else if (start + length > maxLength) + length= maxLength - start; + + return new Region(start, length); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleResizeAncestor(int, int, int, int) + */ + protected final void handleResizeAncestor(int x, int y, int width, int height) { + if (width > 0) { + Rectangle trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0); + int scrollbarHeight= trim.height; + if (Utilities.okToUse(fAncestorCanvas)) + fAncestorCanvas.setVisible(true); + if (fAncestor.isControlOkToUse()) + fAncestor.getSourceViewer().getTextWidget().setVisible(true); + + if (fAncestorCanvas != null) { + fAncestorCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight); + x+= fMarginWidth; + width-= fMarginWidth; + } + fAncestor.setBounds(x, y, width, height); + } else { + if (Utilities.okToUse(fAncestorCanvas)) + fAncestorCanvas.setVisible(false); + if (fAncestor.isControlOkToUse()) { + StyledText t= fAncestor.getSourceViewer().getTextWidget(); + t.setVisible(false); + fAncestor.setBounds(0, 0, 0, 0); + if (fFocusPart == fAncestor) { + fFocusPart= fLeft; + fFocusPart.getSourceViewer().getTextWidget().setFocus(); + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleResizeLeftRight(int, int, int, int, int, int) + */ + protected final void handleResizeLeftRight(int x, int y, int width1, int centerWidth, int width2, int height) { + + if (fBirdsEyeCanvas != null) + width2-= BIRDS_EYE_VIEW_WIDTH; + + Rectangle trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0); + int scrollbarHeight= trim.height + trim.x; + + Composite composite= (Composite) getControl(); + + int leftTextWidth= width1; + if (fLeftCanvas != null) { + fLeftCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight); + x+= fMarginWidth; + leftTextWidth-= fMarginWidth; + } + + fLeft.setBounds(x, y, leftTextWidth, height); + x+= leftTextWidth; + + if (fCenter == null || fCenter.isDisposed()) + fCenter= createCenterControl(composite); + fCenter.setBounds(x, y, centerWidth, height-scrollbarHeight); + x+= centerWidth; + + if (!fSynchronizedScrolling) { // canvas is to the left of text + if (fRightCanvas != null) { + fRightCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight); + fRightCanvas.redraw(); + x+= fMarginWidth; + } + // we draw the canvas to the left of the text widget + } + + int scrollbarWidth= 0; + if (fSynchronizedScrolling && fScrollCanvas != null) { + trim= fLeft.getSourceViewer().getTextWidget().computeTrim(0, 0, 0, 0); + // one pixel was cut off + scrollbarWidth= trim.width + 2*trim.x+1; + } + int rightTextWidth= width2-scrollbarWidth; + if (fRightCanvas != null) + rightTextWidth-= fMarginWidth; + fRight.setBounds(x, y, rightTextWidth, height); + x+= rightTextWidth; + + if (fSynchronizedScrolling) { + if (fRightCanvas != null) { // canvas is to the right of the text + fRightCanvas.setBounds(x, y, fMarginWidth, height-scrollbarHeight); + x+= fMarginWidth; + } + if (fScrollCanvas != null) + fScrollCanvas.setBounds(x, y, scrollbarWidth, height-scrollbarHeight); + } + + if (fBirdsEyeCanvas != null) { + int verticalScrollbarButtonHeight= scrollbarWidth; + int horizontalScrollbarButtonHeight= scrollbarHeight; + if (fIsMac) { + verticalScrollbarButtonHeight+= 2; + horizontalScrollbarButtonHeight= 18; + } + if (fSummaryHeader != null) + fSummaryHeader.setBounds(x+scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH, verticalScrollbarButtonHeight); + y+= verticalScrollbarButtonHeight; + fBirdsEyeCanvas.setBounds(x+scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH, height-(2*verticalScrollbarButtonHeight+horizontalScrollbarButtonHeight)); + } + + // doesn't work since TextEditors don't have their correct size yet. + updateVScrollBar(); + refreshBirdsEyeView(); + } + + /* + * Track selection changes to update the current Diff. + */ + private void handleSelectionChanged(MergeSourceViewer tw) { + Point p= tw.getSourceViewer().getSelectedRange(); + Diff d= findDiff(tw, p.x, p.x+p.y); + updateStatus(d); + setCurrentDiff(d, false); // don't select or reveal + } + + private static IRegion toRegion(Position position) { + if (position != null) + return new Region(position.getOffset(), position.getLength()); + return null; + } + + //---- the differencing + + /** + * Perform a two level 2- or 3-way diff. + * The first level is based on line comparison, the second level on token comparison. + */ + private void doDiff() { + IDocument lDoc= fLeft.getSourceViewer().getDocument(); + IDocument rDoc= fRight.getSourceViewer().getDocument(); + if (lDoc == null || rDoc == null) + return; + fAncestor.resetLineBackground(); + fLeft.resetLineBackground(); + fRight.resetLineBackground(); + + fCurrentDiff= null; + try { + fMerger.doDiff(); + } catch (CoreException e) { + CompareUIPlugin.log(e.getStatus()); + String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$ + String msg= Utilities.getString(getResourceBundle(), "tooComplexError.message"); //$NON-NLS-1$ + MessageDialog.openError(fComposite.getShell(), title, msg); + } + + invalidateTextPresentation(); + } + + private Diff findDiff(char type, int pos) { + try { + return fMerger.findDiff(type, pos); + } catch (CoreException e) { + CompareUIPlugin.log(e.getStatus()); + String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$ + String msg= Utilities.getString(getResourceBundle(), "tooComplexError.message"); //$NON-NLS-1$ + MessageDialog.openError(fComposite.getShell(), title, msg); + return null; + } + } + + private void resetPositions(IDocument doc) { + if (doc == null) + return; + try { + doc.removePositionCategory(DIFF_RANGE_CATEGORY); + } catch (BadPositionCategoryException e) { + // Ignore + } + doc.addPositionCategory(DIFF_RANGE_CATEGORY); + } + + //---- update UI stuff + + private void updateControls() { + if (getControl().isDisposed()) + return; + + boolean leftToRight= false; + boolean rightToLeft= false; + + updateStatus(fCurrentDiff); + updateResolveStatus(); + + if (fCurrentDiff != null) { + IMergeViewerContentProvider cp= getMergeContentProvider(); + if (cp != null) { + if (!isPatchHunk()) { + rightToLeft= cp.isLeftEditable(getInput()); + leftToRight= cp.isRightEditable(getInput()); + } + } + } + + if (fDirectionLabel != null) { + if (fHighlightRanges && fCurrentDiff != null && isThreeWay() && !isIgnoreAncestor()) { + fDirectionLabel.setImage(fCurrentDiff.getImage()); + } else { + fDirectionLabel.setImage(null); + } + } + + if (fCopyDiffLeftToRightItem != null) + ((Action)fCopyDiffLeftToRightItem.getAction()).setEnabled(leftToRight); + if (fCopyDiffRightToLeftItem != null) + ((Action)fCopyDiffRightToLeftItem.getAction()).setEnabled(rightToLeft); + + if (fNextDiff != null) { + IAction a = fNextDiff.getAction(); + a.setEnabled(isNavigationButtonEnabled(true, false)); + } + if (fPreviousDiff != null) { + IAction a = fPreviousDiff.getAction(); + a.setEnabled(isNavigationButtonEnabled(false, false)); + } + if (fNextChange != null) { + IAction a = fNextChange.getAction(); + a.setEnabled(isNavigationButtonEnabled(true, true)); + } + if (fPreviousChange != null) { + IAction a = fPreviousChange.getAction(); + a.setEnabled(isNavigationButtonEnabled(false, true)); + } + } + + private boolean isNavigationButtonEnabled(boolean down, boolean deep) { + String value = fPreferenceStore + .getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION); + if (value.equals(ICompareUIConstants.PREF_VALUE_DO_NOTHING)) { + return getNextVisibleDiff(down, deep) != null; + } else if (value.equals(ICompareUIConstants.PREF_VALUE_LOOP)) { + return isNavigationPossible(); + } else if (value.equals(ICompareUIConstants.PREF_VALUE_NEXT)) { + return getNextVisibleDiff(down, deep) != null || hasNextElement(down); + } else if (value.equals(ICompareUIConstants.PREF_VALUE_PROMPT)) { + return isNavigationPossible() || hasNextElement(true); + } + Assert.isTrue(false); + return false; + } + + private void updateResolveStatus() { + + RGB rgb= null; + + if (showResolveUI()) { + // we only show red or green if there is at least one incoming or conflicting change + int incomingOrConflicting= 0; + int unresolvedIncoming= 0; + int unresolvedConflicting= 0; + + if (fMerger.hasChanges()) { + for (Iterator iterator = fMerger.changesIterator(); iterator + .hasNext();) { + Diff d = (Diff) iterator.next(); + if (d.isIncomingOrConflicting() /* && useChange(d.fDirection) && !d.fIsWhitespace */) { + incomingOrConflicting++; + if (!d.isResolved()) { + if (d.getKind() == RangeDifference.CONFLICT) { + unresolvedConflicting++; + break; // we can stop here because a conflict has the maximum priority + } + unresolvedIncoming++; + } + } + } + } + + if (incomingOrConflicting > 0) { + if (unresolvedConflicting > 0) + rgb= SELECTED_CONFLICT; + else if (unresolvedIncoming > 0) + rgb= SELECTED_INCOMING; + else + rgb= RESOLVED; + } + } + + if (fHeaderPainter.setColor(rgb)) + fSummaryHeader.redraw(); + } + + private void updateStatus(Diff diff) { + + String diffDescription; + + if (diff == null) { + diffDescription= CompareMessages.TextMergeViewer_diffDescription_noDiff_format; + } else { + + if (diff.isToken()) // we don't show special info for token diffs + diff= diff.getParent(); + + String format= CompareMessages.TextMergeViewer_diffDescription_diff_format; + diffDescription= MessageFormat.format(format, + new String[] { + getDiffType(diff), // 0: diff type + getDiffNumber(diff), // 1: diff number + getDiffRange(fLeft, diff.getPosition(LEFT_CONTRIBUTOR)), // 2: left start line + getDiffRange(fRight, diff.getPosition(RIGHT_CONTRIBUTOR)) // 3: left end line + } + ); + } + + String format= CompareMessages.TextMergeViewer_statusLine_format; + String s= MessageFormat.format(format, + new String[] { + getCursorPosition(fLeft), // 0: left column + getCursorPosition(fRight), // 1: right column + diffDescription // 2: diff description + } + ); + + getCompareConfiguration().getContainer().setStatusMessage(s); + } + + private String getDiffType(Diff diff) { + String s= ""; //$NON-NLS-1$ + switch(diff.getKind()) { + case RangeDifference.LEFT: + s= CompareMessages.TextMergeViewer_direction_outgoing; + break; + case RangeDifference.RIGHT: + s= CompareMessages.TextMergeViewer_direction_incoming; + break; + case RangeDifference.CONFLICT: + s= CompareMessages.TextMergeViewer_direction_conflicting; + break; + } + String format= CompareMessages.TextMergeViewer_diffType_format; + return MessageFormat.format(format, new String[] { s, diff.changeType() } ); + } + + private String getDiffNumber(Diff diff) { + // find the diff's number + int diffNumber= 0; + if (fMerger.hasChanges()) { + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff d = (Diff) iterator.next(); + diffNumber++; + if (d == diff) + break; + } + } + return Integer.toString(diffNumber); + } + + private String getDiffRange(MergeSourceViewer v, Position pos) { + Point p= v.getLineRange(pos, new Point(0, 0)); + int startLine= p.x+1; + int endLine= p.x+p.y; + + String format; + if (endLine < startLine) + format= CompareMessages.TextMergeViewer_beforeLine_format; + else + format= CompareMessages.TextMergeViewer_range_format; + return MessageFormat.format(format, + new String[] { Integer.toString(startLine), + Integer.toString(endLine) } ); + } + + /* + * Returns a description of the cursor position. + * + * @return a description of the cursor position + */ + private String getCursorPosition(MergeSourceViewer v) { + if (v != null) { + StyledText styledText= v.getSourceViewer().getTextWidget(); + + IDocument document= v.getSourceViewer().getDocument(); + if (document != null) { + int offset= v.getSourceViewer().getVisibleRegion().getOffset(); + int caret= offset + styledText.getCaretOffset(); + + try { + + int line=document.getLineOfOffset(caret); + + int lineOffset= document.getLineOffset(line); + int occurrences= 0; + for (int i= lineOffset; i < caret; i++) + if ('\t' == document.getChar(i)) + ++ occurrences; + + int tabWidth= styledText.getTabs(); + int column= caret - lineOffset + (tabWidth -1) * occurrences; + + String format= CompareMessages.TextMergeViewer_cursorPosition_format; + return MessageFormat.format(format, + new String[] { Integer.toString(line + 1), Integer.toString(column + 1) } ); + + } catch (BadLocationException x) { + // silently ignored + } + } + } + return ""; //$NON-NLS-1$ + } + + protected void updateHeader() { + + super.updateHeader(); + + updateControls(); + } + + /* + * Creates the two items for copying a difference range from one side to the other + * and adds them to the given toolbar manager. + */ + protected void createToolItems(ToolBarManager tbm) { + + fHandlerService= CompareHandlerService.createFor(getCompareConfiguration().getContainer(), fLeft.getSourceViewer().getControl().getShell()); + + final String ignoreAncestorActionKey= "action.IgnoreAncestor."; //$NON-NLS-1$ + Action ignoreAncestorAction= new Action() { + public void run() { + // First make sure the ancestor is hidden + if (!isIgnoreAncestor()) + getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, Boolean.FALSE); + // Then set the property to ignore the ancestor + getCompareConfiguration().setProperty(ICompareUIConstants.PROP_IGNORE_ANCESTOR, Boolean.valueOf(!isIgnoreAncestor())); + Utilities.initToggleAction(this, getResourceBundle(), ignoreAncestorActionKey, isIgnoreAncestor()); + } + }; + ignoreAncestorAction.setChecked(isIgnoreAncestor()); + Utilities.initAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey); + Utilities.initToggleAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey, isIgnoreAncestor()); + + fIgnoreAncestorItem= new ActionContributionItem(ignoreAncestorAction); + fIgnoreAncestorItem.setVisible(false); + tbm.appendToGroup("modes", fIgnoreAncestorItem); //$NON-NLS-1$ + + tbm.add(new Separator()); + + Action a= new Action() { + public void run() { + if (navigate(true, false, false)) { + endOfDocumentReached(true); + } + } + }; + Utilities.initAction(a, getResourceBundle(), "action.NextDiff."); //$NON-NLS-1$ + fNextDiff= new ActionContributionItem(a); + tbm.appendToGroup("navigation", fNextDiff); //$NON-NLS-1$ + // Don't register this action since it is probably registered by the container + + a= new Action() { + public void run() { + if (navigate(false, false, false)) { + endOfDocumentReached(false); + } + } + }; + Utilities.initAction(a, getResourceBundle(), "action.PrevDiff."); //$NON-NLS-1$ + fPreviousDiff= new ActionContributionItem(a); + tbm.appendToGroup("navigation", fPreviousDiff); //$NON-NLS-1$ + // Don't register this action since it is probably registered by the container + + a= new Action() { + public void run() { + if (navigate(true, false, true)) { + endOfDocumentReached(true); + } + } + }; + Utilities.initAction(a, getResourceBundle(), "action.NextChange."); //$NON-NLS-1$ + fNextChange= new ActionContributionItem(a); + tbm.appendToGroup("navigation", fNextChange); //$NON-NLS-1$ + fHandlerService.registerAction(a, "org.eclipse.compare.selectNextChange"); //$NON-NLS-1$ + + a= new Action() { + public void run() { + if (navigate(false, false, true)) { + endOfDocumentReached(false); + } + } + }; + Utilities.initAction(a, getResourceBundle(), "action.PrevChange."); //$NON-NLS-1$ + fPreviousChange= new ActionContributionItem(a); + tbm.appendToGroup("navigation", fPreviousChange); //$NON-NLS-1$ + fHandlerService.registerAction(a, "org.eclipse.compare.selectPreviousChange"); //$NON-NLS-1$ + + CompareConfiguration cc= getCompareConfiguration(); + + if (cc.isRightEditable()) { + a= new Action() { + public void run() { + copyDiffLeftToRight(); + } + }; + Utilities.initAction(a, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$ + fCopyDiffLeftToRightItem= new ActionContributionItem(a); + fCopyDiffLeftToRightItem.setVisible(true); + tbm.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$ + fHandlerService.registerAction(a, "org.eclipse.compare.copyLeftToRight"); //$NON-NLS-1$ + } + + if (cc.isLeftEditable()) { + a= new Action() { + public void run() { + copyDiffRightToLeft(); + } + }; + Utilities.initAction(a, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$ + fCopyDiffRightToLeftItem= new ActionContributionItem(a); + fCopyDiffRightToLeftItem.setVisible(true); + tbm.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$ + fHandlerService.registerAction(a, "org.eclipse.compare.copyRightToLeft"); //$NON-NLS-1$ + } + + fIgnoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(getResourceBundle(), getCompareConfiguration()); + fIgnoreWhitespace.setActionDefinitionId(ICompareUIConstants.COMMAND_IGNORE_WHITESPACE); + fLeft.addTextAction(fIgnoreWhitespace); + fRight.addTextAction(fIgnoreWhitespace); + fAncestor.addTextAction(fIgnoreWhitespace); + fHandlerService.registerAction(fIgnoreWhitespace, fIgnoreWhitespace.getActionDefinitionId()); + + boolean needsLeftPainter= !isEditorBacked(fLeft.getSourceViewer()); + boolean needsRightPainter= !isEditorBacked(fLeft.getSourceViewer()); + boolean needsAncestorPainter= !isEditorBacked(fAncestor.getSourceViewer()); + showWhitespaceAction = new ShowWhitespaceAction( + new MergeSourceViewer[] {fLeft, fRight, fAncestor}, + new boolean[] {needsLeftPainter, needsRightPainter, needsAncestorPainter }); + fHandlerService.registerAction(showWhitespaceAction, ITextEditorActionDefinitionIds.SHOW_WHITESPACE_CHARACTERS); + + toggleLineNumbersAction = new TextEditorPropertyAction(CompareMessages.TextMergeViewer_16, new MergeSourceViewer[] { + fLeft, fRight, fAncestor + }, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); + fHandlerService.registerAction(toggleLineNumbersAction, ITextEditorActionDefinitionIds.LINENUMBER_TOGGLE); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handlePropertyChangeEvent(org.eclipse.jface.util.PropertyChangeEvent) + */ + protected void handlePropertyChangeEvent(PropertyChangeEvent event) { + String key= event.getProperty(); + + if (key.equals(CompareConfiguration.IGNORE_WHITESPACE) + || key.equals(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS)) { + + fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS); + + update(true); + // selectFirstDiff(true); + if (fFocusPart != null) + handleSelectionChanged(fFocusPart); + +// } else if (key.equals(ComparePreferencePage.USE_SPLINES)) { +// fUseSplines= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES); +// invalidateLines(); + + } else if (key.equals(ComparePreferencePage.USE_SINGLE_LINE)) { + fUseSingleLine= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE); +// fUseResolveUI= fUseSingleLine; + fBasicCenterCurve= null; + updateControls(); + invalidateLines(); + + } else if (key.equals(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES)) { + fHighlightTokenChanges= fPreferenceStore.getBoolean(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES); + updateControls(); + updatePresentation(); + +// } else if (key.equals(ComparePreferencePage.USE_RESOLVE_UI)) { +// fUseResolveUI= fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI); +// updateResolveStatus(); +// invalidateLines(); + + } else if (key.equals(fSymbolicFontName)) { + updateFont(); + invalidateLines(); + + } else if (key.equals(INCOMING_COLOR) || key.equals(OUTGOING_COLOR) || key.equals(CONFLICTING_COLOR) || key.equals(RESOLVED_COLOR)) { + updateColors(null); + invalidateLines(); + invalidateTextPresentation(); + + } else if (key.equals(ComparePreferencePage.SYNCHRONIZE_SCROLLING)) { + boolean b= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING); + setSyncScrolling(b); + + } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)) { + if (!fIsUsingSystemBackground) { + setBackgroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)); + } + + } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)) { + fIsUsingSystemBackground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT); + if (fIsUsingSystemBackground) { + setBackgroundColor(null); + } else { + setBackgroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)); + } + } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) { + if (!fIsUsingSystemForeground) { + setForegroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)); + } + + } else if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)) { + fIsUsingSystemForeground= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT); + if (fIsUsingSystemForeground) { + setForegroundColor(null); + } else { + setForegroundColor(createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)); + } + } else if (key.equals(ICompareUIConstants.PREF_NAVIGATION_END_ACTION)) { + updateControls(); + } else { + super.handlePropertyChangeEvent(event); + + if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) { + update(true); + selectFirstDiff(true); + } + } + } + + private void selectFirstDiff(boolean first) { + + if (fLeft == null || fRight == null) { + return; + } + if (fLeft.getSourceViewer().getDocument() == null || fRight.getSourceViewer().getDocument() == null) { + return; + } + + Diff firstDiff= null; + if (first) + firstDiff= findNext(fRight, -1, -1, false); + else + firstDiff= findPrev(fRight, 9999999, 9999999, false); + setCurrentDiff(firstDiff, true); + } + + + + private void setSyncScrolling(boolean newMode) { + if (fSynchronizedScrolling != newMode) { + fSynchronizedScrolling= newMode; + + scrollVertical(0, 0, 0, null); + + // throw away central control (Sash or Canvas) + Control center= getCenterControl(); + if (center != null && !center.isDisposed()) + center.dispose(); + + fLeft.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling); + fRight.getSourceViewer().getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling); + + fComposite.layout(true); + } + } + + protected void updateToolItems() { + //only update toolbar items if diffs need to be calculated (which + //dictates whether a toolbar gets added at all) + if (!isPatchHunk()){ + if (fIgnoreAncestorItem != null) + fIgnoreAncestorItem.setVisible(isThreeWay()); + + if (fCopyDiffLeftToRightItem != null) { + IAction a= fCopyDiffLeftToRightItem.getAction(); + if (a != null) + a.setEnabled(a.isEnabled() && !fHasErrors); + } + if (fCopyDiffRightToLeftItem != null) { + IAction a= fCopyDiffRightToLeftItem.getAction(); + if (a != null) + a.setEnabled(a.isEnabled() && !fHasErrors); + } + + super.updateToolItems(); + } + } + + //---- painting lines + + private void updateLines(IDocument d) { + + boolean left= false; + boolean right= false; + + // FIXME: this optimization is incorrect because + // it doesn't take replace operations into account where + // the old and new line count does not differ + if (d == fLeft.getSourceViewer().getDocument()) { + int l= fLeft.getLineCount(); + left= fLeftLineCount != l; + fLeftLineCount= l; + } else if (d == fRight.getSourceViewer().getDocument()) { + int l= fRight.getLineCount(); + right= fRightLineCount != l; + fRightLineCount= l; + } + + if (left || right) { + + if (left) { + if (fLeftCanvas != null) + fLeftCanvas.redraw(); + } else { + if (fRightCanvas != null) + fRightCanvas.redraw(); + } + Control center= getCenterControl(); + if (center != null) + center.redraw(); + + updateVScrollBar(); + refreshBirdsEyeView(); + } + } + + private void invalidateLines() { + if (isThreeWay() && isAncestorVisible()) { + if (Utilities.okToUse(fAncestorCanvas)) + fAncestorCanvas.redraw(); + if (fAncestor != null && fAncestor.isControlOkToUse()) + fAncestor.getSourceViewer().getTextWidget().redraw(); + } + + if (Utilities.okToUse(fLeftCanvas)) + fLeftCanvas.redraw(); + + if (fLeft != null && fLeft.isControlOkToUse()) + fLeft.getSourceViewer().getTextWidget().redraw(); + + if (Utilities.okToUse(getCenterControl())) + getCenterControl().redraw(); + + if (fRight != null && fRight.isControlOkToUse()) + fRight.getSourceViewer().getTextWidget().redraw(); + + if (Utilities.okToUse(fRightCanvas)) + fRightCanvas.redraw(); + } + + private boolean showResolveUI() { + if (!fUseResolveUI || !isThreeWay() || isIgnoreAncestor()) + return false; + CompareConfiguration cc= getCompareConfiguration(); + // we only enable the new resolve UI if exactly one side is editable + boolean l= cc.isLeftEditable(); + boolean r= cc.isRightEditable(); + //return (l && !r) || (r && !l); + return l || r; + } + + private void paintCenter(Canvas canvas, GC g) { + + Display display= canvas.getDisplay(); + + checkForColorUpdate(display); + + if (! fSynchronizedScrolling) + return; + + int lineHeightLeft= fLeft.getSourceViewer().getTextWidget().getLineHeight(); + int lineHeightRight= fRight.getSourceViewer().getTextWidget().getLineHeight(); + int visibleHeight= fRight.getViewportHeight(); + + Point size= canvas.getSize(); + int x= 0; + int w= size.x; + + g.setBackground(canvas.getBackground()); + g.fillRectangle(x+1, 0, w-2, size.y); + + if (!fIsMotif) { + // draw thin line between center ruler and both texts + g.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW)); + g.fillRectangle(0, 0, 1, size.y); + g.fillRectangle(w-1, 0, 1, size.y); + } + + if (! fHighlightRanges) + return; + + boolean showResolveUI= showResolveUI(); + + if (fMerger.hasChanges()) { + int lshift= fLeft.getVerticalScrollOffset(); + int rshift= fRight.getVerticalScrollOffset(); + + Point region= new Point(0, 0); + + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted()) + continue; + + if (fShowCurrentOnly2 && !isCurrentDiff(diff)) + continue; + + fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region); + int ly= (region.x * lineHeightLeft) + lshift; + int lh= region.y * lineHeightLeft; + + fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region); + int ry= (region.x * lineHeightRight) + rshift; + int rh= region.y * lineHeightRight; + + if (Math.max(ly+lh, ry+rh) < 0) + continue; + if (Math.min(ly, ry) >= visibleHeight) + break; + + fPts[0]= x; fPts[1]= ly; fPts[2]= w; fPts[3]= ry; + fPts[6]= x; fPts[7]= ly+lh; fPts[4]= w; fPts[5]= ry+rh; + + Color fillColor= getColor(display, getFillColor(diff)); + Color strokeColor= getColor(display, getStrokeColor(diff)); + + if (fUseSingleLine) { + int w2= 3; + + g.setBackground(fillColor); + g.fillRectangle(0, ly, w2, lh); // left + g.fillRectangle(w-w2, ry, w2, rh); // right + + g.setLineWidth(0 /* LW */); + g.setForeground(strokeColor); + g.drawRectangle(0-1, ly, w2, lh); // left + g.drawRectangle(w-w2, ry, w2, rh); // right + + if (fUseSplines) { + int[] points= getCenterCurvePoints(w2, ly+lh/2, w-w2, ry+rh/2); + for (int i= 1; i < points.length; i++) + g.drawLine(w2+i-1, points[i-1], w2+i, points[i]); + } else { + g.drawLine(w2, ly+lh/2, w-w2, ry+rh/2); + } + } else { + // two lines + if (fUseSplines) { + g.setBackground(fillColor); + + g.setLineWidth(0 /* LW */); + g.setForeground(strokeColor); + + int[] topPoints= getCenterCurvePoints(fPts[0], fPts[1], fPts[2], fPts[3]); + int[] bottomPoints= getCenterCurvePoints(fPts[6], fPts[7], fPts[4], fPts[5]); + g.setForeground(fillColor); + g.drawLine(0, bottomPoints[0], 0, topPoints[0]); + for (int i= 1; i < bottomPoints.length; i++) { + g.setForeground(fillColor); + g.drawLine(i, bottomPoints[i], i, topPoints[i]); + g.setForeground(strokeColor); + g.drawLine(i-1, topPoints[i-1], i, topPoints[i]); + g.drawLine(i-1, bottomPoints[i-1], i, bottomPoints[i]); + } + } else { + g.setBackground(fillColor); + g.fillPolygon(fPts); + + g.setLineWidth(0 /* LW */); + g.setForeground(strokeColor); + g.drawLine(fPts[0], fPts[1], fPts[2], fPts[3]); + g.drawLine(fPts[6], fPts[7], fPts[4], fPts[5]); + } + } + + if (fUseSingleLine && showResolveUI && diff.isUnresolvedIncomingOrConflicting()) { + // draw resolve state + int cx= (w-RESOLVE_SIZE)/2; + int cy= ((ly+lh/2) + (ry+rh/2) - RESOLVE_SIZE)/2; + + g.setBackground(fillColor); + g.fillRectangle(cx, cy, RESOLVE_SIZE, RESOLVE_SIZE); + + g.setForeground(strokeColor); + g.drawRectangle(cx, cy, RESOLVE_SIZE, RESOLVE_SIZE); + } + } + } + } + + private int[] getCenterCurvePoints(int startx, int starty, int endx, int endy) { + if (fBasicCenterCurve == null) + buildBaseCenterCurve(endx-startx); + double height= endy - starty; + height= height/2; + int width= endx-startx; + int[] points= new int[width]; + for (int i= 0; i < width; i++) { + points[i]= (int) (-height * fBasicCenterCurve[i] + height + starty); + } + return points; + } + + private void buildBaseCenterCurve(int w) { + double width= w; + fBasicCenterCurve= new double[getCenterWidth()]; + for (int i= 0; i < getCenterWidth(); i++) { + double r= i / width; + fBasicCenterCurve[i]= Math.cos(Math.PI * r); + } + } + + private void paintSides(GC g, MergeSourceViewer tp, Canvas canvas, boolean right) { + + Display display= canvas.getDisplay(); + + int lineHeight= tp.getSourceViewer().getTextWidget().getLineHeight(); + int visibleHeight= tp.getViewportHeight(); + + Point size= canvas.getSize(); + int x= 0; + int w= fMarginWidth; + int w2= w/2; + + g.setBackground(canvas.getBackground()); + g.fillRectangle(x, 0, w, size.y); + + if (!fIsMotif) { + // draw thin line between ruler and text + g.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW)); + if (right) + g.fillRectangle(0, 0, 1, size.y); + else + g.fillRectangle(size.x-1, 0, 1, size.y); + } + + if (! fHighlightRanges) + return; + + if (fMerger.hasChanges()) { + int shift= tp.getVerticalScrollOffset() + (2-LW); + + Point region= new Point(0, 0); + char leg = getLeg(tp); + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted()) + continue; + + if (fShowCurrentOnly2 && !isCurrentDiff(diff)) + continue; + + tp.getLineRange(diff.getPosition(leg), region); + int y= (region.x * lineHeight) + shift; + int h= region.y * lineHeight; + + if (y+h < 0) + continue; + if (y >= visibleHeight) + break; + + g.setBackground(getColor(display, getFillColor(diff))); + if (right) + g.fillRectangle(x, y, w2, h); + else + g.fillRectangle(x+w2, y, w2, h); + + g.setLineWidth(0 /* LW */); + g.setForeground(getColor(display, getStrokeColor(diff))); + if (right) + g.drawRectangle(x-1, y-1, w2, h); + else + g.drawRectangle(x+w2, y-1, w2, h); + } + } + } + + private void paint(PaintEvent event, MergeSourceViewer tp) { + + if (! fHighlightRanges) + return; + if (!fMerger.hasChanges()) + return; + + Control canvas= (Control) event.widget; + GC g= event.gc; + + Display display= canvas.getDisplay(); + + int lineHeight= tp.getSourceViewer().getTextWidget().getLineHeight(); + int w= canvas.getSize().x; + int shift= tp.getVerticalScrollOffset() + (2-LW); + int maxh= event.y+event.height; // visibleHeight + + //if (fIsMotif) + shift+= fTopInset; + + Point range= new Point(0, 0); + + char leg = getLeg(tp); + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted()) + continue; + + if (fShowCurrentOnly && !isCurrentDiff(diff)) + continue; + + tp.getLineRange(diff.getPosition(leg), range); + int y= (range.x * lineHeight) + shift; + int h= range.y * lineHeight; + + if (y+h < event.y) + continue; + if (y > maxh) + break; + + g.setBackground(getColor(display, getStrokeColor(diff))); + g.fillRectangle(0, y-1, w, LW); + g.fillRectangle(0, y+h-1, w, LW); + } + } + + private RGB getFillColor(Diff diff) { + boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff; + RGB selected_fill= getBackground(null); + if (isThreeWay() && !isIgnoreAncestor()) { + switch (diff.getKind()) { + case RangeDifference.RIGHT: + if (fLeftIsLocal) + return selected ? selected_fill : INCOMING_FILL; + return selected ? selected_fill : OUTGOING_FILL; + case RangeDifference.ANCESTOR: + return selected ? selected_fill : CONFLICT_FILL; + case RangeDifference.LEFT: + if (fLeftIsLocal) + return selected ? selected_fill : OUTGOING_FILL; + return selected ? selected_fill : INCOMING_FILL; + case RangeDifference.CONFLICT: + return selected ? selected_fill : CONFLICT_FILL; + } + return null; + } + return selected ? selected_fill : OUTGOING_FILL; + } + + private RGB getStrokeColor(Diff diff) { + boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff; + + if (isThreeWay() && !isIgnoreAncestor()) { + switch (diff.getKind()) { + case RangeDifference.RIGHT: + if (fLeftIsLocal) + return selected ? SELECTED_INCOMING : INCOMING; + return selected ? SELECTED_OUTGOING : OUTGOING; + case RangeDifference.ANCESTOR: + return selected ? SELECTED_CONFLICT : CONFLICT; + case RangeDifference.LEFT: + if (fLeftIsLocal) + return selected ? SELECTED_OUTGOING : OUTGOING; + return selected ? SELECTED_INCOMING : INCOMING; + case RangeDifference.CONFLICT: + return selected ? SELECTED_CONFLICT : CONFLICT; + } + return null; + } + return selected ? SELECTED_OUTGOING : OUTGOING; + } + + private Color getColor(Display display, RGB rgb) { + if (rgb == null) + return null; + if (fColors == null) + fColors= new HashMap(20); + Color c= (Color) fColors.get(rgb); + if (c == null) { + c= new Color(display, rgb); + fColors.put(rgb, c); + } + return c; + } + + static RGB interpolate(RGB fg, RGB bg, double scale) { + if (fg != null && bg != null) + return new RGB( + (int)((1.0-scale) * fg.red + scale * bg.red), + (int)((1.0-scale) * fg.green + scale * bg.green), + (int)((1.0-scale) * fg.blue + scale * bg.blue) + ); + if (fg != null) + return fg; + if (bg != null) + return bg; + return new RGB(128, 128, 128); // a gray + } + + //---- Navigating and resolving Diffs + + private Diff getNextVisibleDiff(boolean down, boolean deep) { + Diff diff= null; + MergeSourceViewer part= getNavigationPart(); + if (part == null) + return null; + Point s = part.getSourceViewer().getSelectedRange(); + char leg = getLeg(part); + for (;;) { + diff = null; + diff = internalGetNextDiff(down, deep, part, s); + if (diff != null && diff.getKind() == RangeDifference.ANCESTOR + && !isAncestorVisible()) { + Position position = diff.getPosition(leg); + s = new Point(position.getOffset(), position.getLength()); + diff= null; + continue; + } + break; + } + return diff; + } + + private Diff internalGetNextDiff(boolean down, boolean deep, MergeSourceViewer part, Point s) { + if (fMerger.hasChanges()) { + if (down) + return findNext(part, s.x, s.x+s.y, deep); + return findPrev(part, s.x, s.x+s.y, deep); + } + return null; + } + + private MergeSourceViewer getNavigationPart() { + MergeSourceViewer part= fFocusPart; + if (part == null) + part= fRight; + return part; + } + + private Diff getWrappedDiff(Diff diff, boolean down) { + return fMerger.getWrappedDiff(diff, down); + } + + /* + * Returns true if end (or beginning) of document reached. + */ + private boolean navigate(boolean down, boolean wrap, boolean deep) { + Diff diff= null; + boolean wrapped = false; + for (;;) { + diff = getNextVisibleDiff(down, deep); + if (diff == null && wrap) { + if (wrapped) + // We've already wrapped once so break out + break; + wrapped = true; + diff = getWrappedDiff(diff, down); + } + if (diff != null) + setCurrentDiff(diff, true, deep); + if (diff != null && diff.getKind() == RangeDifference.ANCESTOR + && !isAncestorVisible()) + continue; + break; + } + return diff == null; + } + + private void endOfDocumentReached(boolean down) { + Control c= getControl(); + if (Utilities.okToUse(c)) { + handleEndOfDocumentReached(c.getShell(), down); + } + } + + private void handleEndOfDocumentReached(Shell shell, boolean next) { + IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore(); + String value = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION); + if (!value.equals(ICompareUIConstants.PREF_VALUE_PROMPT)) { + performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION, next); + } else { + shell.getDisplay().beep(); + String loopMessage; + String nextMessage; + String message; + String title; + if (next) { + title = CompareMessages.TextMergeViewer_0; + message = CompareMessages.TextMergeViewer_1; + loopMessage = CompareMessages.TextMergeViewer_2; + nextMessage = CompareMessages.TextMergeViewer_3; + } else { + title = CompareMessages.TextMergeViewer_4; + message = CompareMessages.TextMergeViewer_5; + loopMessage = CompareMessages.TextMergeViewer_6; + nextMessage = CompareMessages.TextMergeViewer_7; + } + String[] localLoopOption = new String[] { loopMessage, ICompareUIConstants.PREF_VALUE_LOOP }; + String[] nextElementOption = new String[] { nextMessage, ICompareUIConstants.PREF_VALUE_NEXT}; + String[] doNothingOption = new String[] { CompareMessages.TextMergeViewer_17, ICompareUIConstants.PREF_VALUE_DO_NOTHING}; + NavigationEndDialog dialog = new NavigationEndDialog(shell, + title, + null, + message, + new String[][] { + localLoopOption, + nextElementOption, + doNothingOption + }); + int result = dialog.open(); + if (result == Window.OK) { + performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, next); + if (dialog.getToggleState()) { + String oldValue = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION); + store.putValue(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL)); + store.firePropertyChangeEvent(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, oldValue, store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL)); + } + } + } + } + + private void performEndOfDocumentAction(Shell shell, IPreferenceStore store, String key, boolean next) { + String value = store.getString(key); + if (value.equals(ICompareUIConstants.PREF_VALUE_DO_NOTHING)) { + return; + } + if (value.equals(ICompareUIConstants.PREF_VALUE_NEXT)) { + ICompareNavigator navigator = getCompareConfiguration() + .getContainer().getNavigator(); + if (hasNextElement(next)) { + navigator.selectChange(next); + } + } else { + selectFirstDiff(next); + } + } + + private boolean hasNextElement(boolean down) { + ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator(); + if (navigator instanceof CompareNavigator) { + CompareNavigator n = (CompareNavigator) navigator; + return n.hasChange(down); + } + return false; + } + + /* + * Find the Diff that overlaps with the given TextPart's text range. + * If the range doesn't overlap with any range <code>null</code> + * is returned. + */ + private Diff findDiff(MergeSourceViewer tp, int rangeStart, int rangeEnd) { + char contributor = getLeg(tp); + return fMerger.findDiff(contributor, rangeStart, rangeEnd); + } + + private Diff findNext(MergeSourceViewer tp, int start, int end, boolean deep) { + return fMerger.findNext(getLeg(tp), start, end, deep); + } + + private Diff findPrev(MergeSourceViewer tp, int start, int end, boolean deep) { + return fMerger.findPrev(getLeg(tp), start, end, deep); + } + + /* + * Set the currently active Diff and update the toolbars controls and lines. + * If <code>revealAndSelect</code> is <code>true</code> the Diff is revealed and + * selected in both TextParts. + */ + private void setCurrentDiff(Diff d, boolean revealAndSelect) { + setCurrentDiff(d, revealAndSelect, false); + } + + /* + * Set the currently active Diff and update the toolbars controls and lines. + * If <code>revealAndSelect</code> is <code>true</code> the Diff is revealed and + * selected in both TextParts. + */ + private void setCurrentDiff(Diff d, boolean revealAndSelect, boolean deep) { + +// if (d == fCurrentDiff) +// return; + boolean diffChanged = fCurrentDiff != d; + + if (fCenterButton != null && !fCenterButton.isDisposed()) + fCenterButton.setVisible(false); + + if (d != null && revealAndSelect) { + + // before we set fCurrentDiff we change the selection + // so that the paint code uses the old background colors + // otherwise selection isn't drawn correctly + if (d.isToken() || !fHighlightTokenChanges || deep || !d.hasChildren()) { + if (isThreeWay() && !isIgnoreAncestor()) + fAncestor.setSelection(d.getPosition(ANCESTOR_CONTRIBUTOR)); + fLeft.setSelection(d.getPosition(LEFT_CONTRIBUTOR)); + fRight.setSelection(d.getPosition(RIGHT_CONTRIBUTOR)); + } else { + if (isThreeWay() && !isIgnoreAncestor()) + fAncestor.setSelection(new Position(d.getPosition(ANCESTOR_CONTRIBUTOR).offset, 0)); + fLeft.setSelection(new Position(d.getPosition(LEFT_CONTRIBUTOR).offset, 0)); + fRight.setSelection(new Position(d.getPosition(RIGHT_CONTRIBUTOR).offset, 0)); + } + + // now switch diffs + fCurrentDiff= d; + revealDiff(d, d.isToken()); + } else { + fCurrentDiff= d; + } + + updateControls(); + if (diffChanged) + invalidateLines(); + refreshBirdsEyeView(); + } + + /* + * Smart determines whether + */ + private void revealDiff(Diff d, boolean smart) { + + boolean ancestorIsVisible= false; + boolean leftIsVisible= false; + boolean rightIsVisible= false; + + if (smart) { + Point region= new Point(0, 0); + // find the starting line of the diff in all text widgets + int ls= fLeft.getLineRange(d.getPosition(LEFT_CONTRIBUTOR), region).x; + int rs= fRight.getLineRange(d.getPosition(RIGHT_CONTRIBUTOR), region).x; + + if (isThreeWay() && !isIgnoreAncestor()) { + int as= fAncestor.getLineRange(d.getPosition(ANCESTOR_CONTRIBUTOR), region).x; + if (as >= fAncestor.getSourceViewer().getTopIndex() && as <= fAncestor.getSourceViewer().getBottomIndex()) + ancestorIsVisible= true; + } + + if (ls >= fLeft.getSourceViewer().getTopIndex() && ls <= fLeft.getSourceViewer().getBottomIndex()) + leftIsVisible= true; + + if (rs >= fRight.getSourceViewer().getTopIndex() && rs <= fRight.getSourceViewer().getBottomIndex()) + rightIsVisible= true; + } + + // vertical scrolling + if (!leftIsVisible || !rightIsVisible) { + int avpos= 0, lvpos= 0, rvpos= 0; + + MergeSourceViewer allButThis= null; + if (leftIsVisible) { + avpos= lvpos= rvpos= realToVirtualPosition(LEFT_CONTRIBUTOR, fLeft.getSourceViewer().getTopIndex()); + allButThis= fLeft; + } else if (rightIsVisible) { + avpos= lvpos= rvpos= realToVirtualPosition(RIGHT_CONTRIBUTOR, fRight.getSourceViewer().getTopIndex()); + allButThis= fRight; + } else if (ancestorIsVisible) { + avpos= lvpos= rvpos= realToVirtualPosition(ANCESTOR_CONTRIBUTOR, fAncestor.getSourceViewer().getTopIndex()); + allButThis= fAncestor; + } else { + int vpos= 0; + for (Iterator iterator = fMerger.rangesIterator(); iterator + .hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff == d) + break; + if (fSynchronizedScrolling) { + vpos+= diff.getMaxDiffHeight(); + } else { + avpos+= diff.getAncestorHeight(); + lvpos+= diff.getLeftHeight(); + rvpos+= diff.getRightHeight(); + } + } + if (fSynchronizedScrolling) + avpos= lvpos= rvpos= vpos; + int delta= fRight.getViewportLines()/4; + avpos-= delta; + if (avpos < 0) + avpos= 0; + lvpos-= delta; + if (lvpos < 0) + lvpos= 0; + rvpos-= delta; + if (rvpos < 0) + rvpos= 0; + } + + scrollVertical(avpos, lvpos, rvpos, allButThis); + + if (fVScrollBar != null) + fVScrollBar.setSelection(avpos); + } + + // horizontal scrolling + if (d.isToken()) { + // we only scroll horizontally for token diffs + reveal(fAncestor, d.getPosition(ANCESTOR_CONTRIBUTOR)); + reveal(fLeft, d.getPosition(LEFT_CONTRIBUTOR)); + reveal(fRight, d.getPosition(RIGHT_CONTRIBUTOR)); + } else { + // in all other cases we reset the horizontal offset + hscroll(fAncestor); + hscroll(fLeft); + hscroll(fRight); + } + } + + private static void reveal(MergeSourceViewer v, Position p) { + if (v != null && p != null) { + StyledText st= v.getSourceViewer().getTextWidget(); + if (st != null) { + Rectangle r= st.getClientArea(); + if (!r.isEmpty()) // workaround for #7320: Next diff scrolls when going into current diff + v.getSourceViewer().revealRange(p.offset, p.length); + } + } + } + + private static void hscroll(MergeSourceViewer v) { + if (v != null) { + StyledText st= v.getSourceViewer().getTextWidget(); + if (st != null) + st.setHorizontalIndex(0); + } + } + + //-------------------------------------------------------------------------------- + + void copyAllUnresolved(boolean leftToRight) { + if (fMerger.hasChanges() && isThreeWay() && !isIgnoreAncestor()) { + IRewriteTarget target= leftToRight ? fRight.getSourceViewer().getRewriteTarget() : fLeft.getSourceViewer().getRewriteTarget(); + boolean compoundChangeStarted= false; + try { + for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + switch (diff.getKind()) { + case RangeDifference.LEFT: + if (leftToRight) { + if (!compoundChangeStarted) { + target.beginCompoundChange(); + compoundChangeStarted= true; + } + copy(diff, leftToRight); + } + break; + case RangeDifference.RIGHT: + if (!leftToRight) { + if (!compoundChangeStarted) { + target.beginCompoundChange(); + compoundChangeStarted= true; + } + copy(diff, leftToRight); + } + break; + default: + continue; + } + } + } finally { + if (compoundChangeStarted) { + target.endCompoundChange(); + } + } + } + } + + /* + * Copy whole document from one side to the other. + */ + protected void copy(boolean leftToRight) { + if (!validateChange(!leftToRight)) + return; + if (showResolveUI()) { + copyAllUnresolved(leftToRight); + invalidateLines(); + return; + } + copyOperationInProgress = true; + if (leftToRight) { + if (fLeft.getEnabled()) { + // copy text + String text= fLeft.getSourceViewer().getTextWidget().getText(); + fRight.getSourceViewer().getTextWidget().setText(text); + fRight.setEnabled(true); + } else { + // delete + fRight.getSourceViewer().getTextWidget().setText(""); //$NON-NLS-1$ + fRight.setEnabled(false); + } + fRightLineCount= fRight.getLineCount(); + setRightDirty(true); + } else { + if (fRight.getEnabled()) { + // copy text + String text= fRight.getSourceViewer().getTextWidget().getText(); + fLeft.getSourceViewer().getTextWidget().setText(text); + fLeft.setEnabled(true); + } else { + // delete + fLeft.getSourceViewer().getTextWidget().setText(""); //$NON-NLS-1$ + fLeft.setEnabled(false); + } + fLeftLineCount= fLeft.getLineCount(); + setLeftDirty(true); + } + copyOperationInProgress = false; + update(false); + selectFirstDiff(true); + } + + private void historyNotification(OperationHistoryEvent event) { + switch (event.getEventType()) { + case OperationHistoryEvent.OPERATION_ADDED: + if (copyOperationInProgress) { + copyUndoable = event.getOperation(); + } + break; + case OperationHistoryEvent.UNDONE: + if (copyUndoable == event.getOperation()) { + update(false); + } + break; + default: + // Nothing to do + break; + } + } + + private void copyDiffLeftToRight() { + copy(fCurrentDiff, true, false); + } + + private void copyDiffRightToLeft() { + copy(fCurrentDiff, false, false); + } + + /* + * Copy the contents of the given diff from one side to the other. + */ + private void copy(Diff diff, boolean leftToRight, boolean gotoNext) { + if (copy(diff, leftToRight)) { + if (gotoNext) { + navigate(true, true, false /* don't step in */); + } else { + revealDiff(diff, true); + updateControls(); + } + } + } + + /* + * Copy the contents of the given diff from one side to the other but + * doesn't reveal anything. + * Returns true if copy was successful. + */ + private boolean copy(Diff diff, boolean leftToRight) { + + if (diff != null && !diff.isResolved()) { + if (!validateChange(!leftToRight)) + return false; + if (leftToRight) { + fRight.setEnabled(true); + } else { + fLeft.setEnabled(true); + } + boolean result = fMerger.copy(diff, leftToRight); + if (result) + updateResolveStatus(); + return result; + } + return false; + } + + private boolean validateChange(boolean left) { + ContributorInfo info; + if (left) + info = fLeftContributor; + else + info = fRightContributor; + + return info.validateChange(); + } + + //---- scrolling + + /* + * The height of the TextEditors in lines. + */ + private int getViewportHeight() { + StyledText te= fLeft.getSourceViewer().getTextWidget(); + + int vh= te.getClientArea().height; + if (vh == 0) { + Rectangle trim= te.computeTrim(0, 0, 0, 0); + int scrollbarHeight= trim.height; + + int headerHeight= getHeaderHeight(); + + Composite composite= (Composite) getControl(); + Rectangle r= composite.getClientArea(); + + vh= r.height-headerHeight-scrollbarHeight; + } + + return vh / te.getLineHeight(); + } + + /* + * Returns the virtual position for the given view position. + */ + private int realToVirtualPosition(char contributor, int vpos) { + if (! fSynchronizedScrolling) + return vpos; + return fMerger.realToVirtualPosition(contributor, vpos); + } + + private void scrollVertical(int avpos, int lvpos, int rvpos, MergeSourceViewer allBut) { + + int s= 0; + + if (fSynchronizedScrolling) { + s= fMerger.getVirtualHeight() - rvpos; + int height= fRight.getViewportLines()/4; + if (s < 0) + s= 0; + if (s > height) + s= height; + } + + fInScrolling= true; + + if (isThreeWay() && allBut != fAncestor) { + if (fSynchronizedScrolling || allBut == null) { + int y= virtualToRealPosition(ANCESTOR_CONTRIBUTOR, avpos+s)-s; + fAncestor.vscroll(y); + } + } + + if (allBut != fLeft) { + if (fSynchronizedScrolling || allBut == null) { + int y= virtualToRealPosition(LEFT_CONTRIBUTOR, lvpos+s)-s; + fLeft.vscroll(y); + } + } + + if (allBut != fRight) { + if (fSynchronizedScrolling || allBut == null) { + int y= virtualToRealPosition(RIGHT_CONTRIBUTOR, rvpos+s)-s; + fRight.vscroll(y); + } + } + + fInScrolling= false; + + if (isThreeWay() && fAncestorCanvas != null) + fAncestorCanvas.repaint(); + + if (fLeftCanvas != null) + fLeftCanvas.repaint(); + + Control center= getCenterControl(); + if (center instanceof BufferedCanvas) + ((BufferedCanvas)center).repaint(); + + if (fRightCanvas != null) + fRightCanvas.repaint(); + } + + /* + * Updates Scrollbars with viewports. + */ + private void syncViewport(MergeSourceViewer w) { + + if (fInScrolling) + return; + + int ix= w.getSourceViewer().getTopIndex(); + int ix2= w.getDocumentRegionOffset(); + + int viewPosition= realToVirtualPosition(getLeg(w), ix-ix2); + + scrollVertical(viewPosition, viewPosition, viewPosition, w); // scroll all but the given views + + if (fVScrollBar != null) { + int value= Math.max(0, Math.min(viewPosition, fMerger.getVirtualHeight() - getViewportHeight())); + fVScrollBar.setSelection(value); + //refreshBirdEyeView(); + } + } + + /** + */ + private void updateVScrollBar() { + + if (Utilities.okToUse(fVScrollBar) && fSynchronizedScrolling) { + int virtualHeight= fMerger.getVirtualHeight(); + int viewPortHeight= getViewportHeight(); + int pageIncrement= viewPortHeight-1; + int thumb= (viewPortHeight > virtualHeight) ? virtualHeight : viewPortHeight; + + fVScrollBar.setPageIncrement(pageIncrement); + fVScrollBar.setMaximum(virtualHeight); + fVScrollBar.setThumb(thumb); + } + } + + /* + * maps given virtual position into a real view position of this view. + */ + private int virtualToRealPosition(char contributor, int v) { + if (! fSynchronizedScrolling) + return v; + return fMerger.virtualToRealPosition(contributor, v); + } + + void flushLeftSide(Object oldInput, IProgressMonitor monitor){ + IMergeViewerContentProvider content= getMergeContentProvider(); + Object leftContent = content.getLeftContent(oldInput); + + if (leftContent != null && getCompareConfiguration().isLeftEditable() && isLeftDirty()) { + if (fLeftContributor.hasSharedDocument(leftContent)) { + if (flush(fLeftContributor)) + setLeftDirty(false); + } + } + + if (!(content instanceof MergeViewerContentProvider) || isLeftDirty()) { + super.flushLeftSide(oldInput, monitor); + } + } + + void flushRightSide(Object oldInput, IProgressMonitor monitor){ + IMergeViewerContentProvider content= getMergeContentProvider(); + Object rightContent = content.getRightContent(oldInput); + + if (rightContent != null && getCompareConfiguration().isRightEditable() && isRightDirty()) { + if (fRightContributor.hasSharedDocument(rightContent)) { + if (flush(fRightContributor)) + setRightDirty(false); + } + } + + if (!(content instanceof MergeViewerContentProvider) || isRightDirty()) { + super.flushRightSide(oldInput, monitor); + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#flushContent(java.lang.Object, org.eclipse.core.runtime.IProgressMonitor) + */ + protected void flushContent(Object oldInput, IProgressMonitor monitor) { + flushLeftSide(oldInput, monitor); + flushRightSide(oldInput, monitor); + + IMergeViewerContentProvider content = getMergeContentProvider(); + + if (!(content instanceof MergeViewerContentProvider) || isLeftDirty() || isRightDirty()) { + super.flushContent(oldInput, monitor); + } + } + + private boolean flush(final ContributorInfo info) { + try { + return info.flush(); + } catch (CoreException e) { + handleException(e); + } + return false; + } + + private void handleException(Throwable throwable) { + // TODO: Should show error to the user + if (throwable instanceof InvocationTargetException) { + InvocationTargetException ite = (InvocationTargetException) throwable; + handleException(ite.getTargetException()); + return; + } + CompareUIPlugin.log(throwable); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) + */ + public Object getAdapter(Class adapter) { + if (adapter == IMergeViewerTestAdapter.class) { + return new IMergeViewerTestAdapter() { + public IDocument getDocument(char leg) { + switch (leg) { + case LEFT_CONTRIBUTOR: + return fLeft.getSourceViewer().getDocument(); + case RIGHT_CONTRIBUTOR: + return fRight.getSourceViewer().getDocument(); + case ANCESTOR_CONTRIBUTOR: + return fAncestor.getSourceViewer().getDocument(); + } + return null; + } + }; + } + if (adapter == OutlineViewerCreator.class) { + if (fOutlineViewerCreator == null) + fOutlineViewerCreator = new InternalOutlineViewerCreator(); + return fOutlineViewerCreator; + + } + if (adapter == IFindReplaceTarget.class) + return getFindReplaceTarget(); + if (adapter == CompareHandlerService.class) + return fHandlerService; + if (adapter == CompareHandlerService[].class) { + return new CompareHandlerService[] { fHandlerService, + super.getCompareHandlerService() }; + } + if (adapter == IEditorInput.class) { + // return active editor input + if (fLeft != null && fLeft == fFocusPart) + if (fLeftContributor != null) + return fLeftContributor.getDocumentKey(); + if (fRight != null && fRight == fFocusPart) + if (fRightContributor != null) + return fRightContributor.getDocumentKey(); + if (fAncestor != null && fAncestor == fFocusPart) + if (fAncestorContributor != null) + return fAncestorContributor.getDocumentKey(); + } + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleCompareInputChange() + */ + protected void handleCompareInputChange() { + try { + beginRefresh(); + super.handleCompareInputChange(); + } finally { + endRefresh(); + } + } + + private void beginRefresh() { + isRefreshing++; + fLeftContributor.cacheSelection(fLeft); + fRightContributor.cacheSelection(fRight); + fAncestorContributor.cacheSelection(fAncestor); + if (fSynchronizedScrolling) { + fSynchronziedScrollPosition = fVScrollBar.getSelection(); + } + + } + + private void endRefresh() { + isRefreshing--; + fLeftContributor.cacheSelection(null); + fRightContributor.cacheSelection(null); + fAncestorContributor.cacheSelection(null); + fSynchronziedScrollPosition = -1; + } + + private void synchronizedScrollVertical(int vpos) { + scrollVertical(vpos, vpos, vpos, null); + } + + private boolean isIgnoreAncestor() { + return Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false); + } + + /* package */ void update(boolean includeControls) { + if (getControl().isDisposed()) + return; + if (fHasErrors) { + resetDiffs(); + } else { + doDiff(); + } + + if (includeControls) + updateControls(); + + updateVScrollBar(); + updatePresentation(); + } + + private void resetDiffs() { + // clear stuff + fCurrentDiff= null; + fMerger.reset(); + resetPositions(fLeft.getSourceViewer().getDocument()); + resetPositions(fRight.getSourceViewer().getDocument()); + resetPositions(fAncestor.getSourceViewer().getDocument()); + } + + private boolean isPatchHunk() { + return Utilities.isHunk(getInput()); + } + + private boolean isPatchHunkOk() { + if (isPatchHunk()) + return Utilities.isHunkOk(getInput()); + return false; + } + + /** + * Return the provided start position of the hunk in the target file. + * @return the provided start position of the hunk in the target file + */ + private int getHunkStart() { + Object input = getInput(); + if (input != null && input instanceof DiffNode){ + ITypedElement right = ((DiffNode) input).getRight(); + if (right != null) { + Object element = Utilities.getAdapter(right, IHunk.class); + if (element instanceof IHunk) + return ((IHunk)element).getStartPosition(); + } + ITypedElement left = ((DiffNode) input).getLeft(); + if (left != null) { + Object element = Utilities.getAdapter(left, IHunk.class); + if (element instanceof IHunk) + return ((IHunk)element).getStartPosition(); + } + } + return 0; + } + + private IFindReplaceTarget getFindReplaceTarget() { + if (fFindReplaceTarget == null) + fFindReplaceTarget= new FindReplaceTarget(); + return fFindReplaceTarget; + } + + /* package */ char getLeg(MergeSourceViewer w) { + if (w == fLeft) + return LEFT_CONTRIBUTOR; + if (w == fRight) + return RIGHT_CONTRIBUTOR; + if (w == fAncestor) + return ANCESTOR_CONTRIBUTOR; + return ANCESTOR_CONTRIBUTOR; + } + + private boolean isCurrentDiff(Diff diff) { + if (diff == null) + return false; + if (diff == fCurrentDiff) + return true; + if (fCurrentDiff != null && fCurrentDiff.getParent() == diff) + return true; + return false; + } + + private boolean isNavigationPossible() { + if (fCurrentDiff == null && fMerger.hasChanges()) + return true; + else if (fMerger.changesCount() > 1) + return true; + else if (fCurrentDiff != null && fCurrentDiff.hasChildren()) + return true; + else if (fCurrentDiff != null && fCurrentDiff.isToken()) + return true; + return false; + } + + /** + * This method returns {@link ITextEditor} used in the + * {@link ChangeEncodingAction}. It provides implementation of methods that + * are used by the action by delegating them to {@link ContributorInfo} that + * corresponds to the side that has focus. + * + * @return the text editor adapter + */ + private ITextEditor getTextEditorAdapter() { + return new ITextEditor() { + public void close(boolean save) { + // Implementing interface method + } + public void doRevertToSaved() { + // Implementing interface method + } + public IAction getAction(String actionId) { + // Implementing interface method + return null; + } + public IDocumentProvider getDocumentProvider() { + // Implementing interface method + return null; + } + public IRegion getHighlightRange() { + // Implementing interface method + return null; + } + public ISelectionProvider getSelectionProvider() { + // Implementing interface method + return null; + } + public boolean isEditable() { + // Implementing interface method + return false; + } + public void removeActionActivationCode(String actionId) { + // Implementing interface method + } + public void resetHighlightRange() { + // Implementing interface method + } + public void selectAndReveal(int offset, int length) { + // Implementing interface method + } + public void setAction(String actionId, IAction action) { + // Implementing interface method + } + public void setActionActivationCode(String actionId, + char activationCharacter, int activationKeyCode, + int activationStateMask) { + // Implementing interface method + } + public void setHighlightRange(int offset, int length, + boolean moveCursor) { + // Implementing interface method + } + public void showHighlightRangeOnly(boolean showHighlightRangeOnly) { + // Implementing interface method + } + public boolean showsHighlightRangeOnly() { + // Implementing interface method + return false; + } + public IEditorInput getEditorInput() { + if (fFocusPart == fAncestor && fAncestorContributor != null) { + return fAncestorContributor.getDocumentKey(); + } else if (fFocusPart == fLeft && fLeftContributor != null) { + return fLeftContributor.getDocumentKey(); + } else if (fFocusPart == fRight && fRightContributor != null) { + return fRightContributor.getDocumentKey(); + } else { + return null; + } + } + public IEditorSite getEditorSite() { + // Implementing interface method + return null; + } + public void init(IEditorSite site, IEditorInput input) + throws PartInitException { + // Implementing interface method + } + public void addPropertyListener(IPropertyListener listener) { + // Implementing interface method + } + public void createPartControl(Composite parent) { + // Implementing interface method + } + public void dispose() { + // Implementing interface method + } + public IWorkbenchPartSite getSite() { + // Implementing interface method + return new IWorkbenchPartSite() { + public String getId() { + // Implementing interface method + return null; + } + public IKeyBindingService getKeyBindingService() { + // Implementing interface method + return null; + } + public IWorkbenchPart getPart() { + // Implementing interface method + return null; + } + public String getPluginId() { + // Implementing interface method + return null; + } + public String getRegisteredName() { + // Implementing interface method + return null; + } + public void registerContextMenu(MenuManager menuManager, + ISelectionProvider selectionProvider) { + // Implementing interface method + } + public void registerContextMenu(String menuId, + MenuManager menuManager, + ISelectionProvider selectionProvider) { + // Implementing interface method + } + public IWorkbenchPage getPage() { + // Implementing interface method + return null; + } + public ISelectionProvider getSelectionProvider() { + // Implementing interface method + return null; + } + public Shell getShell() { + return fComposite.getShell(); + } + public IWorkbenchWindow getWorkbenchWindow() { + // Implementing interface method + return null; + } + public void setSelectionProvider(ISelectionProvider provider) { + // Implementing interface method + } + public Object getAdapter(Class adapter) { + // Implementing interface method + return null; + } + public Object getService(Class api) { + // Implementing interface method + return null; + } + public boolean hasService(Class api) { + // Implementing interface method + return false; + } + }; + } + public String getTitle() { + // Implementing interface method + return null; + } + public Image getTitleImage() { + // Implementing interface method + return null; + } + public String getTitleToolTip() { + // Implementing interface method + return null; + } + public void removePropertyListener(IPropertyListener listener) { + // Implementing interface method + } + public void setFocus() { + // Implementing interface method + } + public Object getAdapter(Class adapter) { + if (adapter == IEncodingSupport.class) { + if (fFocusPart == fAncestor) { + return getEncodingSupport(fAncestorContributor); + } else if (fFocusPart == fLeft) { + return getEncodingSupport(fLeftContributor); + } else if (fFocusPart == fRight) { + return getEncodingSupport(fRightContributor); + } + } + return null; + } + private IEncodingSupport getEncodingSupport(ContributorInfo contributor) { + if (contributor != null && contributor.getDefaultEncoding() != null) { + return contributor; + } + return null; + } + public void doSave(IProgressMonitor monitor) { + // Implementing interface method + } + public void doSaveAs() { + // Implementing interface method + } + public boolean isDirty() { + if (fFocusPart == fLeft) { + return isLeftDirty(); + } else if (fFocusPart == fRight) { + return isRightDirty(); + } + return false; + } + public boolean isSaveAsAllowed() { + // Implementing interface method + return false; + } + public boolean isSaveOnCloseNeeded() { + // Implementing interface method + return false; + } + }; + } + + private void updateStructure() { + getCompareConfiguration().setProperty("ALL_STRUCTURE_REFRESH", null); //$NON-NLS-1$ + } + + private void updateStructure(char leg) { + String key = null; + switch (leg) { + case ANCESTOR_CONTRIBUTOR: + key = "ANCESTOR_STRUCTURE_REFRESH"; //$NON-NLS-1$ + break; + case LEFT_CONTRIBUTOR: + key = "LEFT_STRUCTURE_REFRESH"; //$NON-NLS-1$ + break; + case RIGHT_CONTRIBUTOR: + key = "RIGHT_STRUCTURE_REFRESH"; //$NON-NLS-1$ + break; + } + Assert.isNotNull(key); + getCompareConfiguration().setProperty(key, null); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties new file mode 100644 index 000000000..e278ba7cd --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties @@ -0,0 +1,111 @@ +############################################################################### +# Copyright (c) 2000, 2009 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 +############################################################################### +# +# @(#)TextMergeViewerResources.properties +# +# Resource strings for TextMergeViewer.java + +title= Text Compare + +saveDialog.title= Save Resource +saveDialog.message= Resource has been modified. Save changes? + +compareProgressTask.title= Computing Differences... + +tooComplexError.title= Error +tooComplexError.message= Too many differences. Turn on the 'Ignore White Space' option or do a structure compare first. + +##################################################### +# Toolbar actions +##################################################### + +action.CopyLeftToRight.label=Copy Left to Right +action.CopyLeftToRight.tooltip=Copy All from Left to Right +action.CopyLeftToRight.image=copy_r_co.gif + +action.CopyRightToLeft.label=Copy Right to Left +action.CopyRightToLeft.tooltip=Copy All Non-Conflicting Changes from Right to Left +action.CopyRightToLeft.image=copy_l_co.gif + +action.CopyDiffLeftToRight.label=Copy Current Change to Right +action.CopyDiffLeftToRight.tooltip=Copy Current Change from Left to Right +action.CopyDiffLeftToRight.image=copycont_r_co.gif + +action.CopyDiffRightToLeft.label=Copy Current Change to Left +action.CopyDiffRightToLeft.tooltip=Copy Current Change from Right to Left +action.CopyDiffRightToLeft.image=copycont_l_co.gif + +action.NextDiff.label=Next Difference +action.NextDiff.tooltip=Next Difference +action.NextDiff.image=next_diff_nav.gif + +action.PrevDiff.label=Previous Difference +action.PrevDiff.tooltip=Previous Difference +action.PrevDiff.image=prev_diff_nav.gif + +action.NextChange.label=Next Change +action.NextChange.tooltip=Next Change +action.NextChange.image=next_change_nav.gif + +action.PrevChange.label=Previous Change +action.PrevChange.tooltip=Previous Change +action.PrevChange.image=prev_change_nav.gif + +action.EnableAncestor.label=Enable Ancestor Pane +action.EnableAncestor.tooltip.unchecked=Show Ancestor Pane +action.EnableAncestor.tooltip.checked=Hide Ancestor Pane +action.EnableAncestor.description.unchecked=Show Ancestor Pane +action.EnableAncestor.description.checked=Hide Ancestor Pane +action.EnableAncestor.image=ancestorpane_co.gif + +action.IgnoreAncestor.label=Ignore Ancestor +action.IgnoreAncestor.tooltip.unchecked=Two-Way Compare (Ignore Ancestor) +action.IgnoreAncestor.tooltip.checked=Three-Way Compare +action.IgnoreAncestor.description.unchecked=Two-Way Compare (Ignore Ancestor) +action.IgnoreAncestor.description.checked=Three-Way Compare +action.IgnoreAncestor.image=twowaycompare_co.gif + + +##################################################### +# Context menu actions +##################################################### + +action.undo.label=&Undo +action.undo.tooltip=Undo Last Operation + +action.redo.label=&Redo +action.redo.tooltip=Redo Last Operation + +action.cut.label=Cu&t +action.cut.tooltip=Cut Text Selection to Clipboard + +action.copy.label=&Copy +action.copy.tooltip=Copy Text Selection to Clipboard + +action.paste.label=&Paste +action.paste.tooltip=Replace Text Selection with Clipboard Contents + +action.delete.label=&Delete +action.delete.tooltip=Delete Current Text Selection + +action.find.label=&Find... +action.find.tooltip=Find Occurrence + +action.selectAll.label=Select &All +action.selectAll.tooltip=Select All Changes + +Editor.FindReplace.label=&Find/Replace... +Editor.FindReplace.tooltip=Find/Replace +Editor.FindReplace.image= +Editor.FindReplace.description=Find/Replace + +action.IgnoreWhiteSpace.label=&Ignore White Space +action.IgnoreWhiteSpace.tooltip=Ignore White Space Where Applicable diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TokenComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TokenComparator.java new file mode 100644 index 000000000..7370388ba --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TokenComparator.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.contentmergeviewer; + +import org.eclipse.compare.rangedifferencer.IRangeComparator; +import org.eclipse.core.runtime.Assert; + +/** + * Implements the <code>ITokenComparator</code> interface for words (or tokens) + * in a string. + * A <code>TokenComparator</code> is used as the input for the <code>RangeDifferencer</code> + * engine to perform a token oriented compare on strings. + * <p> + * This class may be instantiated by clients but is not intended to be subclassed. + * @since 3.4 + */ +public class TokenComparator implements ITokenComparator { + + private String fText; + private int fCount; + private int[] fStarts; + private int[] fLengths; + + /** + * Creates a <code>TokenComparator</code> for the given string. + * + * @param text the string that is split into token + */ + public TokenComparator(String text) { + + Assert.isNotNull(text); + + fText= text; + + int length= fText.length(); + fStarts= new int[length]; // pessimistic assumption! + fLengths= new int[length]; + fCount= 0; + + char lastCategory= 0; // 0: no category + for (int i= 0; i < length; i++) { + char c= fText.charAt(i); + + char category= '?'; // unspecified category + if (Character.isWhitespace(c)) + category= ' '; // white space category + else if (Character.isDigit(c)) + category= '0'; // digits + else if (Character.isLetter(c)) + category= 'a'; // letters + else if (c == '\"' || c == '\'') + category= '\"'; // quotes (see bug 198671) + + if (category != lastCategory) { + // start a new token + fStarts[fCount++]= i; + lastCategory= category; + } + fLengths[fCount-1]++; + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#getRangeCount() + */ + public int getRangeCount() { + return fCount; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ITokenComparator#getTokenStart(int) + */ + public int getTokenStart(int index) { + if (index < fCount) + return fStarts[index]; + return fText.length(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.contentmergeviewer.ITokenComparator#getTokenLength(int) + */ + public int getTokenLength(int index) { + if (index < fCount) + return fLengths[index]; + return 0; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#rangesEqual(int, org.eclipse.compare.rangedifferencer.IRangeComparator, int) + */ + public boolean rangesEqual(int thisIndex, IRangeComparator other, int otherIndex) { + if (other != null && getClass() == other.getClass()) { + TokenComparator tc= (TokenComparator) other; + int thisLen= getTokenLength(thisIndex); + int otherLen= tc.getTokenLength(otherIndex); + if (thisLen == otherLen) + return fText.regionMatches(false, getTokenStart(thisIndex), tc.fText, tc.getTokenStart(otherIndex), thisLen); + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#skipRangeComparison(int, int, org.eclipse.compare.rangedifferencer.IRangeComparator) + */ + public boolean skipRangeComparison(int length, int max, IRangeComparator other) { + + if (getRangeCount() < 50 || other.getRangeCount() < 50) + return false; + + if (max < 100) + return false; + + if (length < 100) + return false; + + if (max > 800) + return true; + + if (length < max / 4) + return false; + + return true; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/package.html b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/package.html new file mode 100644 index 000000000..9f606fb95 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/package.html @@ -0,0 +1,45 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.75 [en] (WinNT; U) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Support for compare and merge viewers which show the +content side-by-side. +<h2> +Package Specification</h2> + +The <b>ContentMergeViewer</b> is an abstract compare and merge viewer +with two side-by-side content areas and an optional content area for a +common ancestor (for three-way compare). Because the implementation makes +no assumptions about the content type it is a subclass responsibility to +deal with a specific type. Its subclass <b>ImageMergeViewer</b> in +package org.eclipse.compare.internal shows how to base a simple +mergeviewer for images on <b>ContentMergeViewer</b>. +<p> + +A <b>ContentMergeViewer</b> accesses its model by means of a content +provider which must implement the <b>IMergeViewerContentProvider</b> interface. +<p> + +The <b>TextMergeViewer</b> is the standard concrete subclass of +<b>ContentMergeViewer</b> for comparing and merging text content. +<br> +A text merge viewer uses the <b>org.eclipse.compare.rangedifferencer.RangeDifferencer</b> +to perform a textual, line-by-line comparison of two (or three) input documents. +For text lines that differ the <b>TextMergeViewer</b> uses an <b>ITokenComparator</b> +to find longest sequences of matching and non-matching tokens. +The <b>TextMergeViewer</b>'s +default token compare works on characters separated by whitespace. If a +different strategy is needed (for example, Java tokens in a Java-aware +merge viewer), clients can create their own token comparators by implementing +the <b>ITokenComparator</b> interface. +<p>The <b>TextMergeViewer</b> not only works on whole documents but on +subranges of documents too. In this case the viewer's input must be an +<b>IDocumentRange</b> instead of an <b>IDocument</b>. + +</body> +</html> diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java new file mode 100644 index 000000000..f76927a20 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.Viewer; + + +public abstract class AbstractViewer extends Viewer { + + public void setInput(Object input) { + // empty default implementation + } + + public Object getInput() { + return null; + } + + public ISelection getSelection() { + return null; + } + + public void setSelection(ISelection s, boolean reveal) { + // empty default implementation + } + + public void refresh() { + // empty default implementation + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java new file mode 100644 index 000000000..e101563b6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 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.internal; + +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.ui.IContributorResourceAdapter; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; + +public class AdapterFactory implements IAdapterFactory { + + public Object getAdapter(final Object adaptableObject, Class adapterType) { + if (IContributorResourceAdapter.class.equals(adapterType) + && adaptableObject instanceof CompareEditorInput) { + return new IContributorResourceAdapter() { + public IResource getAdaptedResource(IAdaptable adaptable) { + Object ei = ((CompareEditorInput) adaptableObject) + .getAdapter(IEditorInput.class); + if (ei instanceof IFileEditorInput) { + return ((IFileEditorInput) ei).getFile(); + } + return null; + } + }; + } + return null; + } + + public Class[] getAdapterList() { + return new Class[] { IContributorResourceAdapter.class }; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java new file mode 100644 index 000000000..1fdd5c1f6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal; + +import java.util.ResourceBundle; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.viewers.ISelection; + +import org.eclipse.ui.actions.WorkspaceModifyOperation; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + + +public class AddFromHistoryAction extends BaseCompareAction { + + private static final String BUNDLE_NAME= "org.eclipse.compare.internal.AddFromHistoryAction"; //$NON-NLS-1$ + + public AddFromHistoryAction() { + // empty default implementation + } + + protected boolean isEnabled(ISelection selection) { + return Utilities.getResources(selection).length == 1; + } + + protected void run(ISelection selection) { + + ResourceBundle bundle= ResourceBundle.getBundle(BUNDLE_NAME); + String title= Utilities.getString(bundle, "title"); //$NON-NLS-1$ + + Shell parentShell= CompareUIPlugin.getShell(); + AddFromHistoryDialog dialog= null; + + Object[] s= Utilities.getResources(selection); + + for (int i= 0; i < s.length; i++) { + Object o= s[i]; + if (o instanceof IContainer) { + IContainer container= (IContainer) o; + + ProgressMonitorDialog pmdialog= new ProgressMonitorDialog(parentShell); + IProgressMonitor pm= pmdialog.getProgressMonitor(); + IFile[] states= null; + try { + states= container.findDeletedMembersWithHistory(IResource.DEPTH_INFINITE, pm); + } catch (CoreException ex) { + pm.done(); + } + + // There could be a recently deleted file at the same path as + // the container. If such a file is the only one to restore, we + // should not suggest to restore it, so set states to null. + if (states.length == 1 && states[0].getFullPath().equals(container.getFullPath())) + states = null; + + if (states == null || states.length <= 0) { + String msg= Utilities.getString(bundle, "noLocalHistoryError"); //$NON-NLS-1$ + MessageDialog.openInformation(parentShell, title, msg); + return; + } + + if (dialog == null) { + dialog= new AddFromHistoryDialog(parentShell, bundle); + dialog.setHelpContextId(ICompareContextIds.ADD_FROM_HISTORY_DIALOG); + } + + if (dialog.select(container, states)) { + AddFromHistoryDialog.HistoryInput[] selected = dialog + .getSelected(); + if (selected != null && selected.length > 0) { + try { + updateWorkspace(bundle, parentShell, selected); + } catch (InterruptedException x) { + // Do nothing. Operation has been canceled by user. + } catch (InvocationTargetException x) { + String reason = x.getTargetException().getMessage(); + MessageDialog.openError(parentShell, title, + Utilities.getFormattedString(bundle, + "replaceError", reason)); //$NON-NLS-1$ + } + } + } + } + } + } + + void createContainers(IResource resource) throws CoreException { + IContainer container= resource.getParent(); + if (container instanceof IFolder) { + IFolder parent= (IFolder) container; + if (parent != null && !parent.exists()) { + createContainers(parent); + parent.create(false, true, null); + } + } + } + + private void updateWorkspace(final ResourceBundle bundle, Shell shell, + final AddFromHistoryDialog.HistoryInput[] selected) + throws InvocationTargetException, InterruptedException { + + WorkspaceModifyOperation operation= new WorkspaceModifyOperation() { + public void execute(IProgressMonitor pm) throws InvocationTargetException { + try { + String taskName= Utilities.getString(bundle, "taskName"); //$NON-NLS-1$ + pm.beginTask(taskName, selected.length); + + for (int i= 0; i < selected.length; i++) { + IFile file= selected[i].fFile; + IFileState fileState= selected[i].fFileState; + createContainers(file); + + SubProgressMonitor subMonitor= new SubProgressMonitor(pm, 1); + try { + file.create(fileState.getContents(), false, subMonitor); + } finally { + subMonitor.done(); + } + } + } catch (CoreException e) { + throw new InvocationTargetException(e); + } finally { + pm.done(); + } + } + }; + + ProgressMonitorDialog pmdialog= new ProgressMonitorDialog(shell); + pmdialog.run(false, true, operation); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties new file mode 100644 index 000000000..0a97df87f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties @@ -0,0 +1,45 @@ +############################################################################### +# 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 +############################################################################### + +# @(#)AddFromHistoryAction.properties +# +# Resources for AddFromHistoryAction.java + +title= Restore from Local History + +memberPaneTitle= {0} - Available Files in Local History: + +treeTitleFormat= Local History of ''{0}'' +dateIcon= obj16/day_obj.gif +timeIcon= obj16/resource_obj.gif + +memberDescription= Check files to restore from local history: +editionDescription= Select an edition of a file: + +treeFormat= {0} +workspaceTreeFormat= {0} (Workspace File) +parseErrorFormat= {0} (Parse Error) + +editionLabel= Local History ({0}) +workspaceEditionLabel= Workspace File + +targetLabel= {0} + +todayFormat= Today ({0}) +yesterdayFormat= Yesterday ({0}) +dayFormat= {0} + +buttonLabel= &Restore + +noLocalHistoryError= No deleted resources in local history for selected container. +replaceError=Cannot replace resource (reason: {0}). + +taskName=Restoring diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java new file mode 100644 index 000000000..0bb24bc9e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java @@ -0,0 +1,486 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal; + +import java.io.*; +import com.ibm.icu.text.DateFormat; +import com.ibm.icu.text.MessageFormat; +import java.util.ArrayList; +import com.ibm.icu.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.ResourceBundle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +import org.eclipse.jface.dialogs.*; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +import org.eclipse.compare.*; + + +public class AddFromHistoryDialog extends ResizableDialog { + + static class HistoryInput implements ITypedElement, IEncodedStreamContentAccessor, IModificationDate { + IFile fFile; + IFileState fFileState; + + HistoryInput(IFile file, IFileState fileState) { + fFile= file; + fFileState= fileState; + } + public InputStream getContents() throws CoreException { + return new BufferedInputStream(fFileState.getContents()); + } + public String getCharset() { + String charset= null; + try { + charset= fFileState.getCharset(); + } catch (CoreException e) { + // fall through + } + if (charset == null) + charset= Utilities.getCharset(fFile); + return charset; + } + public String getName() { + return fFile.getName(); + } + public String getType() { + return fFile.getFileExtension(); + } + public Image getImage() { + return CompareUI.getImage(fFile); + } + public long getModificationDate() { + return fFileState.getModificationTime(); + } + } + + static class FileHistory { + private IFile fFile; + private IFileState[] fStates; + private int fSelected; + + FileHistory(IFile file) { + fFile= file; + } + + IFile getFile() { + return fFile; + } + + IFileState[] getStates() { + if (fStates == null) { + try { + fStates= fFile.getHistory(new NullProgressMonitor()); + } catch (CoreException ex) { + // NeedWork + } + } + return fStates; + } + + IFileState getSelectedState() { + return getStates()[fSelected]; + } + + void setSelected(IFileState state) { + for (int i= 0; i < fStates.length; i++) { + if (fStates[i] == state) { + fSelected= i; + return; + } + } + } + + HistoryInput getHistoryInput() { + return new HistoryInput(fFile, getSelectedState()); + } + + boolean isSelected(int index) { + return index == fSelected; + } + } + + private CompareConfiguration fCompareConfiguration; + private ArrayList fArrayList= new ArrayList(); + private FileHistory fCurrentFileHistory; + + // SWT controls + private CompareViewerSwitchingPane fContentPane; + private Button fCommitButton; + private Table fMemberTable; + private CompareViewerPane fMemberPane; + private Tree fEditionTree; + private CompareViewerPane fEditionPane; + private Image fDateImage; + private Image fTimeImage; + + + public AddFromHistoryDialog(Shell parent, ResourceBundle bundle) { + super(parent, bundle); + + String iconName= Utilities.getString(fBundle, "dateIcon", "obj16/day_obj.gif"); //$NON-NLS-2$ //$NON-NLS-1$ + ImageDescriptor id= CompareUIPlugin.getImageDescriptor(iconName); + if (id != null) + fDateImage= id.createImage(); + iconName= Utilities.getString(fBundle, "timeIcon", "obj16/resource_obj.gif"); //$NON-NLS-1$ //$NON-NLS-2$ + id= CompareUIPlugin.getImageDescriptor(iconName); + if (id != null) + fTimeImage= id.createImage(); + } + + public boolean select(IContainer root, IFile[] inputFiles) { + + create(); // create widgets + + String format= Utilities.getString(fBundle, "memberPaneTitle"); //$NON-NLS-1$ + String title= MessageFormat.format(format, new Object[] { root.getName() }); + fMemberPane.setImage(CompareUI.getImage(root)); + fMemberPane.setText(title); + + // sort input files + final int count= inputFiles.length; + final IFile[] files= new IFile[count]; + for (int i= 0; i < count; i++) + files[i]= inputFiles[i]; + if (count > 1) + internalSort(files, 0, count-1); + + + String prefix= root.getFullPath().toString(); + + if (fMemberTable != null && !fMemberTable.isDisposed()) { + for (int i = 0; i < files.length; i++) { + IFile file = files[i]; + String path = file.getFullPath().toString(); + + // ignore a recently deleted file at the same path as the + // container + if (path.equals(prefix)) + continue; + + if (path.startsWith(prefix)) + path = path.substring(prefix.length() + 1); + TableItem ti = new TableItem(fMemberTable, SWT.NONE); + ti.setImage(CompareUI.getImage(file)); + ti.setText(path); + ti.setData(new FileHistory(file)); + } + } + + open(); + + return (getReturnCode() == OK) && (fArrayList.size() > 0); + } + + HistoryInput[] getSelected() { + HistoryInput[] selected= new HistoryInput[fArrayList.size()]; + Iterator iter= fArrayList.iterator(); + for (int i= 0; iter.hasNext(); i++) { + FileHistory h= (FileHistory) iter.next(); + selected[i]= h.getHistoryInput(); + } + return selected; + } + + protected synchronized Control createDialogArea(Composite parent2) { + + Composite parent= (Composite) super.createDialogArea(parent2); + + getShell().setText(Utilities.getString(fBundle, "title")); //$NON-NLS-1$ + + org.eclipse.compare.Splitter vsplitter= new org.eclipse.compare.Splitter(parent, SWT.VERTICAL); + vsplitter.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL + | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); + + vsplitter.addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fDateImage != null) + fDateImage.dispose(); + if (fTimeImage != null) + fTimeImage.dispose(); + } + } + ); + + // we need two panes: the left for the elements, the right one for the editions + Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); + + Composite c= new Composite(hsplitter, SWT.NONE); + GridLayout layout= new GridLayout(); + layout.marginWidth= 0; + layout.marginHeight= 2; + layout.verticalSpacing= 2; + layout.numColumns= 1; + c.setLayout(layout); + Label l1= new Label(c, SWT.NONE); + l1.setText(Utilities.getString(fBundle, "memberDescription")); //$NON-NLS-1$ + fMemberPane= new CompareViewerPane(c, SWT.BORDER | SWT.FLAT); + GridData gd= new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); + fMemberPane.setLayoutData(gd); + + fMemberTable= new Table(fMemberPane, SWT.CHECK | SWT.H_SCROLL | SWT.V_SCROLL); + fMemberTable.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (e.detail == SWT.CHECK) { + if (e.item instanceof TableItem) { + TableItem ti= (TableItem) e.item; + if (ti.getChecked()) + fArrayList.add(ti.getData()); + else + fArrayList.remove(ti.getData()); + + if (fCommitButton != null) + fCommitButton.setEnabled(fArrayList.size() > 0); + } + } else { + handleMemberSelect(e.item); + } + } + } + ); + + fMemberPane.setContent(fMemberTable); + + c= new Composite(hsplitter, SWT.NONE); + layout= new GridLayout(); + layout.marginWidth= 0; + layout.marginHeight= 2; + layout.verticalSpacing= 2; + layout.numColumns= 1; + c.setLayout(layout); + Label l2= new Label(c, SWT.NONE); + l2.setText(Utilities.getString(fBundle, "editionDescription")); //$NON-NLS-1$ + fEditionPane= new CompareViewerPane(c, SWT.BORDER | SWT.FLAT); + gd= new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); + fEditionPane.setLayoutData(gd); + + fEditionTree= new Tree(fEditionPane, SWT.H_SCROLL | SWT.V_SCROLL); + fEditionTree.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + feedContent(e.item); + } + } + ); + fEditionPane.setContent(fEditionTree); + + applyDialogFont(parent); // to avoid applying font to compare viewer + fContentPane= new CompareViewerSwitchingPane(vsplitter, SWT.BORDER | SWT.FLAT) { + protected Viewer getViewer(Viewer oldViewer, Object input) { + return CompareUI.findContentViewer(oldViewer, input, this, fCompareConfiguration); + } + }; + vsplitter.setWeights(new int[] { 30, 70 }); + + return parent; + } + + /* + * Feeds selection from member viewer to edition viewer. + */ + private void handleMemberSelect(Widget w) { + Object data= null; + if (w != null) + data= w.getData(); + if (data instanceof FileHistory) { + + FileHistory h= (FileHistory) data; + fCurrentFileHistory= h; + + IFile file= h.getFile(); + IFileState[] states= h.getStates(); + + fEditionPane.setImage(CompareUI.getImage(file)); + String pattern= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ + String title= MessageFormat.format(pattern, new Object[] { file.getName() }); + fEditionPane.setText(title); + + if (fEditionTree != null) { + fEditionTree.setRedraw(false); + fEditionTree.removeAll(); + for (int i= 0; i < states.length; i++) { + addEdition(new HistoryInput(file, states[i]), h.isSelected(i)); + } + fEditionTree.setRedraw(true); + } + } else + fCurrentFileHistory= null; + } + + /* + * Adds the given Pair to the edition tree. + * It takes care of creating tree nodes for different dates. + */ + private void addEdition(HistoryInput input, boolean isSelected) { + if (fEditionTree == null || fEditionTree.isDisposed()) + return; + + IFileState state= input.fFileState; + + // find last day + TreeItem[] days= fEditionTree.getItems(); + TreeItem lastDay= null; + if (days.length > 0) + lastDay= days[days.length-1]; + + long ldate= state.getModificationTime(); + long day= dayNumber(ldate); + Date date= new Date(ldate); + if (lastDay == null || day != dayNumber(((Date)lastDay.getData()).getTime())) { + lastDay= new TreeItem(fEditionTree, SWT.NONE); + lastDay.setImage(fDateImage); + String df= DateFormat.getDateInstance().format(date); + long today= dayNumber(System.currentTimeMillis()); + + String formatKey; + if (day == today) + formatKey= "todayFormat"; //$NON-NLS-1$ + else if (day == today-1) + formatKey= "yesterdayFormat"; //$NON-NLS-1$ + else + formatKey= "dayFormat"; //$NON-NLS-1$ + String pattern= Utilities.getString(fBundle, formatKey); + if (pattern != null) + df= MessageFormat.format(pattern, new String[] { df }); + lastDay.setText(df); + lastDay.setData(date); + } + TreeItem ti= new TreeItem(lastDay, SWT.NONE); + ti.setImage(fTimeImage); + ti.setText(DateFormat.getTimeInstance().format(date)); + ti.setData(input); + + if (isSelected) { + lastDay.setExpanded(true); + fEditionTree.setSelection(new TreeItem[] { ti }); + feedContent(ti); + } + } + + /* + * Returns the number of s since Jan 1st, 1970. + * The given date is converted to GMT and daylight saving is taken into account too. + */ + private long dayNumber(long date) { + int ONE_DAY_MS= 24*60*60 * 1000; // one day in milli seconds + + Calendar calendar= Calendar.getInstance(); + long localTimeOffset= calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + + return (date + localTimeOffset) / ONE_DAY_MS; + } + + /* + * Feeds the tree viewer's selection to the contentviewer + */ + private void feedContent(Widget w) { + if (fContentPane != null && !fContentPane.isDisposed()) { + Object o= w.getData(); + if (o instanceof HistoryInput) { + HistoryInput selected= (HistoryInput) o; + fContentPane.setInput(selected); + fContentPane.setText(getEditionLabel(selected)); + fContentPane.setImage(fTimeImage); + + if (fCurrentFileHistory != null) + fCurrentFileHistory.setSelected(selected.fFileState); + } else { + fContentPane.setInput(null); + } + } + } + + protected String getEditionLabel(HistoryInput input) { + String format= Utilities.getString(fBundle, "historyEditionLabel", null); //$NON-NLS-1$ + if (format == null) + format= Utilities.getString(fBundle, "editionLabel"); //$NON-NLS-1$ + if (format == null) + format= "x{0}"; //$NON-NLS-1$ + + long modDate= input.getModificationDate(); + String date= DateFormat.getDateTimeInstance().format(new Date(modDate)); + + return MessageFormat.format(format, new Object[] { date }); + } + + /* (non-Javadoc) + * Method declared on Dialog. + */ + protected void createButtonsForButtonBar(Composite parent) { + String buttonLabel= Utilities.getString(fBundle, "buttonLabel", IDialogConstants.OK_LABEL); //$NON-NLS-1$ + // a 'Cancel' and a 'Add' button + fCommitButton= createButton(parent, IDialogConstants.OK_ID, buttonLabel, true); + fCommitButton.setEnabled(false); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + } + + /* + * Returns true if the pathname of f1 comes after f2 + */ + private static boolean greaterThan(IFile f1, IFile f2) { + String[] ss1= f1.getFullPath().segments(); + String[] ss2= f2.getFullPath().segments(); + int l1= ss1.length; + int l2= ss2.length; + int n= Math.max(l1, l2); + + for (int i= 0; i < n; i++) { + String s1= i < l1 ? ss1[i] : ""; //$NON-NLS-1$ + String s2= i < l2 ? ss2[i] : ""; //$NON-NLS-1$ + int rc= s1.compareToIgnoreCase(s2); + if (rc != 0) + return rc < 0; + } + return false; + } + + private static void internalSort(IFile[] keys, int left, int right) { + + int original_left= left; + int original_right= right; + + IFile mid= keys[(left + right) / 2]; + do { + while (greaterThan(keys[left], mid)) + left++; + + while (greaterThan(mid, keys[right])) + right--; + + if (left <= right) { + IFile tmp= keys[left]; + keys[left]= keys[right]; + keys[right]= tmp; + left++; + right--; + } + } while (left <= right); + + if (original_left < right) + internalSort(keys, original_left, right); + + if (left < original_right) + internalSort(keys, left, original_right); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java new file mode 100644 index 000000000..0998e18d4 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IActionDelegate; + + +public abstract class BaseCompareAction implements IActionDelegate { + + private ISelection fSelection; + + /* (non-Javadoc) + * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction) + */ + final public void run(IAction action) { + run(fSelection); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection) + */ + final public void selectionChanged(IAction action, ISelection selection) { + fSelection= selection; + if (action != null) + action.setEnabled(isEnabled(fSelection)); + } + + protected boolean isEnabled(ISelection selection) { + return false; + } + + abstract protected void run(ISelection selection); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java new file mode 100644 index 000000000..c6d9d2e91 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.PlatformUI; + +import com.ibm.icu.text.MessageFormat; + +/** + * A simple compare viewer for binary files. + * Shows the position of the first non-matching byte. + */ +public class BinaryCompareViewer extends AbstractViewer { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.BinaryCompareViewerResources"; //$NON-NLS-1$ + + private static final int EOF = -1; + private ICompareInput fInput; + private ResourceBundle fBundle; + private boolean fLeftIsLocal; + + private Composite fComposite; + private Label fMessage; + + public BinaryCompareViewer(Composite parent, final CompareConfiguration cc) { + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICompareContextIds.BINARY_COMPARE_VIEW); + + fBundle= ResourceBundle.getBundle(BUNDLE_NAME); + + fComposite= new Composite(parent, SWT.NONE); + RowLayout rowLayout = new RowLayout(); + rowLayout.type = SWT.VERTICAL; + fComposite.setLayout(rowLayout); + + fMessage= new Label(fComposite, SWT.WRAP); + fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, Utilities.getString(fBundle, "title")); //$NON-NLS-1$ + + fLeftIsLocal= Utilities.getBoolean(cc, "LEFT_IS_LOCAL", false); //$NON-NLS-1$ + + if (cc != null && cc.getContainer() instanceof CompareEditorInput) { + Label compareAsTextLabel = new Label(fComposite, SWT.WRAP); + compareAsTextLabel + .setText(Utilities.getString(fBundle, "compareAsText")); //$NON-NLS-1$ + } + } + + public Control getControl() { + return fComposite; + } + + public void setInput(Object input) { + if (fComposite != null && input instanceof ICompareInput) { + fInput= (ICompareInput) input; + + InputStream left= null; + InputStream right= null; + + String message= null; + try { + left= getStream(fInput.getLeft()); + right= getStream(fInput.getRight()); + + if (left != null && right != null) { + int pos= 0; + while (true) { + int l= left.read(); + int r= right.read(); + if (l != r) { + String format= Utilities.getString(fBundle, "diffMessageFormat"); //$NON-NLS-1$ + message= MessageFormat.format(format, new String[] { Integer.toString(pos) } ); + break; + } + if (l == EOF) + break; + pos++; + } + } else if (left == null && right == null) { + message= Utilities.getString(fBundle, "deleteConflictMessage"); //$NON-NLS-1$ + } else if (left == null) { + if (fLeftIsLocal) + message= Utilities.getString(fBundle, "deletedMessage"); //$NON-NLS-1$ + else + message= Utilities.getString(fBundle, "addedMessage"); //$NON-NLS-1$ + } else if (right == null) { + if (fLeftIsLocal) + message= Utilities.getString(fBundle, "addedMessage"); //$NON-NLS-1$ + else + message= Utilities.getString(fBundle, "deletedMessage"); //$NON-NLS-1$ + } + } catch (CoreException ex) { + message = Utilities.getString(fBundle, "errorMessage"); //$NON-NLS-1$ + CompareUIPlugin.log(ex); + } catch (IOException ex) { + message = Utilities.getString(fBundle, "errorMessage"); //$NON-NLS-1$ + CompareUIPlugin.log(ex); + } finally { + Utilities.close(left); + Utilities.close(right); + } + if (message != null) + fMessage.setText(message); + fComposite.layout(); + } + } + + public Object getInput() { + return fInput; + } + + private InputStream getStream(ITypedElement input) throws CoreException { + if (input instanceof IStreamContentAccessor) + return ((IStreamContentAccessor)input).getContents(); + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java new file mode 100644 index 000000000..e66bd88b6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.*; + +/** + * A factory object for the <code>BinaryCompareViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class BinaryCompareViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new BinaryCompareViewer(parent, mp); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties new file mode 100644 index 000000000..225cb447b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties @@ -0,0 +1,23 @@ +############################################################################### +# Copyright (c) 2000, 2009 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 +############################################################################### + +# @(#)BinaryCompareViewerResources.properties +# +# Resource strings for BinaryCompareViewer.java + +title= Binary Compare + +diffMessageFormat= First bytes differ at position {0} +deleteConflictMessage= Delete Conflict +addedMessage= Added Resource +deletedMessage= Removed Resource +errorMessage= Internal Error +compareAsText= Switch to Text Compare using the drop down menu above diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java new file mode 100644 index 000000000..af8af9041 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.events.*; + +/** + * A Canvas which reduces flicker by drawing in an off screen buffer. + */ +public abstract class BufferedCanvas extends Canvas { + + //private static final boolean USE_DOUBLE_BUFFER= !"carbon".equals(SWT.getPlatform()); //$NON-NLS-1$ + private static final boolean USE_DOUBLE_BUFFER= true; + + /** The drawable for double buffering */ + Image fBuffer; + + public BufferedCanvas(Composite parent, int flags) { + super(parent, flags | SWT.NO_BACKGROUND); + + addPaintListener( + new PaintListener() { + public void paintControl(PaintEvent event) { + doubleBufferPaint(event.gc); + } + } + ); + + addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fBuffer != null) { + fBuffer.dispose(); + fBuffer= null; + } + } + } + ); + } + + public void repaint() { + if (!isDisposed()) { + GC gc= new GC(this); + doubleBufferPaint(gc); + gc.dispose(); + } + } + + /* + * Double buffer drawing. + */ + void doubleBufferPaint(GC dest) { + + if (!USE_DOUBLE_BUFFER) { + doPaint(dest); + return; + } + + Point size= getSize(); + + if (size.x <= 1 || size.y <= 1) // we test for <= 1 because on X11 controls have initial size 1,1 + return; + + if (fBuffer != null) { + Rectangle r= fBuffer.getBounds(); + if (r.width != size.x || r.height != size.y) { + fBuffer.dispose(); + fBuffer= null; + } + } + if (fBuffer == null) + fBuffer= new Image(getDisplay(), size.x, size.y); + + GC gc= new GC(fBuffer); + try { + gc.setBackground(getBackground()); + gc.fillRectangle(0, 0, size.x, size.y); + doPaint(gc); + } finally { + gc.dispose(); + } + + dest.drawImage(fBuffer, 0, 0); + } + + abstract public void doPaint(GC gc); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java new file mode 100644 index 000000000..ccf566c21 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.io.*; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.compare.*; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; + +/** + * A buffer for a workspace resource. + */ +public class BufferedResourceNode extends ResourceNode { + + private boolean fDirty= false; + private IFile fDeleteFile; + + /** + * Creates a <code>ResourceNode</code> for the given resource. + * + * @param resource the resource + */ + public BufferedResourceNode(IResource resource) { + super(resource); + } + + /* + * Returns <code>true</code> if buffer contains uncommitted changes. + */ + public boolean isDirty() { + return fDirty; + } + + protected IStructureComparator createChild(IResource child) { + return new BufferedResourceNode(child); + } + + public void setContent(byte[] contents) { + fDirty= true; + super.setContent(contents); + } + + /* + * Commits buffered contents to resource. + */ + public void commit(IProgressMonitor pm) throws CoreException { + if (fDirty) { + + if (fDeleteFile != null) { + fDeleteFile.delete(true, true, pm); + return; + } + + IResource resource= getResource(); + if (resource instanceof IFile) { + + byte[] bytes= getContent(); + ByteArrayInputStream is= new ByteArrayInputStream(bytes); + try { + IFile file= (IFile) resource; + if (file.exists()) + file.setContents(is, false, true, pm); + else + file.create(is, false, pm); + fDirty= false; + } finally { + if (is != null) + try { + is.close(); + } catch(IOException ex) { + // Silently ignored + } + } + } + } + } + + public ITypedElement replace(ITypedElement child, ITypedElement other) { + + if (child == null) { // add resource + // create a node without a resource behind it! + IResource resource= getResource(); + if (resource instanceof IFolder) { + IFolder folder= (IFolder) resource; + IFile file= folder.getFile(other.getName()); + child= new BufferedResourceNode(file); + } + } + + if (other == null) { // delete resource + IResource resource= getResource(); + if (resource instanceof IFolder) { + IFolder folder= (IFolder) resource; + IFile file= folder.getFile(child.getName()); + if (file != null && file.exists()) { + fDeleteFile= file; + fDirty= true; + } + } + return null; + } + + if (other instanceof IStreamContentAccessor && child instanceof IEditableContent) { + IEditableContent dst= (IEditableContent) child; + + try { + InputStream is= ((IStreamContentAccessor)other).getContents(); + byte[] bytes= Utilities.readBytes(is); + if (bytes != null) + dst.setContent(bytes); + } catch (CoreException ex) { + // NeedWork + } + } + return child; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java new file mode 100644 index 000000000..85fd9b48e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.internal; + +import java.util.ResourceBundle; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.compare.CompareConfiguration; + +/** + * Toggles a boolean property of an <code>CompareConfiguration</code>. + */ +public class ChangePropertyAction extends Action implements IPropertyChangeListener, DisposeListener { + + private CompareConfiguration fCompareConfiguration; + private String fPropertyKey; + private ResourceBundle fBundle; + private String fPrefix; + + public static ChangePropertyAction createIgnoreWhiteSpaceAction(ResourceBundle bundle, CompareConfiguration compareConfiguration) { + return new ChangePropertyAction(bundle, compareConfiguration, "action.IgnoreWhiteSpace.", CompareConfiguration.IGNORE_WHITESPACE); //$NON-NLS-1$ + } + public static ChangePropertyAction createShowPseudoConflictsAction(ResourceBundle bundle, CompareConfiguration compareConfiguration) { + return new ChangePropertyAction(bundle, compareConfiguration, "action.ShowPseudoConflicts.", CompareConfiguration.SHOW_PSEUDO_CONFLICTS); //$NON-NLS-1$ + } + + public ChangePropertyAction(ResourceBundle bundle, CompareConfiguration cc, String rkey, String pkey) { + fPropertyKey= pkey; + fBundle= bundle; + fPrefix= rkey; + Utilities.initAction(this, fBundle, fPrefix); + setCompareConfiguration(cc); + } + + public void run() { + boolean b= !Utilities.getBoolean(fCompareConfiguration, fPropertyKey, false); + setChecked(b); + if (fCompareConfiguration != null) + fCompareConfiguration.setProperty(fPropertyKey, new Boolean(b)); + } + + public void setChecked(boolean state) { + super.setChecked(state); + Utilities.initToggleAction(this, fBundle, fPrefix, state); + } + + public void setCompareConfiguration(CompareConfiguration cc) { + if (fCompareConfiguration != null) + fCompareConfiguration.removePropertyChangeListener(this); + fCompareConfiguration= cc; + if (fCompareConfiguration != null) + fCompareConfiguration.addPropertyChangeListener(this); + setChecked(Utilities.getBoolean(fCompareConfiguration, fPropertyKey, false)); + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(fPropertyKey)) { + setChecked(Utilities.getBoolean(fCompareConfiguration, fPropertyKey, false)); + } + } + + public void dispose(){ + if (fCompareConfiguration != null) + fCompareConfiguration.removePropertyChangeListener(this); + } + + public void widgetDisposed(DisposeEvent e) { + dispose(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java new file mode 100644 index 000000000..0ff9e5444 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959 + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; + + +/* + * The "Compare with each other" action + */ +public class CompareAction extends BaseCompareAction implements IObjectActionDelegate { + + protected ResourceCompareInput fInput; + protected IWorkbenchPage fWorkbenchPage; + protected boolean showSelectAncestorDialog = true; + + public void run(ISelection selection) { + if (fInput != null) { + // Pass the shell so setSelection can prompt the user for which + // resource should be the ancestor + boolean ok = fInput.setSelection(selection, fWorkbenchPage + .getWorkbenchWindow().getShell(), showSelectAncestorDialog); + if (!ok) return; + fInput.initializeCompareConfiguration(); + CompareUI.openCompareEditorOnPage(fInput, fWorkbenchPage); + fInput= null; // don't reuse this input! + } + } + + protected boolean isEnabled(ISelection selection) { + if (fInput == null) { + CompareConfiguration cc= new CompareConfiguration(); + // buffered merge mode: don't ask for confirmation + // when switching between modified resources + cc.setProperty(CompareEditor.CONFIRM_SAVE_PROPERTY, new Boolean(false)); + + // uncomment following line to have separate outline view + //cc.setProperty(CompareConfiguration.USE_OUTLINE_VIEW, new Boolean(true)); + + fInput= new ResourceCompareInput(cc); + } + return fInput.isEnabled(selection); + } + + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + fWorkbenchPage= targetPart.getSite().getPage(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java new file mode 100644 index 000000000..2a106c004 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.internal; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.ICompareContainer; +import org.eclipse.compare.ICompareNavigator; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ui.*; +import org.eclipse.ui.services.IServiceLocator; + +public class CompareContainer implements ICompareContainer { + + private WorkerJob worker; + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) + */ + public void setStatusMessage(String message) { + // Do nothing, by default + } + + /* (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) { + input.addCompareInputChangeListener(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) { + input.removeCompareInputChangeListener(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) { + // Nothing to do + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getServiceLocator() + */ + public IServiceLocator getServiceLocator() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getActionBars() + */ + public IActionBars getActionBars() { + 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 { + PlatformUI.getWorkbench().getProgressService().run(fork, cancelable, runnable); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getNavigator() + */ + public ICompareNavigator getNavigator() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#runAsynchronously(org.eclipse.jface.operation.IRunnableWithProgress) + */ + public synchronized void runAsynchronously(IRunnableWithProgress runnable) { + if (worker == null) + worker = createWorkerJob(); + worker.add(runnable); + } + + protected WorkerJob createWorkerJob() { + WorkerJob workerJob = new WorkerJob(getWorkerJobName()); + return workerJob; + } + + protected String getWorkerJobName() { + return CompareMessages.CompareContainer_0; + } + + public IWorkbenchPart getWorkbenchPart() { + return null; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java new file mode 100644 index 000000000..d92ad78ba --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * Copyright (c) 2009, 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.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareViewerSwitchingPane; +import org.eclipse.compare.Splitter; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.MenuAdapter; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.PlatformUI; + +public class CompareContentViewerSwitchingPane extends + CompareViewerSwitchingPane { + + private static final String OPTIMIZED_INFO_IMAGE_NAME = "obj16/message_info.gif"; //$NON-NLS-1$ + public static final String OPTIMIZED_ALGORITHM_USED = "OPTIMIZED_ALGORITHM_USED"; //$NON-NLS-1$ + + private CompareEditorInput fCompareEditorInput; + + private ViewerDescriptor fSelectedViewerDescriptor; + + private ToolBar toolBar; + private CLabel clOptimized; + + private boolean menuShowing; + + public CompareContentViewerSwitchingPane(Splitter parent, int style, + CompareEditorInput cei) { + super(parent, style); + fCompareEditorInput = cei; + } + + private CompareConfiguration getCompareConfiguration() { + return fCompareEditorInput.getCompareConfiguration(); + } + + protected Viewer getViewer(Viewer oldViewer, Object input) { + if (fSelectedViewerDescriptor != null) { + ViewerDescriptor[] array = CompareUIPlugin.getDefault().findContentViewerDescriptor( + oldViewer, input, getCompareConfiguration()); + List list = array != null ? Arrays.asList(array) + : Collections.EMPTY_LIST; + if (list.contains(fSelectedViewerDescriptor)) { + // use selected viewer only when appropriate for the new input + fCompareEditorInput + .setContentViewerDescriptor(fSelectedViewerDescriptor); + Viewer viewer = fCompareEditorInput.findContentViewer( + oldViewer, (ICompareInput) input, this); + return viewer; + } + // fallback to default otherwise + fSelectedViewerDescriptor = null; + } + if (input instanceof ICompareInput) { + fCompareEditorInput.setContentViewerDescriptor(null); + Viewer viewer = fCompareEditorInput.findContentViewer(oldViewer, + (ICompareInput) input, this); + fCompareEditorInput.setContentViewerDescriptor(fSelectedViewerDescriptor); + return viewer; + } + return null; + } + + protected Control createTopLeft(Composite p) { + final Composite composite = new Composite(p, SWT.NONE) { + public Point computeSize(int wHint, int hHint, boolean changed) { + return super.computeSize(wHint, Math.max(24, hHint), changed); + } + }; + + RowLayout layout = new RowLayout(); + layout.marginTop = 0; + composite.setLayout(layout); + + CLabel cl = new CLabel(composite, SWT.NONE); + cl.setText(null); + + toolBar = new ToolBar(composite, SWT.FLAT); + toolBar.setVisible(false); // hide by default + final ToolItem toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + toolItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage( + /* IWorkbenchGraphicConstants */"IMG_LCL_VIEW_MENU")); //$NON-NLS-1$ + toolItem + .setToolTipText(CompareMessages.CompareContentViewerSwitchingPane_switchButtonTooltip); + toolItem.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + showMenu(); + } + }); + toolBar.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent e) { + showMenu(); + } + }); + + clOptimized = new CLabel(composite, SWT.NONE); + clOptimized + .setText(CompareMessages.CompareContentViewerSwitchingPane_optimized); + clOptimized + .setToolTipText(CompareMessages.CompareContentViewerSwitchingPane_optimizedTooltip); + clOptimized.setImage(CompareUIPlugin.getImageDescriptor( + OPTIMIZED_INFO_IMAGE_NAME).createImage()); + clOptimized.setVisible(false); // hide by default + clOptimized.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + Image img = clOptimized.getImage(); + if ((img != null) && (!img.isDisposed())) { + img.dispose(); + } + } + }); + + return composite; + } + + protected boolean inputChanged(Object input) { + return getInput() != input + || fCompareEditorInput.getContentViewerDescriptor() != fSelectedViewerDescriptor; + } + + public void setInput(Object input) { + super.setInput(input); + if (getViewer() == null || !Utilities.okToUse(getViewer().getControl())) + return; + ViewerDescriptor[] vd = CompareUIPlugin.getDefault() + .findContentViewerDescriptor(getViewer(), getInput(), + getCompareConfiguration()); + toolBar.setVisible(vd != null && vd.length > 1); + CompareConfiguration cc = getCompareConfiguration(); + Boolean isOptimized = (Boolean) cc.getProperty(OPTIMIZED_ALGORITHM_USED); + clOptimized.setVisible(isOptimized != null && isOptimized.booleanValue()); + } + + private void showMenu() { + if (menuShowing) + return; + menuShowing= true; + + ViewerDescriptor[] vd = CompareUIPlugin.getDefault() + .findContentViewerDescriptor(getViewer(), getInput(), + getCompareConfiguration()); + + // 1. create + final Menu menu = new Menu(getShell(), SWT.POP_UP); + + // add default + String label = CompareMessages.CompareContentViewerSwitchingPane_defaultViewer; + MenuItem defaultItem = new MenuItem(menu, SWT.RADIO); + defaultItem.setText(label); + defaultItem.addSelectionListener(createSelectionListener(null)); + defaultItem.setSelection(fSelectedViewerDescriptor == null); + + new MenuItem(menu, SWT.SEPARATOR); + + // add others + for (int i = 0; i < vd.length; i++) { + final ViewerDescriptor vdi = vd[i]; + label = vdi.getLabel(); + if (label == null || label.equals("")) { //$NON-NLS-1$ + String l = CompareUIPlugin.getDefault().findContentTypeNameOrType((ICompareInput) getInput(), vdi, getCompareConfiguration()); + if (l == null) + // couldn't figure out the label, skip the viewer + continue; + label = NLS.bind(CompareMessages.CompareContentViewerSwitchingPane_discoveredLabel, new Object[] {l}); + } + MenuItem item = new MenuItem(menu, SWT.RADIO); + item.setText(label); + item.addSelectionListener(createSelectionListener(vdi)); + item.setSelection(vdi == fSelectedViewerDescriptor); + } + + // 2. show + Rectangle bounds = toolBar.getItem(0).getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + + // 3. dispose on close + menu.addMenuListener(new MenuAdapter() { + public void menuHidden(MenuEvent e) { + menuShowing= false; + e.display.asyncExec(new Runnable() { + public void run() { + menu.dispose(); + } + }); + } + }); + } + + private SelectionListener createSelectionListener(final ViewerDescriptor vd) { + return new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + MenuItem mi = (MenuItem) e.widget; + if (mi.getSelection()) { + Viewer oldViewer = getViewer(); + fSelectedViewerDescriptor = vd; + CompareContentViewerSwitchingPane.this.setInput(oldViewer + .getInput()); + } + } + + public void widgetDefaultSelected(SelectionEvent e) { + // nothing to do + } + }; + } + + public void setText(String label) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) { + cl.setText(label); + c.layout(); + } + return; + } + } + } + + public void setImage(Image image) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) + cl.setImage(image); + return; + } + } + } + + public void addMouseListener(MouseListener listener) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + cl.addMouseListener(listener); + } + } + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java new file mode 100644 index 000000000..7a7fcd0ac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java @@ -0,0 +1,286 @@ +/******************************************************************************* + * 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.internal; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; + +/** + * This is a dialog that can host a {@link CompareEditorInput}. + * <p> + * This class can be used as is or can be subclassed. + * + * @since 3.3 + */ +public class CompareDialog extends TrayDialog implements IPropertyChangeListener { + + private final CompareEditorInput fCompareEditorInput; + private Button fCommitButton; + private Label statusLabel; + boolean hasSettings = true; + private final DialogCompareContainer fContainer = new DialogCompareContainer(); + + private class DialogCompareContainer extends CompareContainer { + + /* (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 { + ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell()); + dialog.run(fork, cancelable, runnable); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) + */ + public void setStatusMessage(String message) { + if (statusLabel != null && !statusLabel.isDisposed()) { + if (message == null) { + statusLabel.setText(""); //$NON-NLS-1$ + } else { + statusLabel.setText(message); + } + } + } + } + + /** + * Create a dialog to host the given input. + * @param shell a shell + * @param input the dialog input + */ + public CompareDialog(Shell shell, CompareEditorInput input) { + super(shell); + setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX); + Assert.isNotNull(input); + fCompareEditorInput= input; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.ResizableDialog#close() + */ + public boolean close() { + if (super.close()) { + if (fCompareEditorInput != null) + fCompareEditorInput.removePropertyChangeListener(this); + return true; + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite) + */ + protected void createButtonsForButtonBar(Composite parent) { + fCommitButton= createButton(parent, IDialogConstants.OK_ID, getOKButtonLabel(), true); + fCommitButton.setEnabled(isOKEnabled()); + if (isCancelable()) { + createButton(parent, IDialogConstants.CANCEL_ID, getCancelButtonLabel(), false); + } + } + + private String getCancelButtonLabel() { + return fCompareEditorInput.getCancelButtonLabel(); + } + + private boolean isCancelable() { + return isInputEditable() || isElementSelectionDialog(); + } + + private String getOKButtonLabel() { + return fCompareEditorInput.getOKButtonLabel(); + } + + private boolean isElementSelectionDialog() { + return fCompareEditorInput.isEditionSelectionDialog(); + } + + /** + * Return whether the compare editor input of this dialog is editable. + * By default, the input is editable if the compare configuration + * indicates that either the left or right sides are editable. + * Subclasses may override. + * @return whether the compare editor input of this dialog is editable + * @see CompareConfiguration#isLeftEditable() + * @see CompareConfiguration#isRightEditable() + */ + protected boolean isInputEditable() { + return fCompareEditorInput.getCompareConfiguration().isLeftEditable() + || fCompareEditorInput.getCompareConfiguration().isRightEditable(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(CompareEditorInput.DIRTY_STATE) + || event.getProperty().equals(CompareEditorInput.PROP_SELECTED_EDITION)) { + if (fCommitButton != null && fCompareEditorInput != null) + fCommitButton.setEnabled(isOKEnabled()); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE)) { + getShell().setText(fCompareEditorInput.getTitle()); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE_IMAGE)) { + getShell().setImage(fCompareEditorInput.getTitleImage()); + } + } + + private boolean isOKEnabled() { + if (isInputEditable()) + return fCompareEditorInput.isDirty(); + if (isElementSelectionDialog()) + return getSelectedElement() != null; + return true; + } + + private Object getSelectedElement() { + return fCompareEditorInput.getSelectedEdition(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + protected Control createDialogArea(Composite parent2) { + + Composite parent= (Composite) super.createDialogArea(parent2); + + Control c= fCompareEditorInput.createContents(parent); + c.setLayoutData(new GridData(GridData.FILL_BOTH)); + + statusLabel = new Label(parent, SWT.NONE); + statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + Shell shell= c.getShell(); + shell.setText(fCompareEditorInput.getTitle()); + shell.setImage(fCompareEditorInput.getTitleImage()); + applyDialogFont(parent); + return parent; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.window.Window#open() + */ + public int open() { + // Before opening, set the container of the input and listen + // for changes to the input + fCompareEditorInput.addPropertyChangeListener(this); + fCompareEditorInput.setContainer(fContainer); + return super.open(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int) + */ + protected void buttonPressed(int buttonId) { + if (buttonId == OK) { + if (!fCompareEditorInput.okPressed()) + return; + } else if (buttonId == CANCEL) { + fCompareEditorInput.cancelPressed(); + } + super.buttonPressed(buttonId); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings() + */ + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings compareSettings = CompareUIPlugin.getDefault().getDialogSettings(); + String sectionName = this.getClass().getName(); + IDialogSettings dialogSettings = compareSettings.getSection(sectionName); + if (dialogSettings == null) { + hasSettings = false; + dialogSettings = compareSettings.addNewSection(sectionName); + } + return dialogSettings; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.ResizableDialog#configureShell(org.eclipse.swt.widgets.Shell) + */ + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + if (getHelpContextId() != null) + PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, getHelpContextId()); + } + + /** + * Return the help content id for this dialog or <code>null</code>. + * By default, a generic help content id is returned. Subclasses may + * override. + * @return the help content id for this dialog or <code>null</code> + */ + public String getHelpContextId() { + return ICompareContextIds.COMPARE_DIALOG; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#getInitialSize() + */ + protected Point getInitialSize() { + Point initialSize = super.getInitialSize(); + if (hasSettings) { + return initialSize; + } + return getDefaultSize(); + } + + /** + * If we don't have settings we need to come up with a reasonable default + * since we can't depend on the compare editor input layout returning a + * good default size. + * @return the default size of the dialog + */ + protected Point getDefaultSize() { + int width= 0; + int height= 0; + Shell shell= getParentShell(); + if (shell != null) { + Point parentSize= shell.getSize(); + width= parentSize.x-100; + height= parentSize.y-100; + } + if (width < 700) + width= 700; + if (height < 500) + height= 500; + return new Point(width, height); + } + + /** + * Return the compare editor input for this dialog. + * @return the compare editor input for this dialog + */ + protected final CompareEditorInput getInput() { + return fCompareEditorInput; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java new file mode 100644 index 000000000..f3fbc5261 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java @@ -0,0 +1,771 @@ +/******************************************************************************* + * 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.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashSet; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.IPropertyChangeNotifier; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.IStatusLineManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorActionBarContributor; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IReusableEditor; +import org.eclipse.ui.ISaveablesLifecycleListener; +import org.eclipse.ui.ISaveablesSource; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartConstants; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.Saveable; +import org.eclipse.ui.SaveablesLifecycleEvent; +import org.eclipse.ui.actions.WorkspaceModifyOperation; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.part.EditorPart; +import org.eclipse.ui.part.IShowInSource; +import org.eclipse.ui.part.PageBook; +import org.eclipse.ui.services.IServiceLocator; +import org.eclipse.ui.texteditor.ITextEditorExtension3; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + * A CompareEditor takes a ICompareEditorInput as input. + * Most functionality is delegated to the ICompareEditorInput. + */ +public class CompareEditor extends EditorPart implements IReusableEditor, ISaveablesSource, IPropertyChangeListener, ISaveablesLifecycleListener { + + public final static String CONFIRM_SAVE_PROPERTY= "org.eclipse.compare.internal.CONFIRM_SAVE_PROPERTY"; //$NON-NLS-1$ + + private static final int UNINITIALIZED = 0; + private static final int INITIALIZING = 1; + private static final int NO_DIFF = 2; + private static final int CANCELED = 3; + private static final int INITIALIZED = 4; + private static final int ERROR = 5; + private static final int STILL_INITIALIZING = 6; + private static final int CREATING_CONTROL = 7; + private static final int DONE = 8; + + private IActionBars fActionBars; + + private PageBook fPageBook; + + /** the SWT control from the compare editor input*/ + private Control fControl; + /** the outline page */ + private CompareOutlinePage fOutlinePage; + + private CompareSaveable fSaveable; + + private Control initializingPage; + private Control emptyPage; + + private int state = UNINITIALIZED; + private HashSet knownSaveables; + + private final EditorCompareContainer fContainer = new EditorCompareContainer(); + + private class EditorCompareContainer extends CompareContainer { + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider) + */ + public void registerContextMenu(MenuManager menu, ISelectionProvider provider) { + if (getSite() instanceof IEditorSite) { + IEditorSite es = (IEditorSite) getSite(); + es.registerContextMenu(menu, provider, true); + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) + */ + public void setStatusMessage(String message) { + if (fActionBars != null) { + IStatusLineManager slm= fActionBars.getStatusLineManager(); + if (slm != null) { + slm.setMessage(message); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getServiceLocator() + */ + public IServiceLocator getServiceLocator() { + return getSite(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#createWorkerJob() + */ + protected WorkerJob createWorkerJob() { + WorkerJob workerJob = new WorkerJob(getWorkerJobName()) { + public boolean belongsTo(Object family) { + if (family == CompareEditor.this) + return true; + return super.belongsTo(family); + } + }; + return workerJob; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#getWorkerJobName() + */ + protected String getWorkerJobName() { + return NLS.bind(CompareMessages.CompareEditor_2, getTitle()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#getWorkbenchPart() + */ + public IWorkbenchPart getWorkbenchPart() { + return CompareEditor.this; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#getActionBars() + */ + public IActionBars getActionBars() { + return CompareEditor.this.getActionBars(); + } + } + + /** + * No-argument constructor required for extension points. + */ + public CompareEditor() { + // empty default implementation + } + + /* (non-Javadoc) + * Method declared on IAdaptable + */ + public Object getAdapter(Class key) { + + if (key.equals(IContentOutlinePage.class)) { + Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW); + if (object instanceof Boolean && ((Boolean)object).booleanValue()) { + if (fOutlinePage != null) { + if (fOutlinePage.getControl() != null && fOutlinePage.getControl().isDisposed()) { + fOutlinePage = null; + } else { + return fOutlinePage; + } + } + fOutlinePage= new CompareOutlinePage(this); + return fOutlinePage; + } + } + + if (key == IShowInSource.class + || key == OutlineViewerCreator.class + || key == IFindReplaceTarget.class) { + Object input = getEditorInput(); + if (input != null) { + return Utilities.getAdapter(input, key); + } + } + + if (key == IEditorInput.class) { + return getEditorInput().getAdapter(IEditorInput.class); + } + + if (key == ITextEditorExtension3.class) { + return getEditorInput().getAdapter(ITextEditorExtension3.class); + } + + return super.getAdapter(key); + } + + /* + * Helper method used by ComapreEditorConfiguration to get at the compare configuration of the editor + */ + /* package */ CompareConfiguration getCompareConfiguration() { + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) + return ((CompareEditorInput)input).getCompareConfiguration(); + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput) + */ + public void init(IEditorSite site, IEditorInput input) throws PartInitException { + + if (!(input instanceof CompareEditorInput)) + throw new PartInitException(Utilities.getString("CompareEditor.invalidInput")); //$NON-NLS-1$ + + setSite(site); + setInput(input); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput) + */ + public void setInput(IEditorInput input) { + if (!(input instanceof CompareEditorInput)) { + IStatus s= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, Utilities.getString("CompareEditor.invalidInput"), null); //$NON-NLS-1$ + String title= Utilities.getString("CompareEditor.error.setinput.title"); //$NON-NLS-1$ + String msg= Utilities.getString("CompareEditor.error.setinput.message"); //$NON-NLS-1$ + ErrorDialog.openError(getSite().getShell(), title, msg, s); + return; + } + doSetInput(input); + // Need to refresh the contributor (see #67888) + refreshActionBarsContributor(); + } + + public void refreshActionBarsContributor() { + IEditorSite editorSite= getEditorSite(); + if (editorSite != null) { + IEditorActionBarContributor actionBarContributor= editorSite.getActionBarContributor(); + if (actionBarContributor != null) { + actionBarContributor.setActiveEditor(null); + actionBarContributor.setActiveEditor(this); + } + } + } + + private void doSetInput(IEditorInput input) { + IEditorInput oldInput= getEditorInput(); + disconnectFromInput(oldInput); + Point oldSize = null; + boolean hadPreviousInput = oldInput != null; + if (hadPreviousInput) { + // Cancel any jobs associated with the old input + Job.getJobManager().cancel(this); + if (fControl != null && !fControl.isDisposed()) { + oldSize= fControl.getSize(); + if (emptyPage == null) + emptyPage = new Composite(fPageBook, SWT.NONE); + fPageBook.showPage(emptyPage); + fControl.dispose(); + fControl = null; + } + } + + super.setInput(input); + + if (fOutlinePage != null) + fOutlinePage.reset(); + + final CompareEditorInput cei= (CompareEditorInput) input; + cei.setContainer(fContainer); + setTitleImage(cei.getTitleImage()); + setPartName(cei.getTitle()); + setTitleToolTip(cei.getToolTipText()); + + if (input instanceof IPropertyChangeNotifier) + ((IPropertyChangeNotifier)input).addPropertyChangeListener(this); + + setState(cei.getCompareResult() == null ? INITIALIZING : INITIALIZED); + if (fPageBook != null) + createCompareControl(); + if (fControl != null && oldSize != null) + fControl.setSize(oldSize); + + boolean hasResult = cei.getCompareResult() != null; + if (!hasResult) { + initializeInBackground(cei, hadPreviousInput); + } + + firePropertyChange(IWorkbenchPartConstants.PROP_INPUT); + + // We only need to notify of new Saveables if we are changing inputs + if (hadPreviousInput && hasResult) { + registerSaveable(); + } + } + + private void registerSaveable() { + ISaveablesLifecycleListener lifecycleListener= (ISaveablesLifecycleListener) getSite().getService(ISaveablesLifecycleListener.class); + lifecycleListener.handleLifecycleEvent( + new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_OPEN, internalGetSaveables(true), false)); + } + + private void disconnectFromInput(IEditorInput oldInput) { + if (oldInput != null) { + + if (oldInput instanceof IPropertyChangeNotifier) + ((IPropertyChangeNotifier)oldInput).removePropertyChangeListener(this); + + // Let the workbench know that the old input's saveables are no longer needed + if (knownSaveables != null && !knownSaveables.isEmpty()) { + ISaveablesLifecycleListener lifecycleListener= (ISaveablesLifecycleListener) getSite().getService(ISaveablesLifecycleListener.class); + lifecycleListener.handleLifecycleEvent( + new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_CLOSE, (Saveable[]) knownSaveables.toArray(new Saveable[knownSaveables.size()]), false)); + knownSaveables.clear(); + } + } + } + + protected void initializeInBackground(final CompareEditorInput cei, final boolean hadPreviousInput) { + // Need to cancel any running jobs associated with the oldInput + Job job = new Job(NLS.bind(CompareMessages.CompareEditor_0, cei.getTitle())) { + protected IStatus run(final IProgressMonitor monitor) { + final int[] newState = new int[] { ERROR }; + try { + IStatus status = CompareUIPlugin.getDefault().prepareInput(cei, monitor); + if (status.isOK()) { + // We need to update the saveables list + newState[0] = INITIALIZED; + return Status.OK_STATUS; + } + if (status.getCode() == CompareUIPlugin.NO_DIFFERENCE) { + newState[0] = NO_DIFF; + return Status.OK_STATUS; + } + newState[0] = ERROR; + return status; + } catch (OperationCanceledException e) { + newState[0] = CANCELED; + return Status.CANCEL_STATUS; + } finally { + if (monitor.isCanceled()) + newState[0] = CANCELED; + Display.getDefault().syncExec(new Runnable() { + public void run() { + if (fPageBook.isDisposed()) + return; + // we need to register the saveable if we had a previous input or if + // there are knownSaveables (which means that the workbench called + // getSaveables and got an empty list + if (hadPreviousInput || (knownSaveables != null && !isAllSaveablesKnown())) { + registerSaveable(); + } + setState(newState[0]); + createCompareControl(); + } + }); + monitor.done(); + } + } + public boolean belongsTo(Object family) { + if (family == CompareEditor.this || family == cei) + return true; + return cei.belongsTo(family); + } + }; + job.setUser(true); + Utilities.schedule(job, getSite()); + } + + /* + * Helper method used to find an action bars using the Utilities#findActionsBars(Control) + */ + public IActionBars getActionBars() { + return fActionBars; + } + + /* + * Set the action bars so the Utilities class can access it. + */ + /* package */ void setActionBars(IActionBars actionBars) { + fActionBars= actionBars; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) + */ + public void createPartControl(Composite parent) { + parent.setData(this); + fPageBook = new PageBook(parent, SWT.NONE); + createCompareControl(); + IContextService service = (IContextService)getSite().getService(IContextService.class); + if (service != null) { + service.activateContext("org.eclipse.compare.compareEditorScope"); //$NON-NLS-1$ + service.activateContext("org.eclipse.ui.textEditorScope"); //$NON-NLS-1$ + } + } + + private void createCompareControl() { + if (fPageBook.isDisposed()) + return; + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) { + CompareEditorInput ci = (CompareEditorInput) input; + if (ci.getCompareResult() == null) { + if (getState() == INITIALIZING) { + getSite().setSelectionProvider(new CompareEditorSelectionProvider()); + setPageLater(); + } else if (getState() == STILL_INITIALIZING) { + if (initializingPage == null) { + initializingPage = getInitializingMessagePane(fPageBook); + } + fPageBook.showPage(initializingPage); + } else if (getState() == CANCELED) { + // Close the editor when we are canceled + closeEditor(); + } else if (getState() == NO_DIFF) { + // Prompt and close the editor as well + setState(DONE); + closeEditor(); + CompareUIPlugin.getDefault().handleNoDifference(); + } else if (getState() == ERROR) { + // If an error occurred, close the editor + // (the message would be displayed by the progress view) + closeEditor(); + } + } else if (fControl == null && getState() != CREATING_CONTROL) { + if (getState() == CANCELED) { + // Close the editor when we are canceled, even when compare + // result has been already prepared + closeEditor(); + return; + } + // Set the state in case this method gets called again + setState(CREATING_CONTROL); + if (getSite().getSelectionProvider() == null) + getSite().setSelectionProvider(new CompareEditorSelectionProvider()); + try { + fControl = ci.createContents(fPageBook); + } catch (SWTException e) { + // closed while creating + if (e.code == SWT.ERROR_WIDGET_DISPOSED) { + setState(CANCELED); + return; + } + } + fPageBook.showPage(fControl); + PlatformUI.getWorkbench().getHelpSystem().setHelp(fControl, ICompareContextIds.COMPARE_EDITOR); + if (isActive()) { + setFocus(); + } + setState(INITIALIZED); + } + } + } + + private boolean isActive() { + return getSite().getPage().getActivePart() == this; + } + + private void setPageLater() { + Display.getCurrent().timerExec(1000, new Runnable() { + public void run() { + synchronized(CompareEditor.this) { + if (getState() == INITIALIZING) { + setState(STILL_INITIALIZING); + createCompareControl(); + } + } + } + }); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#dispose() + */ + public void dispose() { + IEditorInput input= getEditorInput(); + if (input instanceof IPropertyChangeNotifier) + ((IPropertyChangeNotifier)input).removePropertyChangeListener(this); + super.dispose(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#setFocus() + */ + public void setFocus() { + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) + if (!((CompareEditorInput)input).setFocus2()) + fPageBook.setFocus(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed() + */ + public boolean isSaveAsAllowed() { + return false; + } + + /* (non-Javadoc) + * Always throws an AssertionFailedException. + * @see org.eclipse.ui.part.EditorPart#doSaveAs() + */ + public void doSaveAs() { + Assert.isTrue(false); // Save As not supported for CompareEditor + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor) + */ + public void doSave(IProgressMonitor progressMonitor) { + + final IEditorInput input= getEditorInput(); + + WorkspaceModifyOperation operation= new WorkspaceModifyOperation() { + public void execute(IProgressMonitor pm) throws CoreException { + if (input instanceof CompareEditorInput) + ((CompareEditorInput)input).saveChanges(pm); + } + }; + + Shell shell= getSite().getShell(); + + try { + + operation.run(progressMonitor); + + firePropertyChange(PROP_DIRTY); + + } catch (InterruptedException x) { + // NeedWork + } catch (OperationCanceledException x) { + // NeedWork + } catch (InvocationTargetException x) { + String title= Utilities.getString("CompareEditor.saveError.title"); //$NON-NLS-1$ + String reason= x.getTargetException().getMessage(); + MessageDialog.openError(shell, title, Utilities.getFormattedString("CompareEditor.cantSaveError", reason)); //$NON-NLS-1$ + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#isDirty() + */ + public boolean isDirty() { + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) + return ((CompareEditorInput)input).isDirty(); + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(CompareEditorInput.DIRTY_STATE)) { + Object old_value= event.getOldValue(); + Object new_value= event.getNewValue(); + if (old_value == null || new_value == null || !old_value.equals(new_value)) + firePropertyChange(PROP_DIRTY); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE)) { + setPartName(((CompareEditorInput)getEditorInput()).getTitle()); + setTitleToolTip(((CompareEditorInput)getEditorInput()).getToolTipText()); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE_IMAGE)) { + setTitleImage(((CompareEditorInput)getEditorInput()).getTitleImage()); + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.ISaveablesSource#getModels() + */ + public Saveable[] getSaveables() { + return internalGetSaveables(knownSaveables == null); + } + + private Saveable[] internalGetSaveables(boolean init) { + IEditorInput input= getEditorInput(); + Saveable[] sourceSaveables = getSaveables(input); + if (init || knownSaveables == null) { + recordSaveables(sourceSaveables); + } else { + for (int i = 0; i < sourceSaveables.length; i++) { + Saveable saveable = sourceSaveables[i]; + if (!knownSaveables.contains(saveable)) { + CompareUIPlugin.logErrorMessage(NLS.bind("Saveable {0} was not added using a saveables lifecycle event.", saveable.getName())); //$NON-NLS-1$ + knownSaveables.add(saveable); + } + } + if (sourceSaveables.length != knownSaveables.size()) { + CompareUIPlugin.logErrorMessage("Saveables were removed without an appropriate event"); //$NON-NLS-1$ + knownSaveables.clear(); + recordSaveables(sourceSaveables); + } + } + return sourceSaveables; + } + + private boolean isAllSaveablesKnown() { + IEditorInput input= getEditorInput(); + Saveable[] sourceSaveables = getSaveables(input); + if (knownSaveables == null) { + return sourceSaveables.length == 0; + } + if (sourceSaveables.length != knownSaveables.size()) { + return false; + } + for (int i = 0; i < sourceSaveables.length; i++) { + Saveable saveable = sourceSaveables[i]; + if (!knownSaveables.contains(saveable)) { + return false; + } + } + return true; + } + + private void recordSaveables(Saveable[] sourceSaveables) { + if (knownSaveables == null) + knownSaveables = new HashSet(); + for (int i = 0; i < sourceSaveables.length; i++) { + Saveable saveable = sourceSaveables[i]; + knownSaveables.add(saveable); + } + } + + private Saveable[] getSaveables(IEditorInput input) { + if (input instanceof ISaveablesSource) { + ISaveablesSource source= (ISaveablesSource) input; + return source.getSaveables(); + } + return new Saveable[] { getSaveable() }; + } + + private Saveable getSaveable() { + if (fSaveable == null) { + fSaveable= new CompareSaveable(); + } + return fSaveable; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.ISaveablesSource#getActiveModels() + */ + public Saveable[] getActiveSaveables() { + IEditorInput input= getEditorInput(); + if (input instanceof ISaveablesSource) { + ISaveablesSource source= (ISaveablesSource) input; + return source.getActiveSaveables(); + } + return new Saveable[] { getSaveable() }; + } + + private class CompareSaveable extends Saveable { + + public String getName() { + return CompareEditor.this.getPartName(); + } + + public String getToolTipText() { + return CompareEditor.this.getTitleToolTip(); + } + + public ImageDescriptor getImageDescriptor() { + return ImageDescriptor.createFromImage(CompareEditor.this.getTitleImage()); + } + + public void doSave(IProgressMonitor monitor) throws CoreException { + CompareEditor.this.doSave(monitor); + } + + public boolean isDirty() { + return CompareEditor.this.isDirty(); + } + + public boolean equals(Object object) { + return object == this; + } + + public int hashCode() { + return CompareEditor.this.hashCode(); + } + } + + private Composite getInitializingMessagePane(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setBackground(getBackgroundColor(parent)); + GridLayout layout = new GridLayout(); + layout.numColumns = 3; + composite.setLayout(layout); + + createDescriptionLabel(composite, CompareMessages.CompareEditor_1); + return composite; + } + + private Color getBackgroundColor(Composite parent) { + return parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + } + + private Label createDescriptionLabel(Composite parent, String text) { + Label description = new Label(parent, SWT.WRAP); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalSpan = 2; + description.setLayoutData(data); + description.setText(text); + description.setBackground(getBackgroundColor(parent)); + return description; + } + + private void closeEditor() { + getSite().getPage().closeEditor(CompareEditor.this, false); + } + + private synchronized void setState(int state) { + this.state = state; + } + + private int getState() { + return state; + } + + public void handleLifecycleEvent(SaveablesLifecycleEvent event) { + ISaveablesLifecycleListener lifecycleListener= (ISaveablesLifecycleListener) getSite().getService(ISaveablesLifecycleListener.class); + if (event.getEventType() == SaveablesLifecycleEvent.POST_CLOSE) { + // We may get a post close for a saveable that is not known to the workbench. + // Only pass on the event for known saveables + if (knownSaveables == null || knownSaveables.isEmpty()) + return; + java.util.List result = new ArrayList(); + Saveable[] all = event.getSaveables(); + for (int i = 0; i < all.length; i++) { + Saveable saveable = all[i]; + if (knownSaveables.contains(saveable)) + result.add(saveable); + knownSaveables.remove(saveable); + } + if (result.isEmpty()) + return; + event = new SaveablesLifecycleEvent(this, + SaveablesLifecycleEvent.POST_CLOSE, + (Saveable[]) result.toArray(new Saveable[result.size()]), + false); + } else if (event.getEventType() == SaveablesLifecycleEvent.POST_OPEN) { + recordSaveables(event.getSaveables()); + } + lifecycleListener.handleLifecycleEvent(event); + } + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java new file mode 100644 index 000000000..fc427fe23 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.internal; + +import java.util.ResourceBundle; + +import org.eclipse.jface.action.*; + +import org.eclipse.ui.*; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.help.IWorkbenchHelpSystem; +import org.eclipse.ui.part.EditorActionBarContributor; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; + +import org.eclipse.compare.*; + + +public class CompareEditorContributor extends EditorActionBarContributor { + + private IEditorPart fActiveEditorPart= null; + + private ChangePropertyAction fIgnoreWhitespace; + private NavigationAction fNext; + private NavigationAction fPrevious; + + private NavigationAction fToolbarNext; + private NavigationAction fToolbarPrevious; + + public CompareEditorContributor() { + ResourceBundle bundle= CompareUI.getResourceBundle(); + + IWorkbenchHelpSystem helpSystem= PlatformUI.getWorkbench().getHelpSystem(); + + fIgnoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle, null); + helpSystem.setHelp(fIgnoreWhitespace, ICompareContextIds.IGNORE_WHITESPACE_ACTION); + + fNext= new NavigationAction(bundle, true); + helpSystem.setHelp(fNext, ICompareContextIds.GLOBAL_NEXT_DIFF_ACTION); + + fPrevious= new NavigationAction(bundle, false); + helpSystem.setHelp(fPrevious, ICompareContextIds.GLOBAL_PREVIOUS_DIFF_ACTION); + + fToolbarNext= new NavigationAction(bundle, true); + helpSystem.setHelp(fToolbarNext, ICompareContextIds.NEXT_DIFF_ACTION); + + fToolbarPrevious= new NavigationAction(bundle, false); + helpSystem.setHelp(fToolbarPrevious, ICompareContextIds.PREVIOUS_DIFF_ACTION); + } + + /* + * @see EditorActionBarContributor#contributeToToolBar(IToolBarManager) + */ + public void contributeToToolBar(IToolBarManager tbm) { + tbm.add(new Separator()); + tbm.add(fIgnoreWhitespace); + tbm.add(fToolbarNext); + tbm.add(fToolbarPrevious); + } + + /* + * @see EditorActionBarContributor#contributeToMenu(IMenuManager) + */ + public void contributeToMenu(IMenuManager menuManager) { + // empty implementation + } + + public void setActiveEditor(IEditorPart targetEditor) { + + if (fActiveEditorPart == targetEditor) + return; + + fActiveEditorPart= targetEditor; + + if (fActiveEditorPart != null) { + IEditorInput input= fActiveEditorPart.getEditorInput(); + if (input instanceof CompareEditorInput) { + CompareEditorInput compareInput= (CompareEditorInput) input; + fNext.setCompareEditorInput(compareInput); + fPrevious.setCompareEditorInput(compareInput); + // Begin fix http://bugs.eclipse.org/bugs/show_bug.cgi?id=20105 + fToolbarNext.setCompareEditorInput(compareInput); + fToolbarPrevious.setCompareEditorInput(compareInput); + // End fix http://bugs.eclipse.org/bugs/show_bug.cgi?id=20105 + } + } + + if (targetEditor instanceof CompareEditor) { + IActionBars actionBars= getActionBars(); + + CompareEditor editor= (CompareEditor) targetEditor; + editor.setActionBars(actionBars); + + actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), fNext); + actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), fPrevious); + + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION, fNext); + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION, fPrevious); + + CompareConfiguration cc= editor.getCompareConfiguration(); + fIgnoreWhitespace.setCompareConfiguration(cc); + } else { + IActionBars actionBars= getActionBars(); + actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), null); + actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), null); + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION, null); + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION, null); + } + } + + public void dispose() { + setActiveEditor(null); + super.dispose(); + fIgnoreWhitespace.dispose(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java new file mode 100644 index 000000000..9fc2ccf8b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.compare.*; + +/** + * Supports cross-pane navigation through the differences contained in a {@link CompareEditorInput} + * or a similar type of compare container. + * @see INavigatable + */ +public class CompareEditorInputNavigator extends CompareNavigator { + + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + private boolean fNextFirstTime= true; + private Object[] fPanes; + + /** + * Create a navigator for navigating the given panes + * @param panes the panes to navigate. + */ + public CompareEditorInputNavigator(Object[] panes) { + fPanes= panes; + } + + /** + * Return the set of panes that this navigator is navigating. + * The {@link INavigatable} is obtain from each pane using the + * adaptable mechanism. + * @return the set of panes that this navigator is navigating + */ + public Object[] getPanes() { + return fPanes; + } + + protected INavigatable[] getNavigatables() { + List result = new ArrayList(); + Object[] panes = getPanes(); + for (int i = 0; i < panes.length; i++) { + Object pane = panes[i]; + INavigatable navigator= getNavigator(pane); + if (navigator != null) + result.add(navigator); + } + return (INavigatable[]) result.toArray(new INavigatable[result.size()]); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareNavigator#selectChange(boolean) + */ + public boolean selectChange(boolean next) { + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + if (next && fNextFirstTime && mustOpen()) { + fNextFirstTime= false; + if (openElement()) + return false; + } + return super.selectChange(next); + } + + /* + * Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + */ + private boolean mustOpen() { + Object[] panes = getPanes(); + if (panes == null || panes.length == 0) + return false; + for (int i= 1; i < panes.length; i++) { + Object pane= panes[i]; + INavigatable nav = getNavigator(pane); + if (nav != null && nav.getInput() != null) + return false; + } + return true; + } + + /* + * Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + */ + private boolean openElement() { + Object[] panes = getPanes(); + if (panes == null || panes.length == 0) + return false; + INavigatable nav = getNavigator(panes[0]); + if (nav != null) { + if (!nav.openSelectedChange()) + // selected change not opened, open first instead + nav.selectChange(INavigatable.FIRST_CHANGE); + return true; + } + return false; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java new file mode 100644 index 000000000..771688bcd --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * Copyright (c) 2008, 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.internal; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.widgets.Widget; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; + +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; + +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.TextSelection; +import org.eclipse.jface.text.TextViewer; + + +/** + * A selection provider for view parts with more that one viewer. Tracks the + * focus of the viewers to provide the correct selection. + * + * This is a modified version of + * org.eclipse.jdt.internal.ui.viewsupport.SelectionProviderMediator + */ +public class CompareEditorSelectionProvider implements IPostSelectionProvider { + + private class InternalListener implements ISelectionChangedListener, FocusListener { + /* + * @see ISelectionChangedListener#selectionChanged + */ + public void selectionChanged(SelectionChangedEvent event) { + doSelectionChanged(event); + } + + /* + * @see FocusListener#focusGained + */ + public void focusGained(FocusEvent e) { + // expecting a StyledText widget here + doFocusChanged(e.widget); + } + + /* + * @see FocusListener#focusLost + */ + public void focusLost(FocusEvent e) { + // do not reset due to focus behavior on GTK + //fViewerInFocus= null; + } + } + + private class InternalPostSelectionListener implements ISelectionChangedListener { + public void selectionChanged(SelectionChangedEvent event) { + doPostSelectionChanged(event); + } + + } + + private TextViewer[] fViewers; + + private TextViewer fViewerInFocus; + private ListenerList fSelectionChangedListeners; + private ListenerList fPostSelectionChangedListeners; + + public CompareEditorSelectionProvider() { + fSelectionChangedListeners = new ListenerList(); + fPostSelectionChangedListeners = new ListenerList(); + // nothing more to do here, Compare Editor is initializing + } + + /** + * @param viewers All viewers that can provide a selection + * @param viewerInFocus the viewer currently in focus or <code>null</code> + */ + public void setViewers(TextViewer[] viewers, TextViewer viewerInFocus) { + Assert.isNotNull(viewers); + fViewers= viewers; + InternalListener listener= new InternalListener(); + fViewerInFocus= viewerInFocus; + + for (int i= 0; i < fViewers.length; i++) { + TextViewer viewer= fViewers[i]; + viewer.addSelectionChangedListener(listener); + viewer.addPostSelectionChangedListener(new InternalPostSelectionListener()); + StyledText textWidget = viewer.getTextWidget(); + textWidget.addFocusListener(listener); + } + } + + private void doFocusChanged(Widget control) { + for (int i= 0; i < fViewers.length; i++) { + if (fViewers[i].getTextWidget() == control) { + propagateFocusChanged(fViewers[i]); + return; + } + } + } + + final void doPostSelectionChanged(SelectionChangedEvent event) { + ISelectionProvider provider= event.getSelectionProvider(); + if (provider == fViewerInFocus) { + firePostSelectionChanged(); + } + } + + final void doSelectionChanged(SelectionChangedEvent event) { + ISelectionProvider provider= event.getSelectionProvider(); + if (provider == fViewerInFocus) { + fireSelectionChanged(); + } + } + + final void propagateFocusChanged(TextViewer viewer) { + if (viewer != fViewerInFocus) { // OK to compare by identity + fViewerInFocus= viewer; + fireSelectionChanged(); + firePostSelectionChanged(); + } + } + + private void fireSelectionChanged() { + if (fSelectionChangedListeners != null) { + SelectionChangedEvent event= new SelectionChangedEvent(this, getSelection()); + + Object[] listeners= fSelectionChangedListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ISelectionChangedListener listener= (ISelectionChangedListener) listeners[i]; + listener.selectionChanged(event); + } + } + } + + private void firePostSelectionChanged() { + if (fPostSelectionChangedListeners != null) { + SelectionChangedEvent event= new SelectionChangedEvent(this, getSelection()); + + Object[] listeners= fPostSelectionChangedListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ISelectionChangedListener listener= (ISelectionChangedListener) listeners[i]; + listener.selectionChanged(event); + } + } + } + + /* + * @see ISelectionProvider#addSelectionChangedListener + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + fSelectionChangedListeners.add(listener); + } + + /* + * @see ISelectionProvider#removeSelectionChangedListener + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + fSelectionChangedListeners.remove(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IPostSelectionProvider#addPostSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + fPostSelectionChangedListeners.add(listener); + } + + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IPostSelectionProvider#removePostSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + fPostSelectionChangedListeners.remove(listener); + } + + /* + * @see ISelectionProvider#getSelection + */ + public ISelection getSelection() { + if (fViewerInFocus != null) { + return fViewerInFocus.getSelection(); + } + return TextSelection.emptySelection(); + } + + /* + * @see ISelectionProvider#setSelection + */ + public void setSelection(ISelection selection) { + setSelection(selection, true); + } + + public void setSelection(ISelection selection, boolean reveal) { + if (fViewerInFocus != null) { + if (reveal && !isSelectionInsideVisibleRegion(fViewerInFocus, selection)) + resetVisibleRegion(); + fViewerInFocus.setSelection(selection, reveal); + } + } + + /** + * Resets the visible region for all text viewers of this selection provider. + * + * @since 3.6 + */ + private void resetVisibleRegion() { + if (fViewers == null) + return; + + for (int i= 0; i < fViewers.length; i++) + fViewers[i].setVisibleRegion(0, fViewers[i].getDocument().getLength()); + } + + /** + * Tells whether the given selection is inside the text viewer's visible region. + * + * @param textViewer the text viewer + * @param selection the selection + * @return <code>true</code> if the selection is inside the text viewer's visible region + * @since 3.6 + */ + private static boolean isSelectionInsideVisibleRegion(TextViewer textViewer, ISelection selection) { + if (!(selection instanceof ITextSelection)) + return false; + + ITextSelection textSelection= (ITextSelection)selection; + IRegion visibleRegion= textViewer.getVisibleRegion(); + + return textSelection.getOffset() >= visibleRegion.getOffset() && textSelection.getOffset() + textSelection.getLength() <= visibleRegion.getOffset() + visibleRegion.getLength(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java new file mode 100644 index 000000000..3a17994be --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java @@ -0,0 +1,397 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import com.ibm.icu.text.MessageFormat; +import java.util.StringTokenizer; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.runtime.IStatus; + + +public class CompareFilter { + private static final char[][] NO_CHAR_CHAR= new char[0][]; + + private char[][] fExtraResourceFileFilters; + private String[] fExtraResourceFolderFilters; + + + public CompareFilter() { + // nothing to do + } + + /* + * Returns true if path matches filter, that is if path should be filtered. + */ + public boolean filter(String path0, boolean folder, boolean isArchive) { + if (!folder && fExtraResourceFileFilters != null) { + char[] name= path0.toCharArray(); + for (int i= 0, l= fExtraResourceFileFilters.length; i < l; i++) + if (match(fExtraResourceFileFilters[i], name, true)) + return true; + } + if (folder && fExtraResourceFolderFilters != null) { + for (int i= 0, l= fExtraResourceFolderFilters.length; i < l; i++) + if (fExtraResourceFolderFilters[i].equals(path0)) + return true; + } + return false; + } + + public static String validateResourceFilters(String text) { + IWorkspace workspace= ResourcesPlugin.getWorkspace(); + String[] filters= getTokens(text, ","); //$NON-NLS-1$ + for (int i= 0; i < filters.length; i++) { + String fileName= filters[i].replace('*', 'x'); + int resourceType= IResource.FILE; + int lastCharacter= fileName.length() - 1; + if (lastCharacter >= 0 && fileName.charAt(lastCharacter) == '/') { + fileName= fileName.substring(0, lastCharacter); + resourceType= IResource.FOLDER; + } + IStatus status= workspace.validateName(fileName, resourceType); + if (status.matches(IStatus.ERROR)) { + String format= Utilities.getString("ComparePreferencePage.filter.invalidsegment.error"); //$NON-NLS-1$ + return MessageFormat.format(format, new String[] { status.getMessage() } ); + } + } + return null; + } + + public void setFilters(String filterSequence) { + char[][] filters= filterSequence != null && filterSequence.length() > 0 + ? splitAndTrimOn(',', filterSequence.toCharArray()) + : null; + if (filters == null) { + fExtraResourceFileFilters= null; + fExtraResourceFolderFilters= null; + } else { + int fileCount= 0, folderCount= 0; + for (int i= 0, l= filters.length; i < l; i++) { + char[] f= filters[i]; + if (f.length == 0) + continue; + if (f[f.length - 1] == '/') + folderCount++; + else + fileCount++; + } + fExtraResourceFileFilters= new char[fileCount][]; + fExtraResourceFolderFilters= new String[folderCount]; + for (int i= 0, l= filters.length; i < l; i++) { + char[] f= filters[i]; + if (f.length == 0) + continue; + if (f[f.length - 1] == '/') + fExtraResourceFolderFilters[--folderCount]= new String(subarray(f, 0, f.length - 1)); + else + fExtraResourceFileFilters[--fileCount]= f; + } + } + } + + ///////// + + private static String[] getTokens(String text, String separator) { + StringTokenizer tok= new StringTokenizer(text, separator); + int nTokens= tok.countTokens(); + String[] res= new String[nTokens]; + for (int i= 0; i < res.length; i++) + res[i]= tok.nextToken().trim(); + return res; + } + + /** + * Answers true if the pattern matches the given name, false otherwise. + * This char[] pattern matching accepts wild-cards '*' and '?'. + * + * When not case sensitive, the pattern is assumed to already be + * lowercased, the name will be lowercased character per character as + * comparing. If name is null, the answer is false. If pattern is null, the + * answer is true if name is not null. <br><br>For example: + * <ol> + * <li> + * + * <pre> + * pattern = { '?', 'b', '*' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => true + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * pattern = { '?', 'b', '?' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => false + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * pattern = { 'b', '*' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => false + * </pre> + * + * + * </li> + * </ol> + * + * @param pattern + * the given pattern + * @param name + * the given name + * @param isCaseSensitive + * flag to know whether or not the matching should be case + * sensitive + * @return true if the pattern matches the given name, false otherwise + */ + private boolean match(char[] pattern, char[] name, boolean isCaseSensitive) { + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + return match(pattern, 0, pattern.length, name, 0, name.length, isCaseSensitive); + } + + /** + * Answers true if the a sub-pattern matches the subpart of the given name, + * false otherwise. char[] pattern matching, accepting wild-cards '*' and + * '?'. Can match only subset of name/pattern. end positions are + * non-inclusive. The subpattern is defined by the patternStart and + * pattternEnd positions. When not case sensitive, the pattern is assumed + * to already be lowercased, the name will be lowercased character per + * character as comparing. <br><br>For example: + * <ol> + * <li> + * + * <pre> + * pattern = { '?', 'b', '*' } patternStart = 1 patternEnd = 3 name = { 'a', 'b', 'c' , 'd' } nameStart = 1 nameEnd = 4 isCaseSensitive = true result => true + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * pattern = { '?', 'b', '*' } patternStart = 1 patternEnd = 2 name = { 'a', 'b', 'c' , 'd' } nameStart = 1 nameEnd = 2 isCaseSensitive = true result => false + * </pre> + * + * + * </li> + * </ol> + * + * @param pattern + * the given pattern + * @param patternStart + * the given pattern start + * @param patternEnd + * the given pattern end + * @param name + * the given name + * @param nameStart + * the given name start + * @param nameEnd + * the given name end + * @param isCaseSensitive + * flag to know if the matching should be case sensitive + * @return true if the a sub-pattern matches the subpart of the given name, + * false otherwise + */ + private boolean match(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd, + boolean isCaseSensitive) { + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + int iPattern= patternStart; + int iName= nameStart; + if (patternEnd < 0) + patternEnd= pattern.length; + if (nameEnd < 0) + nameEnd= name.length; + /* check first segment */ + char patternChar= 0; + while ((iPattern < patternEnd) && (patternChar= pattern[iPattern]) != '*') { + if (iName == nameEnd) + return false; + if (patternChar != (isCaseSensitive ? name[iName] : Character.toLowerCase(name[iName])) && patternChar != '?') { + return false; + } + iName++; + iPattern++; + } + /* check sequence of star+segment */ + int segmentStart; + if (patternChar == '*') { + segmentStart= ++iPattern; // skip star + } else { + segmentStart= 0; // force iName check + } + int prefixStart= iName; + checkSegment : while (iName < nameEnd) { + if (iPattern == patternEnd) { + iPattern= segmentStart; // mismatch - restart current segment + iName= ++prefixStart; + continue checkSegment; + } + /* segment is ending */ + if ((patternChar= pattern[iPattern]) == '*') { + segmentStart= ++iPattern; // skip start + if (segmentStart == patternEnd) { + return true; + } + prefixStart= iName; + continue checkSegment; + } + /* check current name character */ + if ((isCaseSensitive ? name[iName] : Character.toLowerCase(name[iName])) != patternChar && patternChar != '?') { + iPattern= segmentStart; // mismatch - restart current segment + iName= ++prefixStart; + continue checkSegment; + } + iName++; + iPattern++; + } + return (segmentStart == patternEnd) || (iName == nameEnd && iPattern == patternEnd) + || (iPattern == patternEnd - 1 && pattern[iPattern] == '*'); + } + + /** + * Return a new array which is the split of the given array using the given + * divider and triming each subarray to remove whitespaces equals to ' '. + * <br><br>For example: + * <ol> + * <li> + * + * <pre> + * divider = 'b' array = { 'a' , 'b', 'b', 'a', 'b', 'a' } result => { { 'a' }, { }, { 'a' }, { 'a' } } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * divider = 'c' array = { 'a' , 'b', 'b', 'a', 'b', 'a' } result => { { 'a', 'b', 'b', 'a', 'b', 'a' } } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * divider = 'b' array = { 'a' , ' ', 'b', 'b', 'a', 'b', 'a' } result => { { 'a' }, { }, { 'a' }, { 'a' } } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * divider = 'c' array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' } result => { { 'a', 'b', 'b', 'a', 'b', 'a' } } + * </pre> + * + * + * </li> + * </ol> + * + * @param divider + * the given divider + * @param array + * the given array + * @return a new array which is the split of the given array using the + * given divider and triming each subarray to remove whitespaces + * equals to ' ' + */ + private char[][] splitAndTrimOn(char divider, char[] array) { + int length= array == null ? 0 : array.length; + if (length == 0) + return NO_CHAR_CHAR; + int wordCount= 1; + for (int i= 0; i < length; i++) + if (array[i] == divider) + wordCount++; + char[][] split= new char[wordCount][]; + int last= 0, currentWord= 0; + for (int i= 0; i < length; i++) { + if (array[i] == divider) { + int start= last, end= i - 1; + while (start < i && array[start] == ' ') + start++; + while (end > start && array[end] == ' ') + end--; + split[currentWord]= new char[end - start + 1]; + System.arraycopy(array, start, split[currentWord++], 0, end - start + 1); + last= i + 1; + } + } + int start= last, end= length - 1; + while (start < length && array[start] == ' ') + start++; + while (end > start && array[end] == ' ') + end--; + split[currentWord]= new char[end - start + 1]; + System.arraycopy(array, start, split[currentWord++], 0, end - start + 1); + return split; + } + + /** + * Answers a new array which is a copy of the given array starting at the + * given start and ending at the given end. The given start is inclusive + * and the given end is exclusive. Answers null if start is greater than + * end, if start is lower than 0 or if end is greater than the length of + * the given array. If end equals -1, it is converted to the array length. + * <br><br>For example: + * <ol> + * <li> + * + * <pre> + * array = { 'a' , 'b' } start = 0 end = 1 result => { 'a' } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * array = { 'a', 'b' } start = 0 end = -1 result => { 'a' , 'b' } + * </pre> + * + * + * </li> + * </ol> + * + * @param array + * the given array + * @param start + * the given starting index + * @param end + * the given ending index + * @return a new array which is a copy of the given array starting at the + * given start and ending at the given end + * @exception NullPointerException + * if the given array is null + */ + private char[] subarray(char[] array, int start, int end) { + if (end == -1) + end= array.length; + if (start > end) + return null; + if (start < 0) + return null; + if (end > array.length) + return null; + char[] result= new char[end - start]; + System.arraycopy(array, start, result, 0, end - start); + return result; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java new file mode 100644 index 000000000..809e79d21 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2007 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.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.compare.ICompareContainer; +import org.eclipse.core.expressions.Expression; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.commands.ActionHandler; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.*; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.services.IServiceLocator; + +public class CompareHandlerService { + + private final List fActivations = new ArrayList(); + private final Expression fExpression; + private ICompareContainer fContainer; + private boolean fDisposed; + private List fPaneActivations = new ArrayList(); + private IHandlerService fHandlerService; + + public static CompareHandlerService createFor(ICompareContainer container, Shell shell) { + IServiceLocator serviceLocator = container.getServiceLocator(); + if (serviceLocator != null) { + IHandlerService service = (IHandlerService)serviceLocator.getService(IHandlerService.class); + if (service != null) + return new CompareHandlerService(container, null); + } + if (container.getWorkbenchPart() == null && shell != null) { + // We're in a dialog so we can use an active shell expression + IHandlerService service = (IHandlerService)PlatformUI.getWorkbench().getService(IHandlerService.class); + if (service != null) { + Expression e = new ActiveShellExpression(shell); + return new CompareHandlerService(container, e); + } + } + return new CompareHandlerService(null, null); + } + + private CompareHandlerService(ICompareContainer container, + Expression expression) { + fContainer = container; + fExpression = expression; + initialize(); + } + + public void registerAction(IAction action, String commandId) { + IHandlerService handlerService = getHandlerService(); + if (handlerService == null) + return; + action.setActionDefinitionId(commandId); + IHandlerActivation activation; + if (fExpression == null) { + activation = handlerService.activateHandler(commandId, new ActionHandler(action)); + } else { + activation = handlerService.activateHandler(commandId, new ActionHandler(action), fExpression); + } + if (activation != null) { + fActivations .add(activation); + } + } + + private IHandlerService getHandlerService() { + if (fDisposed) + return null; + return fHandlerService; + } + + private void initialize() { + if (fHandlerService == null) { + IServiceLocator serviceLocator = fContainer.getServiceLocator(); + if (serviceLocator != null) { + IHandlerService service = (IHandlerService)serviceLocator.getService(IHandlerService.class); + if (service != null) + fHandlerService = service; + } + if (fHandlerService == null && fContainer.getWorkbenchPart() == null && fExpression != null) { + // We're in a dialog so we can use an active shell expression + IHandlerService service = (IHandlerService)PlatformUI.getWorkbench().getService(IHandlerService.class); + if (service != null) { + fHandlerService = service; + } + } + } + } + + public void setGlobalActionHandler(String actionId, IAction actionHandler) { + IActionBars bars = getActionBars(); + if (bars != null) { + bars.setGlobalActionHandler(actionId, actionHandler); + return; + } else if (fExpression != null && actionHandler != null && actionHandler.getActionDefinitionId() != null) { + IHandlerService service = getHandlerService(); + if (service != null) { + IHandlerActivation activation = service.activateHandler(actionHandler.getActionDefinitionId(), new ActionHandler(actionHandler), fExpression); + fPaneActivations.add(activation); + return; + } + } + // Remove the action definition id since we won't get key bindings + if (actionHandler != null) + actionHandler.setActionDefinitionId(null); + } + + private void updateActionBars() { + IActionBars bars = getActionBars(); + if (bars != null) + bars.updateActionBars(); + } + + private void clearPaneActionHandlers() { + if (!fPaneActivations.isEmpty()) { + IHandlerService service = getHandlerService(); + if (service != null) { + service.deactivateHandlers(fPaneActivations); + fPaneActivations.clear(); + } + } + } + + private IActionBars getActionBars() { + return fContainer.getActionBars(); + } + + public void dispose() { + clearPaneActionHandlers(); + IHandlerService service = getHandlerService(); + if (service == null) + return; + service.deactivateHandlers(fActivations); + fActivations.clear(); + fDisposed = true; + } + + public void updatePaneActionHandlers(Runnable runnable) { + clearPaneActionHandlers(); + runnable.run(); + updateActionBars(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java new file mode 100644 index 000000000..6be02f6ff --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * 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 + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959, Bug 73923 + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.osgi.util.NLS; + +public final class CompareMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.CompareMessages";//$NON-NLS-1$ + + private CompareMessages() { + // Do not instantiate + } + + public static String CompareContainer_0; + public static String CompareDialog_commit_button; + public static String CompareDialog_error_message; + public static String CompareDialog_error_title; + public static String CompareEditor_0; + public static String CompareEditor_1; + public static String CompareEditor_2; + public static String DocumentMerger_0; + public static String DocumentMerger_1; + public static String DocumentMerger_2; + public static String DocumentMerger_3; + public static String CompareEditorInput_0; + public static String ComparePlugin_internal_error; + public static String ComparePreferencePage_0; + public static String ComparePreferencePage_1; + public static String ComparePreferencePage_2; + public static String ComparePreferencePage_3; + public static String ComparePreferencePage_4; + public static String CompareUIPlugin_0; + public static String CompareUIPlugin_1; + public static String ContentMergeViewer_resource_changed_description; + public static String ContentMergeViewer_resource_changed_title; + public static String ExceptionDialog_seeErrorLogMessage; + public static String CompareViewerSwitchingPane_Titleformat; + public static String NavigationEndDialog_0; + public static String NavigationEndDialog_1; + public static String ShowWhitespaceAction_0; + public static String StructureDiffViewer_0; + public static String StructureDiffViewer_1; + public static String StructureDiffViewer_2; + public static String StructureDiffViewer_3; + public static String StructureDiffViewer_NoStructuralDifferences; + public static String StructureDiffViewer_StructureError; + public static String TextMergeViewer_0; + public static String TextMergeViewer_1; + public static String TextMergeViewer_10; + public static String TextMergeViewer_11; + public static String TextMergeViewer_12; + public static String TextMergeViewer_13; + public static String TextMergeViewer_14; + public static String TextMergeViewer_15; + public static String TextMergeViewer_16; + public static String TextMergeViewer_17; + public static String TextMergeViewer_2; + public static String TextMergeViewer_3; + public static String TextMergeViewer_4; + public static String TextMergeViewer_5; + public static String TextMergeViewer_6; + public static String TextMergeViewer_7; + public static String TextMergeViewer_8; + public static String TextMergeViewer_9; + public static String TextMergeViewer_accessible_ancestor; + public static String TextMergeViewer_accessible_left; + public static String TextMergeViewer_accessible_right; + public static String TextMergeViewer_cursorPosition_format; + public static String TextMergeViewer_beforeLine_format; + public static String TextMergeViewer_range_format; + public static String TextMergeViewer_changeType_addition; + public static String TextMergeViewer_changeType_deletion; + public static String TextMergeViewer_changeType_change; + public static String TextMergeViewer_direction_outgoing; + public static String TextMergeViewer_direction_incoming; + public static String TextMergeViewer_direction_conflicting; + public static String TextMergeViewer_diffType_format; + public static String TextMergeViewer_diffDescription_noDiff_format; + public static String TextMergeViewer_diffDescription_diff_format; + public static String TextMergeViewer_statusLine_format; + public static String TextMergeViewer_atEnd_title; + public static String TextMergeViewer_atEnd_message; + public static String TextMergeViewer_atBeginning_title; + public static String TextMergeViewer_atBeginning_message; + public static String CompareNavigator_atEnd_title; + public static String CompareNavigator_atEnd_message; + public static String CompareNavigator_atBeginning_title; + public static String CompareNavigator_atBeginning_message; + public static String WorkerJob_0; + public static String SelectAncestorDialog_title; + public static String SelectAncestorDialog_message; + public static String SelectAncestorDialog_option; + public static String CompareWithOtherResourceDialog_ancestor; + public static String CompareWithOtherResourceDialog_rightPanel; + public static String CompareWithOtherResourceDialog_leftPanel; + public static String CompareWithOtherResourceDialog_dialogTitle; + public static String CompareWithOtherResourceDialog_dialogMessage; + public static String CompareWithOtherResourceDialog_error_not_comparable; + public static String CompareWithOtherResourceDialog_error_empty; + public static String CompareWithOtherResourceDialog_clear; + public static String CompareWithOtherResourceDialog_info; + public static String CompareWithOtherResourceDialog_externalFile_errorTitle; + public static String CompareWithOtherResourceDialog_externalFile_errorMessage; + public static String CompareWithOtherResourceDialog_externalFileMainButton; + public static String CompareWithOtherResourceDialog_externalFileRadioButton; + public static String CompareWithOtherResourceDialog_externalFolderMainButton; + public static String CompareWithOtherResourceDialog_externalFolderRadioButton; + public static String CompareWithOtherResourceDialog_workspaceMainButton; + public static String CompareWithOtherResourceDialog_workspaceRadioButton; + public static String CompareContentViewerSwitchingPane_defaultViewer; + public static String CompareContentViewerSwitchingPane_switchButtonTooltip; + public static String CompareContentViewerSwitchingPane_discoveredLabel; + public static String CompareContentViewerSwitchingPane_optimized; + public static String CompareContentViewerSwitchingPane_optimizedTooltip; + public static String CompareStructureViewerSwitchingPane_defaultViewer; + public static String CompareStructureViewerSwitchingPane_switchButtonTooltip; + public static String CompareStructureViewerSwitchingPane_discoveredLabel; + + public static String ReaderCreator_fileIsNotAccessible; + + static { + NLS.initializeMessages(BUNDLE_NAME, CompareMessages.class); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties new file mode 100644 index 000000000..451ac3bb6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties @@ -0,0 +1,143 @@ +############################################################################### +# 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 +# Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource +# Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959, Bug 73923 +############################################################################### + +ComparePlugin_internal_error= Internal Error +ExceptionDialog_seeErrorLogMessage= See error log for details. + +# +# Title format for CompareViewerSwitchingPane +# +CompareViewerSwitchingPane_Titleformat= {0} ({1}) + +# +# Title message for StructureDiffViewer if no structural differences could be found +# +StructureDiffViewer_NoStructuralDifferences= No Structural Differences +StructureDiffViewer_StructureError= Cannot Compare Structures +StructureDiffViewer_0=Generating Structure Differences +StructureDiffViewer_1=Computing Structure Differences +StructureDiffViewer_2=Generating Structure Differences +StructureDiffViewer_3=Refresh Canceled + +# +# TextMergeViewer +# +TextMergeViewer_cursorPosition_format= {0} : {1} + +TextMergeViewer_beforeLine_format= before line {0} +TextMergeViewer_range_format= {0} : {1} + +TextMergeViewer_changeType_addition= addition +TextMergeViewer_changeType_deletion= deletion +TextMergeViewer_changeType_change= change + +TextMergeViewer_direction_outgoing= outgoing +TextMergeViewer_direction_incoming= incoming +TextMergeViewer_direction_conflicting= conflicting + +TextMergeViewer_diffType_format= {0} {1} +TextMergeViewer_accessible_left=Left: {0} + +TextMergeViewer_diffDescription_noDiff_format= no diff +TextMergeViewer_diffDescription_diff_format= {0} #{1} (Left: {2}, Right: {3}) +TextMergeViewer_statusLine_format= Left: {0}, Right: {1}, {2} + +TextMergeViewer_atEnd_title= Go to Next Difference +TextMergeViewer_0=End Reached +TextMergeViewer_1=You have reached the end of the currently displayed element. What would you like to do? +TextMergeViewer_2=&Go to the beginning of this element +TextMergeViewer_3=&Display the next element +TextMergeViewer_4=Beginning Reached +TextMergeViewer_5=You have reached the beginning of the currently displayed element. What would you like to do? +TextMergeViewer_6=&Go to the end of this element +TextMergeViewer_7=&Display the previous element +TextMergeViewer_8=End Reached +TextMergeViewer_9=You have reached the last difference. Would you like go to the beginning of this element? +TextMergeViewer_10=Beginning Reached +TextMergeViewer_11=You have reached the first difference. Would you like to go to the end of this element? +TextMergeViewer_12=Element is Read Only +TextMergeViewer_13=The element being edited is read-only +TextMergeViewer_14=Element is Read Only +TextMergeViewer_15=The element being edited is read-only +TextMergeViewer_16=Show &Line Numbers +TextMergeViewer_17=Do ¬hing +TextMergeViewer_atEnd_message= End of document reached. Continue from beginning? + +TextMergeViewer_atBeginning_title= Go to Previous Difference +TextMergeViewer_accessible_right=Right: {0} +TextMergeViewer_atBeginning_message= Beginning of document reached. Continue from end? +TextMergeViewer_accessible_ancestor=Ancestor: {0} + +CompareNavigator_atEnd_title= End Reached +CompareDialog_commit_button=C&ommit +CompareDialog_error_title=Error Saving Changes +ComparePreferencePage_0=When the end/beginning is reached while navigating an element +ComparePreferencePage_1=Pro&mpt +ComparePreferencePage_2=G&o to the beginning/end of the element +ComparePreferencePage_3=Disp&lay the next/previous element +ComparePreferencePage_4=Do ¬hing +CompareDialog_error_message=The changes were not saved: {0} +CompareNavigator_atEnd_message= You have reached the last difference. + +CompareNavigator_atBeginning_title= Beginning Reached +CompareNavigator_atBeginning_message= You have reached the first difference. +ContentMergeViewer_resource_changed_title=Resources Changed +ContentMergeViewer_resource_changed_description=The resources being compared have changed outside the compare editor. Do you want to save your changes? Any unsaved changes will be discarded. +NavigationEndDialog_0=Remember decision +NavigationEndDialog_1=Navigation Options +CompareUIPlugin_0=Opening Compare Editor +CompareUIPlugin_1=Opening Compare Dialog +CompareContainer_0=Updating Comparison State +CompareEditor_0=Initializing Compare Editor for {0} +CompareEditor_1=Initializing... +CompareEditor_2=Update comparison {0} +DocumentMerger_0=Computing Differences... +DocumentMerger_1=Too many differences found +DocumentMerger_2=Finding Differences... +DocumentMerger_3=Too many differences found +CompareEditorInput_0=&Select +WorkerJob_0=Multiple errors occurred while processing compare editor events + +SelectAncestorDialog_title=Select Common Ancestor +SelectAncestorDialog_message=Which resource would you like to use as the common ancestor in the three-way compare? +SelectAncestorDialog_option=''{0}'' +ShowWhitespaceAction_0=Show &Whitespace Characters + +CompareWithOtherResourceDialog_ancestor=Ancestor +CompareWithOtherResourceDialog_rightPanel=Right +CompareWithOtherResourceDialog_leftPanel=Left +CompareWithOtherResourceDialog_dialogTitle=Compare with Other Resource +CompareWithOtherResourceDialog_dialogMessage=Select resources to compare +CompareWithOtherResourceDialog_error_not_comparable=Selected resources are not comparable. +CompareWithOtherResourceDialog_error_empty=Both left and right panel must contain a valid path. +CompareWithOtherResourceDialog_clear=&Clear +CompareWithOtherResourceDialog_info=Drag files from a view or between dialog's fields. +CompareWithOtherResourceDialog_externalFile_errorTitle=Compare With Other Resource Error +CompareWithOtherResourceDialog_externalFile_errorMessage=Cannot create a link to an external resource. +CompareWithOtherResourceDialog_externalFileMainButton=Browse... +CompareWithOtherResourceDialog_externalFileRadioButton=&External file +CompareWithOtherResourceDialog_externalFolderMainButton=Browse... +CompareWithOtherResourceDialog_externalFolderRadioButton=External folder +CompareWithOtherResourceDialog_workspaceMainButton=&Browse... +CompareWithOtherResourceDialog_workspaceRadioButton=&Workspace + +CompareContentViewerSwitchingPane_defaultViewer=Default Compare +CompareContentViewerSwitchingPane_switchButtonTooltip=Switch Compare Viewer +CompareContentViewerSwitchingPane_discoveredLabel={0} Compare +CompareContentViewerSwitchingPane_optimized=Differences shown might not be optimal +CompareContentViewerSwitchingPane_optimizedTooltip=To avoid long computation time a faster comparison algorithm has been used. As a result, the differences highlighted in the viewer may be larger than necessary. +CompareStructureViewerSwitchingPane_defaultViewer=Default Structure Compare +CompareStructureViewerSwitchingPane_switchButtonTooltip=Switch Structure Compare Viewer +CompareStructureViewerSwitchingPane_discoveredLabel={0} Structure Compare + +ReaderCreator_fileIsNotAccessible=Cannot create a reader because the file is inaccessible. diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java new file mode 100644 index 000000000..0c7b8c27b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.internal; + +import org.eclipse.compare.*; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.part.IPageSite; +import org.eclipse.ui.part.Page; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + */ +public class CompareOutlinePage extends Page implements IContentOutlinePage, IPropertyChangeListener { + + private CompareEditor fCompareEditor; + private Control fControl; + private CompareViewerSwitchingPane fStructurePane; + private OutlineViewerCreator fCreator; + + CompareOutlinePage(CompareEditor editor) { + fCompareEditor= editor; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite) + */ + public void createControl(Composite parent) { + final Splitter h= new Splitter(parent, SWT.HORIZONTAL); + fStructurePane= 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(fStructurePane, true); + fControl = h; + IPageSite site = getSite(); + site.setSelectionProvider(fStructurePane); + h.layout(); + reset(); + } + + private Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) { + OutlineViewerCreator creator = getCreator(); + if (creator != null) + return creator.findStructureViewer(oldViewer, input, parent, getCompareConfiguration()); + return null; + } + + private CompareConfiguration getCompareConfiguration() { + return fCompareEditor.getCompareConfiguration(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#getControl() + */ + public Control getControl() { + return fControl; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#setFocus() + */ + public void setFocus() { + if (fStructurePane != null) + fStructurePane.setFocus(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if (fStructurePane != null) + fStructurePane.addSelectionChangedListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() + */ + public ISelection getSelection() { + if (fStructurePane != null) + return fStructurePane.getSelection(); + return StructuredSelection.EMPTY; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + if (fStructurePane != null) + fStructurePane.removeSelectionChangedListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) + */ + public void setSelection(ISelection selection) { + if (fStructurePane != null) + fStructurePane.setSelection(selection); + } + + private void setInput(Object input) { + if (fStructurePane != null) { + fStructurePane.setInput(input); + ((Splitter)fControl).layout(); + } + } + + public OutlineViewerCreator getCreator() { + if (fCreator == null) { + fCreator = (OutlineViewerCreator)Utilities.getAdapter(fCompareEditor, OutlineViewerCreator.class); + if (fCreator != null) + fCreator.addPropertyChangeListener(this); + } + return fCreator; + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(OutlineViewerCreator.PROP_INPUT)) { + fStructurePane.setInput(event.getNewValue()); + ((Splitter)fControl).layout(); + } + } + + public void dispose() { + super.dispose(); + if (fCreator != null) + fCreator.removePropertyChangeListener(this); + fCreator = null; + } + + public void reset() { + if (fCreator != null) + fCreator.removePropertyChangeListener(this); + fCreator = null; + OutlineViewerCreator creator = getCreator(); + if (creator != null) + setInput(creator.getInput()); + else + setInput(null); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java new file mode 100644 index 000000000..23162e562 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.internal; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; + +public class ComparePreferenceInitializer extends AbstractPreferenceInitializer { + + public ComparePreferenceInitializer() { + // Nothing to do + } + + public void initializeDefaultPreferences() { + IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore(); + ComparePreferencePage.initDefaults(store); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java new file mode 100644 index 000000000..f4fe72b73 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java @@ -0,0 +1,489 @@ +/******************************************************************************* + * 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.internal; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.swt.widgets.Text; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.preference.RadioGroupFieldEditor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.PreferenceLinkArea; +import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.contentmergeviewer.TextMergeViewer; +import org.eclipse.compare.internal.core.ComparePlugin; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.Differencer; + + +public class ComparePreferencePage extends PreferencePage implements IWorkbenchPreferencePage { + + class FakeInput implements ITypedElement, IEncodedStreamContentAccessor { + static final String UTF_16= "UTF-16"; //$NON-NLS-1$ + String fContent; + + FakeInput(String name) { + fContent= loadPreviewContentFromFile(name); + } + public Image getImage() { + return null; + } + public String getName() { + return "no name"; //$NON-NLS-1$ + } + public String getType() { + return "no type"; //$NON-NLS-1$ + } + public InputStream getContents() { + return new ByteArrayInputStream(Utilities.getBytes(fContent, UTF_16)); + } + public String getCharset() { + return UTF_16; + } + } + + private static final String PREFIX= CompareUIPlugin.PLUGIN_ID + "."; //$NON-NLS-1$ + public static final String OPEN_STRUCTURE_COMPARE= PREFIX + "OpenStructureCompare"; //$NON-NLS-1$ + public static final String USE_OUTLINE_VIEW= PREFIX + "UseOutlineView"; //$NON-NLS-1$ + public static final String SYNCHRONIZE_SCROLLING= PREFIX + "SynchronizeScrolling"; //$NON-NLS-1$ + public static final String SHOW_PSEUDO_CONFLICTS= PREFIX + "ShowPseudoConflicts"; //$NON-NLS-1$ + public static final String INITIALLY_SHOW_ANCESTOR_PANE= PREFIX + "InitiallyShowAncestorPane"; //$NON-NLS-1$ + public static final String PREF_SAVE_ALL_EDITORS= PREFIX + "SaveAllEditors"; //$NON-NLS-1$ + public static final String IGNORE_WHITESPACE= PREFIX + "IgnoreWhitespace"; //$NON-NLS-1$ + + //public static final String USE_SPLINES= PREFIX + "UseSplines"; //$NON-NLS-1$ + public static final String USE_SINGLE_LINE= PREFIX + "UseSingleLine"; //$NON-NLS-1$ + public static final String HIGHLIGHT_TOKEN_CHANGES= PREFIX + "HighlightTokenChanges"; //$NON-NLS-1$ + //public static final String USE_RESOLVE_UI= PREFIX + "UseResolveUI"; //$NON-NLS-1$ + public static final String CAPPING_DISABLED= PREFIX + "CappingDisable"; //$NON-NLS-1$ + public static final String PATH_FILTER= PREFIX + "PathFilter"; //$NON-NLS-1$ + public static final String ADDED_LINES_REGEX= PREFIX + "AddedLinesRegex"; //$NON-NLS-1$ + public static final String REMOVED_LINES_REGEX= PREFIX + "RemovedLinesRegex"; //$NON-NLS-1$ + + + private TextMergeViewer fPreviewViewer; + private IPropertyChangeListener fPreferenceChangeListener; + private CompareConfiguration fCompareConfiguration; + private OverlayPreferenceStore fOverlayStore; + private Map fCheckBoxes= new HashMap(); + private Text fFilters; + private Text addedLinesRegex; + private Text removedLinesRegex; + private SelectionListener fCheckBoxListener; + + + public final OverlayPreferenceStore.OverlayKey[] fKeys= new OverlayPreferenceStore.OverlayKey[] { + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, OPEN_STRUCTURE_COMPARE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_OUTLINE_VIEW), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, SYNCHRONIZE_SCROLLING), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, SHOW_PSEUDO_CONFLICTS), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, INITIALLY_SHOW_ANCESTOR_PANE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, IGNORE_WHITESPACE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PREF_SAVE_ALL_EDITORS), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ADDED_LINES_REGEX), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, REMOVED_LINES_REGEX), + //new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_SPLINES), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_SINGLE_LINE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, HIGHLIGHT_TOKEN_CHANGES), + //new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_RESOLVE_UI), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, CAPPING_DISABLED), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, PATH_FILTER), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL), + }; + private RadioGroupFieldEditor editor; + + + public static void initDefaults(IPreferenceStore store) { + store.setDefault(OPEN_STRUCTURE_COMPARE, true); + store.setDefault(USE_OUTLINE_VIEW, false); + store.setDefault(SYNCHRONIZE_SCROLLING, true); + store.setDefault(SHOW_PSEUDO_CONFLICTS, false); + store.setDefault(INITIALLY_SHOW_ANCESTOR_PANE, false); + store.setDefault(IGNORE_WHITESPACE, false); + store.setDefault(PREF_SAVE_ALL_EDITORS, false); + store.setDefault(ADDED_LINES_REGEX, ""); //$NON-NLS-1$ + store.setDefault(REMOVED_LINES_REGEX, ""); //$NON-NLS-1$ + //store.setDefault(USE_SPLINES, false); + store.setDefault(USE_SINGLE_LINE, true); + store.setDefault(HIGHLIGHT_TOKEN_CHANGES, true); + //store.setDefault(USE_RESOLVE_UI, false); + store.setDefault(CAPPING_DISABLED, false); + store.setDefault(PATH_FILTER, ""); //$NON-NLS-1$ + store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, ICompareUIConstants.PREF_VALUE_PROMPT); + store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, ICompareUIConstants.PREF_VALUE_LOOP); + } + + public ComparePreferencePage() { + + //setDescription(Utilities.getString("ComparePreferencePage.description")); //$NON-NLS-1$ + + setPreferenceStore(CompareUIPlugin.getDefault().getPreferenceStore()); + + fOverlayStore= new OverlayPreferenceStore(getPreferenceStore(), fKeys); + fPreferenceChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + String key= event.getProperty(); + if (key.equals(INITIALLY_SHOW_ANCESTOR_PANE)) { + boolean b= fOverlayStore.getBoolean(INITIALLY_SHOW_ANCESTOR_PANE); + if (fCompareConfiguration != null) { + fCompareConfiguration.setProperty(INITIALLY_SHOW_ANCESTOR_PANE, new Boolean(b)); + } + } + } + }; + fOverlayStore.addPropertyChangeListener(fPreferenceChangeListener); + } + + /* + * @see IWorkbenchPreferencePage#init() + */ + public void init(IWorkbench workbench) { + // empty + } + + /* + * @see PreferencePage#performOk() + */ + public boolean performOk() { + fOverlayStore.setValue(ADDED_LINES_REGEX, addedLinesRegex.getText()); + fOverlayStore.setValue(REMOVED_LINES_REGEX, removedLinesRegex.getText()); + + editor.store(); + fOverlayStore.propagate(); + + ComparePlugin.getDefault().setCappingDisabled( + getPreferenceStore().getBoolean( + ComparePreferencePage.CAPPING_DISABLED)); + return true; + } + + /* + * @see PreferencePage#performDefaults() + */ + protected void performDefaults() { + + fOverlayStore.loadDefaults(); + initializeFields(); + + super.performDefaults(); + } + + /* + * @see DialogPage#dispose() + */ + public void dispose() { + + if (fOverlayStore != null) { + if (fPreferenceChangeListener != null) { + fOverlayStore.removePropertyChangeListener(fPreferenceChangeListener); + fPreferenceChangeListener= null; + } + fOverlayStore.stop(); + fOverlayStore= null; + } + + super.dispose(); + } + + static public boolean getSaveAllEditors() { + IPreferenceStore store= CompareUIPlugin.getDefault().getPreferenceStore(); + return store.getBoolean(PREF_SAVE_ALL_EDITORS); + } + + static public void setSaveAllEditors(boolean value) { + IPreferenceStore store= CompareUIPlugin.getDefault().getPreferenceStore(); + store.setValue(PREF_SAVE_ALL_EDITORS, value); + } + + /* + * @see PreferencePage#createContents(Composite) + */ + protected Control createContents(Composite parent) { + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICompareContextIds.COMPARE_PREFERENCE_PAGE); + + fOverlayStore.load(); + fOverlayStore.start(); + + TabFolder folder= new TabFolder(parent, SWT.NONE); + folder.setLayout(new TabFolderLayout()); + folder.setLayoutData(new GridData(GridData.FILL_BOTH)); + + TabItem item= new TabItem(folder, SWT.NONE); + item.setText(Utilities.getString("ComparePreferencePage.generalTab.label")); //$NON-NLS-1$ + //item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CFILE)); + item.setControl(createGeneralPage(folder)); + + item= new TabItem(folder, SWT.NONE); + item.setText(Utilities.getString("ComparePreferencePage.textCompareTab.label")); //$NON-NLS-1$ + //item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CFILE)); + item.setControl(createTextComparePage(folder)); + + initializeFields(); + Dialog.applyDialogFont(folder); + return folder; + } + + private Control createGeneralPage(Composite parent) { + Composite composite= new Composite(parent, SWT.NULL); + GridLayout layout= new GridLayout(); + layout.numColumns= 1; + composite.setLayout(layout); + + addCheckBox(composite, "ComparePreferencePage.structureCompare.label", OPEN_STRUCTURE_COMPARE, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.structureOutline.label", USE_OUTLINE_VIEW, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.ignoreWhitespace.label", IGNORE_WHITESPACE, 0); //$NON-NLS-1$ + + // a spacer + new Label(composite, SWT.NONE); + + addCheckBox(composite, "ComparePreferencePage.saveBeforePatching.label", PREF_SAVE_ALL_EDITORS, 0); //$NON-NLS-1$ + + // a spacer + new Label(composite, SWT.NONE); + + Label l= new Label(composite, SWT.WRAP); + l.setText(Utilities.getString("ComparePreferencePage.regex.description")); //$NON-NLS-1$ + + Composite c2= new Composite(composite, SWT.NONE); + c2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout= new GridLayout(2, false); + layout.marginWidth= 0; + c2.setLayout(layout); + + l= new Label(c2, SWT.NONE); + l.setText(Utilities.getString("ComparePreferencePage.regexAdded.label")); //$NON-NLS-1$ + addedLinesRegex = new Text(c2, SWT.BORDER); + addedLinesRegex.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + addedLinesRegex.setText(fOverlayStore.getString(ADDED_LINES_REGEX)); + + l= new Label(c2, SWT.NONE); + l.setText(Utilities.getString("ComparePreferencePage.regexRemoved.label")); //$NON-NLS-1$ + removedLinesRegex = new Text(c2, SWT.BORDER); + removedLinesRegex.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + removedLinesRegex.setText(fOverlayStore.getString(REMOVED_LINES_REGEX)); + + // a spacer + new Label(composite, SWT.NONE); + + l= new Label(composite, SWT.WRAP); + l.setText(Utilities.getString("ComparePreferencePage.filter.description")); //$NON-NLS-1$ + + Composite c3= new Composite(composite, SWT.NONE); + c3.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout= new GridLayout(2, false); + layout.marginWidth= 0; + c3.setLayout(layout); + + l= new Label(c3, SWT.NONE); + l.setText(Utilities.getString("ComparePreferencePage.filter.label")); //$NON-NLS-1$ + + fFilters= new Text(c3, SWT.BORDER); + fFilters.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + fFilters.setText(fOverlayStore.getString(PATH_FILTER)); + fFilters.addModifyListener( + new ModifyListener() { + public void modifyText(ModifyEvent e) { + String filters= fFilters.getText(); + String message= CompareFilter.validateResourceFilters(filters); + setValid(message == null); + setMessage(null); + setErrorMessage(message); + fOverlayStore.setValue(PATH_FILTER, filters); + } + } + ); + + return composite; + } + + private Control createTextComparePage(Composite parent) { + + Composite composite= new Composite(parent, SWT.NULL); + GridLayout layout= new GridLayout(); + layout.numColumns= 1; + composite.setLayout(layout); + + addCheckBox(composite, "ComparePreferencePage.synchronizeScrolling.label", SYNCHRONIZE_SCROLLING, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.initiallyShowAncestorPane.label", INITIALLY_SHOW_ANCESTOR_PANE, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.showPseudoConflicts.label", SHOW_PSEUDO_CONFLICTS, 0); //$NON-NLS-1$ + + //addCheckBox(composite, "ComparePreferencePage.useSplines.label", USE_SPLINES, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.useSingleLine.label", USE_SINGLE_LINE, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.highlightTokenChanges.label", HIGHLIGHT_TOKEN_CHANGES, 0); //$NON-NLS-1$ + //addCheckBox(composite, "ComparePreferencePage.useResolveUI.label", USE_RESOLVE_UI, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.disableCapping.label", CAPPING_DISABLED, 0); //$NON-NLS-1$ + + Composite radioGroup = new Composite(composite, SWT.NULL); + radioGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + editor = new RadioGroupFieldEditor(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, CompareMessages.ComparePreferencePage_0, 1, + new String[][] { + new String[] { CompareMessages.ComparePreferencePage_1, ICompareUIConstants.PREF_VALUE_PROMPT }, + new String[] { CompareMessages.ComparePreferencePage_2, ICompareUIConstants.PREF_VALUE_LOOP }, + new String[] { CompareMessages.ComparePreferencePage_3, ICompareUIConstants.PREF_VALUE_NEXT }, + new String[] { CompareMessages.ComparePreferencePage_4, ICompareUIConstants.PREF_VALUE_DO_NOTHING} + }, + radioGroup, true); + editor.setPreferenceStore(fOverlayStore); + editor.fillIntoGrid(radioGroup, 1); + + // a spacer + Label separator= new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); + separator.setVisible(false); + + Label previewLabel= new Label(composite, SWT.NULL); + previewLabel.setText(Utilities.getString("ComparePreferencePage.preview.label")); //$NON-NLS-1$ + + Control previewer= createPreviewer(composite); + GridData gd= new GridData(GridData.FILL_BOTH); + gd.widthHint= convertWidthInCharsToPixels(60); + gd.heightHint= convertHeightInCharsToPixels(13); + previewer.setLayoutData(gd); + + PreferenceLinkArea area = new PreferenceLinkArea(composite, SWT.NONE, + "org.eclipse.ui.preferencePages.ColorsAndFonts", Utilities.getString("ComparePreferencePage.colorAndFontLink"), //$NON-NLS-1$ //$NON-NLS-2$ + (IWorkbenchPreferenceContainer)getContainer(), "selectCategory:org.eclipse.compare.contentmergeviewer.TextMergeViewer"); //$NON-NLS-1$ + + GridData data= new GridData(SWT.FILL, SWT.CENTER, false, false); + area.getControl().setLayoutData(data); + + return composite; + } + + private Control createPreviewer(Composite parent) { + + fCompareConfiguration= new CompareConfiguration(fOverlayStore); + fCompareConfiguration.setAncestorLabel(Utilities.getString("ComparePreferencePage.ancestor.label")); //$NON-NLS-1$ + + fCompareConfiguration.setLeftLabel(Utilities.getString("ComparePreferencePage.left.label")); //$NON-NLS-1$ + fCompareConfiguration.setLeftEditable(false); + + fCompareConfiguration.setRightLabel(Utilities.getString("ComparePreferencePage.right.label")); //$NON-NLS-1$ + fCompareConfiguration.setRightEditable(false); + + fPreviewViewer= new TextMergeViewer(parent, SWT.BORDER, fCompareConfiguration); + + fPreviewViewer.setInput( + new DiffNode(Differencer.CONFLICTING, + new FakeInput("ComparePreferencePage.previewAncestor"), //$NON-NLS-1$ + new FakeInput("ComparePreferencePage.previewLeft"), //$NON-NLS-1$ + new FakeInput("ComparePreferencePage.previewRight") //$NON-NLS-1$ + ) + ); + + Control c= fPreviewViewer.getControl(); + c.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fCompareConfiguration != null) + fCompareConfiguration.dispose(); + } + }); + + return c; + } + + private void initializeFields() { + + Iterator e= fCheckBoxes.keySet().iterator(); + while (e.hasNext()) { + Button b= (Button) e.next(); + String key= (String) fCheckBoxes.get(b); + b.setSelection(fOverlayStore.getBoolean(key)); + } + + if (fFilters != null) + fFilters.setText(fOverlayStore.getString(PATH_FILTER)); + if (addedLinesRegex != null) + addedLinesRegex.setText(fOverlayStore.getString(ADDED_LINES_REGEX)); + if (removedLinesRegex != null) + removedLinesRegex.setText(fOverlayStore.getString(REMOVED_LINES_REGEX)); + + editor.load(); + } + + // overlay stuff + + private Button addCheckBox(Composite parent, String labelKey, String key, int indentation) { + + String label= Utilities.getString(labelKey); + + Button checkBox= new Button(parent, SWT.CHECK); + checkBox.setText(label); + + GridData gd= new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalIndent= indentation; + gd.horizontalSpan= 2; + checkBox.setLayoutData(gd); + + if (fCheckBoxListener == null) { + fCheckBoxListener= new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + Button button= (Button) e.widget; + fOverlayStore.setValue((String) fCheckBoxes.get(button), button.getSelection()); + } + }; + } + checkBox.addSelectionListener(fCheckBoxListener); + + fCheckBoxes.put(checkBox, key); + + return checkBox; + } + + private String loadPreviewContentFromFile(String key) { + + String preview= Utilities.getString(key); + String separator= System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuffer buffer= new StringBuffer(); + for (int i= 0; i < preview.length(); i++) { + char c= preview.charAt(i); + if (c == '\n') + buffer.append(separator); + else + buffer.append(c); + } + return buffer.toString(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java new file mode 100644 index 000000000..04bfdf3c5 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2009, 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.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareViewerSwitchingPane; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.MenuAdapter; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.PlatformUI; + +public class CompareStructureViewerSwitchingPane extends + CompareViewerSwitchingPane { + + private CompareEditorInput fCompareEditorInput; + + private ViewerDescriptor fSelectedViewerDescriptor; + + private ToolBar toolBar; + + public CompareStructureViewerSwitchingPane(Composite parent, int style, + boolean visibility, CompareEditorInput cei) { + super(parent, style, visibility); + fCompareEditorInput = cei; + } + + private CompareConfiguration getCompareConfiguration() { + return fCompareEditorInput.getCompareConfiguration(); + } + + protected Viewer getViewer(Viewer oldViewer, Object input) { + if (input instanceof ICompareInput) { + if (fSelectedViewerDescriptor != null) { + ViewerDescriptor[] array = CompareUIPlugin.getDefault().findStructureViewerDescriptor( + oldViewer, (ICompareInput)input, getCompareConfiguration()); + List list = array != null ? Arrays.asList(array) + : Collections.EMPTY_LIST; + if (list.contains(fSelectedViewerDescriptor)) { + // use selected viewer only when appropriate for the new input + fCompareEditorInput + .setStructureViewerDescriptor(fSelectedViewerDescriptor); + Viewer viewer = fCompareEditorInput.findStructureViewer( + oldViewer, (ICompareInput) input, this); + return viewer; + } + // fallback to default otherwise + fSelectedViewerDescriptor = null; + } + + fCompareEditorInput.setStructureViewerDescriptor(null); + Viewer viewer = fCompareEditorInput.findStructureViewer(oldViewer, + (ICompareInput) input, this); + fCompareEditorInput.setStructureViewerDescriptor(fSelectedViewerDescriptor); + return viewer; + } + return null; + } + + protected Control createTopLeft(Composite p) { + final Composite composite = new Composite(p, SWT.NONE) { + public Point computeSize(int wHint, int hHint, boolean changed) { + return super.computeSize(wHint, Math.max(24, hHint), changed); + } + }; + + RowLayout layout = new RowLayout(); + layout.marginTop = 0; + composite.setLayout(layout); + + CLabel cl = new CLabel(composite, SWT.NONE); + cl.setText(null); + + toolBar = new ToolBar(composite, SWT.FLAT); + toolBar.setVisible(false); // hide by default + final ToolItem toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + toolItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage( + /* IWorkbenchGraphicConstants */"IMG_LCL_VIEW_MENU")); //$NON-NLS-1$ + toolItem + .setToolTipText(CompareMessages.CompareStructureViewerSwitchingPane_switchButtonTooltip); + toolItem.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + showMenu(); + } + }); + toolBar.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent e) { + showMenu(); + } + }); + return composite; + } + + protected boolean inputChanged(Object input) { + return getInput() != input + || fCompareEditorInput.getStructureViewerDescriptor() != fSelectedViewerDescriptor; + } + + public void setInput(Object input) { + super.setInput(input); + if (getViewer() == null || !Utilities.okToUse(getViewer().getControl())) + return; + ViewerDescriptor[] vd = null; + if (getInput() instanceof ICompareInput) { + vd = CompareUIPlugin.getDefault().findStructureViewerDescriptor( + getViewer(), (ICompareInput) getInput(), + getCompareConfiguration()); + } + toolBar.setVisible(vd != null && vd.length > 1); + } + + private void showMenu() { + if (!(getInput() instanceof ICompareInput)) + return; + + ViewerDescriptor[] vd = CompareUIPlugin.getDefault() + .findStructureViewerDescriptor(getViewer(), + (ICompareInput) getInput(), getCompareConfiguration()); + + // 1. create + final Menu menu = new Menu(getShell(), SWT.POP_UP); + + // add default + String label = CompareMessages.CompareStructureViewerSwitchingPane_defaultViewer; + MenuItem defaultItem = new MenuItem(menu, SWT.RADIO); + defaultItem.setText(label); + defaultItem.addSelectionListener(createSelectionListener(null)); + defaultItem.setSelection(fSelectedViewerDescriptor == null); + + new MenuItem(menu, SWT.SEPARATOR); + + // add others + for (int i = 0; i < vd.length; i++) { + final ViewerDescriptor vdi = vd[i]; + label = vdi.getLabel(); + if (label == null || label.equals("")) { //$NON-NLS-1$ + String l = CompareUIPlugin.getDefault().findStructureTypeNameOrType((ICompareInput) getInput(), vdi, getCompareConfiguration()); + if (l == null) + // couldn't figure out the label, skip the viewer + continue; + label = NLS.bind(CompareMessages.CompareStructureViewerSwitchingPane_discoveredLabel, new Object[] {l}); + } + MenuItem item = new MenuItem(menu, SWT.RADIO); + item.setText(label); + item.addSelectionListener(createSelectionListener(vdi)); + item.setSelection(vdi == fSelectedViewerDescriptor); + } + + // 2. show + Rectangle bounds = toolBar.getItem(0).getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + + // 3. dispose on close + menu.addMenuListener(new MenuAdapter() { + public void menuHidden(MenuEvent e) { + e.display.asyncExec(new Runnable() { + public void run() { + menu.dispose(); + } + }); + } + }); + } + + private SelectionListener createSelectionListener(final ViewerDescriptor vd) { + return new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + MenuItem mi = (MenuItem) e.widget; + if (mi.getSelection()) { + Viewer oldViewer = getViewer(); + fSelectedViewerDescriptor = vd; + CompareStructureViewerSwitchingPane.this.setInput(oldViewer + .getInput()); + } + } + + public void widgetDefaultSelected(SelectionEvent e) { + // nothing to do + } + }; + } + + public void setText(String label) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) { + cl.setText(label); + c.layout(); + } + return; + } + } + } + + public void setImage(Image image) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) + cl.setImage(image); + return; + } + } + } + + public void addMouseListener(MouseListener listener) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + cl.addMouseListener(listener); + } + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java new file mode 100644 index 000000000..15815f375 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java @@ -0,0 +1,1421 @@ +/******************************************************************************* + * 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.internal; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.StringTokenizer; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.IStreamMerger; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.ComparePlugin; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IStructureCreator; +import org.eclipse.compare.structuremergeviewer.StructureDiffViewer; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorRegistry; +import org.eclipse.ui.IReusableEditor; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.IWorkbenchAdapter; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The Compare UI plug-in defines the entry point to initiate a configurable + * compare operation on arbitrary resources. The result of the compare + * is opened into a compare editor where the details can be browsed and + * edited in dynamically selected structure and content viewers. + * <p> + * The Compare UI provides a registry for content and structure compare viewers, + * which is initialized from extensions contributed to extension points + * declared by this plug-in. + * <p> + * This class is the plug-in runtime class for the + * <code>"org.eclipse.compare"</code> plug-in. + * </p> + */ +public final class CompareUIPlugin extends AbstractUIPlugin { + + static class CompareRegistry { + + private final static String ID_ATTRIBUTE= "id"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE= "extensions"; //$NON-NLS-1$ + private final static String CONTENT_TYPE_ID_ATTRIBUTE= "contentTypeId"; //$NON-NLS-1$ + + private HashMap fIdMap; // maps ids to data + private HashMap fExtensionMap; // multimap: maps extensions to list of data + private HashMap fContentTypeBindings; // multimap: maps content type bindings to list of data + + + void register(IConfigurationElement element, Object data) { + String id= element.getAttribute(ID_ATTRIBUTE); + if (id != null) { + if (fIdMap == null) + fIdMap= new HashMap(); + fIdMap.put(id, data); + } + + String types= element.getAttribute(EXTENSIONS_ATTRIBUTE); + if (types != null) { + if (fExtensionMap == null) + fExtensionMap= new HashMap(); + StringTokenizer tokenizer= new StringTokenizer(types, ","); //$NON-NLS-1$ + while (tokenizer.hasMoreElements()) { + String extension= tokenizer.nextToken().trim(); + List l = (List) fExtensionMap.get(normalizeCase(extension)); + if (l == null) + fExtensionMap.put(normalizeCase(extension), l = new ArrayList()); + l.add(data); + } + } + } + + void createBinding(IConfigurationElement element, String idAttributeName) { + String type= element.getAttribute(CONTENT_TYPE_ID_ATTRIBUTE); + String id= element.getAttribute(idAttributeName); + if (id == null) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.targetIdAttributeMissing", idAttributeName)); //$NON-NLS-1$ + if (type != null && id != null && fIdMap != null) { + Object o= fIdMap.get(id); + if (o != null) { + IContentType ct= fgContentTypeManager.getContentType(type); + if (ct != null) { + if (fContentTypeBindings == null) + fContentTypeBindings= new HashMap(); + List l = (List) fContentTypeBindings.get(ct); + if (l == null) + fContentTypeBindings.put(ct, l = new ArrayList()); + l.add(o); + } else { + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.contentTypeNotFound", type)); //$NON-NLS-1$ + } + } else { + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.targetNotFound", id)); //$NON-NLS-1$ + } + } + } + + Object search(IContentType type) { + List list = searchAll(type); + return list != null ? list.get(0) : null; + } + + List searchAll(IContentType type) { + if (fContentTypeBindings != null) { + for (; type != null; type= type.getBaseType()) { + List data= (List) fContentTypeBindings.get(type); + if (data != null) + return data; + } + } + return null; + } + + Object search(String extension) { + List list = searchAll(extension); + return list != null ? list.get(0) : null; + } + + List searchAll(String extension) { + if (fExtensionMap != null) + return (List) fExtensionMap.get(normalizeCase(extension)); + return null; + } + } + + /** Status code describing an internal error */ + public static final int INTERNAL_ERROR= 1; + + private static boolean NORMALIZE_CASE= true; + + public static final String PLUGIN_ID= "org.eclipse.compare"; //$NON-NLS-1$ + + private static final String BINARY_TYPE= "binary"; //$NON-NLS-1$ + + private static final String STREAM_MERGER_EXTENSION_POINT= "streamMergers"; //$NON-NLS-1$ + private static final String STREAM_MERGER= "streamMerger"; //$NON-NLS-1$ + private static final String STREAM_MERGER_ID_ATTRIBUTE= "streamMergerId"; //$NON-NLS-1$ + private static final String STRUCTURE_CREATOR_EXTENSION_POINT= "structureCreators"; //$NON-NLS-1$ + private static final String STRUCTURE_CREATOR= "structureCreator"; //$NON-NLS-1$ + private static final String STRUCTURE_CREATOR_ID_ATTRIBUTE= "structureCreatorId"; //$NON-NLS-1$ + + private static final String VIEWER_TAG= "viewer"; //$NON-NLS-1$ + private static final String STRUCTURE_MERGE_VIEWER_EXTENSION_POINT= "structureMergeViewers"; //$NON-NLS-1$ + private static final String STRUCTURE_MERGE_VIEWER_ID_ATTRIBUTE= "structureMergeViewerId"; //$NON-NLS-1$ + private static final String CONTENT_MERGE_VIEWER_EXTENSION_POINT= "contentMergeViewers"; //$NON-NLS-1$ + private static final String CONTENT_MERGE_VIEWER_ID_ATTRIBUTE= "contentMergeViewerId"; //$NON-NLS-1$ + private static final String CONTENT_VIEWER_EXTENSION_POINT= "contentViewers"; //$NON-NLS-1$ + private static final String CONTENT_VIEWER_ID_ATTRIBUTE= "contentViewerId"; //$NON-NLS-1$ + + private static final String CONTENT_TYPE_BINDING= "contentTypeBinding"; //$NON-NLS-1$ + + + private static final String COMPARE_EDITOR= PLUGIN_ID + ".CompareEditor"; //$NON-NLS-1$ + + private static final String STRUCTUREVIEWER_ALIASES_PREFERENCE_NAME= "StructureViewerAliases"; //$NON-NLS-1$ + + // content type + private static final IContentTypeManager fgContentTypeManager= Platform.getContentTypeManager(); + + public static final int NO_DIFFERENCE = 10000; + + /** + * The plugin singleton. + */ + private static CompareUIPlugin fgComparePlugin; + + /** Maps type to icons */ + private static Map fgImages= new Hashtable(10); + /** Maps type to ImageDescriptors */ + private static Map fgImageDescriptors= new Hashtable(10); + /** Maps ImageDescriptors to Images */ + private static Map fgImages2= new Hashtable(10); + + private static List fgDisposeOnShutdownImages= new ArrayList(); + + private ResourceBundle fResourceBundle; + + private boolean fRegistriesInitialized; + private CompareRegistry fStreamMergers= new CompareRegistry(); + private CompareRegistry fStructureCreators= new CompareRegistry(); + private CompareRegistry fStructureMergeViewers= new CompareRegistry(); + private CompareRegistry fContentViewers= new CompareRegistry(); + private CompareRegistry fContentMergeViewers= new CompareRegistry(); + + private Map fStructureViewerAliases; + private CompareFilter fFilter; + private IPropertyChangeListener fPropertyChangeListener; + + /** + * Creates the <code>CompareUIPlugin</code> object and registers all + * structure creators, content merge viewers, and structure merge viewers + * contributed to this plug-in's extension points. + * <p> + * Note that instances of plug-in runtime classes are automatically created + * by the platform in the course of plug-in activation. + */ + public CompareUIPlugin() { + super(); + Assert.isTrue(fgComparePlugin == null); + fgComparePlugin= this; + } + + public void start(BundleContext context) throws Exception { + super.start(context); + + ComparePlugin.getDefault().setCappingDisabled( + getPreferenceStore().getBoolean( + ComparePreferencePage.CAPPING_DISABLED)); + } + + public void stop(BundleContext context) throws Exception { + + IPreferenceStore ps= getPreferenceStore(); + rememberAliases(ps); + if (fPropertyChangeListener != null) { + ps.removePropertyChangeListener(fPropertyChangeListener); + fPropertyChangeListener= null; + } + + super.stop(context); + + if (fgDisposeOnShutdownImages != null) { + Iterator i= fgDisposeOnShutdownImages.iterator(); + while (i.hasNext()) { + Image img= (Image) i.next(); + if (!img.isDisposed()) + img.dispose(); + } + fgImages= null; + } + } + + /** + * Returns the singleton instance of this plug-in runtime class. + * + * @return the compare plug-in instance + */ + public static CompareUIPlugin getDefault() { + return fgComparePlugin; + } + + /** + * Returns this plug-in's resource bundle. + * + * @return the plugin's resource bundle + */ + public ResourceBundle getResourceBundle() { + if (fResourceBundle == null) + fResourceBundle= Platform.getResourceBundle(getBundle()); + return fResourceBundle; + } + + /** + * Returns this plug-in's unique identifier. + * + * @return the plugin's unique identifier + */ + public static String getPluginId() { + return getDefault().getBundle().getSymbolicName(); + } + + private void initializeRegistries() { + if (!fRegistriesInitialized) { + registerExtensions(); + fRegistriesInitialized= true; + } + } + + /** + * Registers all stream mergers, structure creators, content merge viewers, and structure merge viewers + * that are found in the XML plugin files. + */ + private void registerExtensions() { + IExtensionRegistry registry= Platform.getExtensionRegistry(); + + // collect all IStreamMergers + IConfigurationElement[] elements= registry.getConfigurationElementsFor(PLUGIN_ID, STREAM_MERGER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (STREAM_MERGER.equals(element.getName())) + fStreamMergers.register(element, new StreamMergerDescriptor(element)); + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fStreamMergers.createBinding(element, STREAM_MERGER_ID_ATTRIBUTE); + } + + // collect all IStructureCreators + elements= registry.getConfigurationElementsFor(PLUGIN_ID, STRUCTURE_CREATOR_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!STRUCTURE_CREATOR.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, STRUCTURE_CREATOR)); //$NON-NLS-1$ + fStructureCreators.register(element, new StructureCreatorDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fStructureCreators.createBinding(element, STRUCTURE_CREATOR_ID_ATTRIBUTE); + } + + // collect all viewers which define the structure merge viewer extension point + elements= registry.getConfigurationElementsFor(PLUGIN_ID, STRUCTURE_MERGE_VIEWER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + fStructureMergeViewers.register(element, new ViewerDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fStructureMergeViewers.createBinding(element, STRUCTURE_MERGE_VIEWER_ID_ATTRIBUTE); + } + + // collect all viewers which define the content merge viewer extension point + elements= registry.getConfigurationElementsFor(PLUGIN_ID, CONTENT_MERGE_VIEWER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + fContentMergeViewers.register(element, new ViewerDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fContentMergeViewers.createBinding(element, CONTENT_MERGE_VIEWER_ID_ATTRIBUTE); + } + + // collect all viewers which define the content viewer extension point + elements= registry.getConfigurationElementsFor(PLUGIN_ID, CONTENT_VIEWER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + fContentViewers.register(element, new ViewerDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fContentViewers.createBinding(element, CONTENT_VIEWER_ID_ATTRIBUTE); + } + } + + public static IWorkbench getActiveWorkbench() { + CompareUIPlugin plugin= getDefault(); + if (plugin == null) + return null; + return plugin.getWorkbench(); + } + + public static IWorkbenchWindow getActiveWorkbenchWindow() { + IWorkbench workbench= getActiveWorkbench(); + if (workbench == null) + return null; + return workbench.getActiveWorkbenchWindow(); + } + + /** + * Returns the active workbench page or <code>null</code> if + * no active workbench page can be determined. + * + * @return the active workbench page or <code>null</code> if + * no active workbench page can be determined + */ + private static IWorkbenchPage getActivePage() { + IWorkbenchWindow window= getActiveWorkbenchWindow(); + if (window == null) + return null; + return window.getActivePage(); + } + + /** + * Returns the SWT Shell of the active workbench window or <code>null</code> if + * no workbench window is active. + * + * @return the SWT Shell of the active workbench window, or <code>null</code> if + * no workbench window is active + */ + public static Shell getShell() { + IWorkbenchWindow window= getActiveWorkbenchWindow(); + if (window == null) + return null; + return window.getShell(); + } + + /** + * Registers the given image for being disposed when this plug-in is shutdown. + * + * @param image the image to register for disposal + */ + public static void disposeOnShutdown(Image image) { + if (image != null) + fgDisposeOnShutdownImages.add(image); + } + + /** + * Performs the comparison described by the given input and opens a compare + * editor on the result. + * + * @param input + * the input on which to open the compare editor + * @param page + * the workbench page on which to create a new compare editor + * @param editor + * if not null the input is opened in this editor + * @param activate + * if <code>true</code> the editor will be activated + * @see IWorkbenchPage#openEditor(org.eclipse.ui.IEditorInput, String, + * boolean) + * @see CompareEditorInput + */ + public void openCompareEditor(final CompareEditorInput input, + final IWorkbenchPage page, final IReusableEditor editor, + final boolean activate) { + CompareConfiguration configuration = input.getCompareConfiguration(); + if (configuration != null) { + IPreferenceStore ps= configuration.getPreferenceStore(); + if (ps != null) + configuration.setProperty( + CompareConfiguration.USE_OUTLINE_VIEW, + Boolean.valueOf(ps.getBoolean(ComparePreferencePage.USE_OUTLINE_VIEW))); + } + if (input.canRunAsJob()) { + openEditorInBackground(input, page, editor, activate); + } else { + if (compareResultOK(input, null)) { + internalOpenEditor(input, page, editor, activate); + } + } + } + + private void openEditorInBackground(final CompareEditorInput input, + final IWorkbenchPage page, final IReusableEditor editor, + final boolean activate) { + internalOpenEditor(input, page, editor, activate); + } + + private void internalOpenEditor(final CompareEditorInput input, + final IWorkbenchPage wp, final IReusableEditor editor, + final boolean activate) { + Runnable runnable = new Runnable() { + public void run() { + if (editor != null && !editor.getSite().getShell().isDisposed()) { // reuse the given editor + editor.setInput(input); + return; + } + + IWorkbenchPage page = wp; + if (page == null) + page= getActivePage(); + if (page != null) { + // open new CompareEditor on page + try { + page.openEditor(input, COMPARE_EDITOR, activate); + } catch (PartInitException e) { + MessageDialog.openError(getShell(), Utilities.getString("CompareUIPlugin.openEditorError"), e.getMessage()); //$NON-NLS-1$ + } + } else { + MessageDialog.openError(getShell(), + Utilities.getString("CompareUIPlugin.openEditorError"), //$NON-NLS-1$ + Utilities.getString("CompareUIPlugin.noActiveWorkbenchPage")); //$NON-NLS-1$ + } + } + }; + syncExec(runnable); + } + + /** + * Performs the comparison described by the given input and opens a + * compare dialog on the result. + * + * @param input the input on which to open the compare editor + * @see CompareEditorInput + */ + public void openCompareDialog(final CompareEditorInput input) { + // We don't ever open dialogs in the background + if (compareResultOK(input, null)) { + internalOpenDialog(input); + } + } + + public IStatus prepareInput(CompareEditorInput input, IProgressMonitor monitor) { + try { + input.run(monitor); + String message= input.getMessage(); + if (message != null) { + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, message, null); + } + if (input.getCompareResult() == null) { + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, NO_DIFFERENCE, Utilities.getString("CompareUIPlugin.noDifferences"), null); //$NON-NLS-1$ + } + return Status.OK_STATUS; + } catch (InterruptedException e) { + throw new OperationCanceledException(); + } catch (InvocationTargetException e) { + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, Utilities.getString("CompareUIPlugin.compareFailed"), e.getTargetException()); //$NON-NLS-1$ + } + } + + /* + * @return <code>true</code> if compare result is OK to show, <code>false</code> otherwise + */ + public boolean compareResultOK(CompareEditorInput input, IRunnableContext context) { + final Shell shell= getShell(); + try { + + // run operation in separate thread and make it cancelable + if (context == null) + context = PlatformUI.getWorkbench().getProgressService(); + context.run(true, true, input); + + String message= input.getMessage(); + if (message != null) { + MessageDialog.openError(shell, Utilities.getString("CompareUIPlugin.compareFailed"), message); //$NON-NLS-1$ + return false; + } + + if (input.getCompareResult() == null) { + MessageDialog.openInformation(shell, Utilities.getString("CompareUIPlugin.dialogTitle"), Utilities.getString("CompareUIPlugin.noDifferences")); //$NON-NLS-2$ //$NON-NLS-1$ + return false; + } + + return true; + + } catch (InterruptedException x) { + // canceled by user + } catch (InvocationTargetException x) { + MessageDialog.openError(shell, Utilities.getString("CompareUIPlugin.compareFailed"), x.getTargetException().getMessage()); //$NON-NLS-1$ + } + return false; + } + + /* + * Registers an image for the given type. + */ + private static void registerImage(String type, Image image, boolean dispose) { + fgImages.put(normalizeCase(type), image); + if (image != null && dispose) { + fgDisposeOnShutdownImages.add(image); + } + } + + /** + * Registers an image descriptor for the given type. + * + * @param type the type + * @param descriptor the image descriptor + */ + public static void registerImageDescriptor(String type, ImageDescriptor descriptor) { + fgImageDescriptors.put(normalizeCase(type), descriptor); + } + + public static ImageDescriptor getImageDescriptor(String relativePath) { + if (fgComparePlugin == null) + return null; + IPath path= Utilities.getIconPath(null).append(relativePath); + URL url= FileLocator.find(fgComparePlugin.getBundle(), path, null); + if (url == null) + return null; + return ImageDescriptor.createFromURL(url); + } + + /** + * Returns a shared image for the given type, or a generic image if none + * has been registered for the given type. + * <p> + * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + * </p> + * + * @param type the type + * @return the image + */ + public static Image getImage(String type) { + + type= normalizeCase(type); + + boolean dispose= false; + Image image= null; + if (type != null) + image= (Image) fgImages.get(type); + if (image == null) { + ImageDescriptor id= (ImageDescriptor) fgImageDescriptors.get(type); + if (id != null) { + image= id.createImage(); + dispose= true; + } + + if (image == null) { + if (fgComparePlugin != null) { + if (ITypedElement.FOLDER_TYPE.equals(type)) { + image= getDefault().getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); + //image= SharedImages.getImage(ISharedImages.IMG_OBJ_FOLDER); + } else { + image= createWorkbenchImage(type); + dispose= true; + } + } else { + id= (ImageDescriptor) fgImageDescriptors.get(normalizeCase("file")); //$NON-NLS-1$ + image= id.createImage(); + dispose= true; + } + } + if (image != null) + registerImage(type, image, dispose); + } + return image; + } + + /** + * Returns a shared image for the given adaptable. + * This convenience method queries the given adaptable + * for its <code>IWorkbenchAdapter.getImageDescriptor</code>, which it + * uses to create an image if it does not already have one. + * <p> + * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + * </p> + * + * @param adaptable the adaptable for which to find an image + * @return an image + */ + public static Image getImage(IAdaptable adaptable) { + if (adaptable != null) { + Object o= adaptable.getAdapter(IWorkbenchAdapter.class); + if (o instanceof IWorkbenchAdapter) { + ImageDescriptor id= ((IWorkbenchAdapter) o).getImageDescriptor(adaptable); + if (id != null) { + Image image= (Image)fgImages2.get(id); + if (image == null) { + image= id.createImage(); + try { + fgImages2.put(id, image); + } catch (NullPointerException ex) { + // NeedWork + } + fgDisposeOnShutdownImages.add(image); + + } + return image; + } + } + } + return null; + } + + private static Image createWorkbenchImage(String type) { + IEditorRegistry er= getDefault().getWorkbench().getEditorRegistry(); + ImageDescriptor id= er.getImageDescriptor("foo." + type); //$NON-NLS-1$ + return id.createImage(); + } + + /** + * Returns an structure creator descriptor for the given type. + * + * @param type the type for which to find a descriptor + * @return a descriptor for the given type, or <code>null</code> if no + * descriptor has been registered + */ + public StructureCreatorDescriptor getStructureCreator(String type) { + initializeRegistries(); + return (StructureCreatorDescriptor) fStructureCreators.search(type); + } + + /** + * Returns a stream merger for the given type. + * + * @param type the type for which to find a stream merger + * @return a stream merger for the given type, or <code>null</code> if no + * stream merger has been registered + */ + public IStreamMerger createStreamMerger(String type) { + initializeRegistries(); + StreamMergerDescriptor descriptor= (StreamMergerDescriptor) fStreamMergers.search(type); + if (descriptor != null) + return descriptor.createStreamMerger(); + return null; + } + + /** + * Returns a stream merger for the given content type. + * + * @param type the type for which to find a stream merger + * @return a stream merger for the given type, or <code>null</code> if no + * stream merger has been registered + */ + public IStreamMerger createStreamMerger(IContentType type) { + initializeRegistries(); + StreamMergerDescriptor descriptor= (StreamMergerDescriptor) fStreamMergers.search(type); + if (descriptor != null) + return descriptor.createStreamMerger(); + return null; + } + + public ViewerDescriptor[] findStructureViewerDescriptor(Viewer oldViewer, + ICompareInput input, CompareConfiguration configuration) { + if (input == null) + return null; + // we don't show the structure of additions or deletions + if (input == null || input.getLeft() == null || input.getRight() == null) + return null; + + Set result = new LinkedHashSet(); + + // content type search + IContentType ctype= getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(ctype); + if (list != null) + result.addAll(list); + } + + // old style search + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) { + type= normalizeCase(types[0]); + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(type); + if (list != null) + result.addAll(list); + String alias= getStructureViewerAlias(type); + if (alias != null) { + list = fStructureMergeViewers.searchAll(alias); + if (list != null) + result.addAll(list); + } + } + + return result.size() > 0 ? (ViewerDescriptor[]) result + .toArray(new ViewerDescriptor[0]) : null; + } + + /** + * Returns a structure compare viewer based on an old viewer and an input object. + * If the old viewer is suitable for showing the input, the old viewer + * is returned. Otherwise, the input's type is used to find a viewer descriptor in the registry + * which in turn is used to create a structure compare viewer under the given parent composite. + * If no viewer descriptor can be found <code>null</code> is returned. + * + * @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 + * @param configuration a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent, + CompareConfiguration configuration) { + ViewerDescriptor[] descriptors = findStructureViewerDescriptor(oldViewer, input, configuration); + if (descriptors == null || descriptors.length == 0) { + // we didn't found any viewer so far. + // now we try to find a structure creator for the generic StructureDiffViewer + IContentType ctype= getCommonType(input); + + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) { + type= normalizeCase(types[0]); + } + + StructureCreatorDescriptor scc= null; + initializeRegistries(); + Object desc= fStructureCreators.search(ctype); // search for content type + if (desc instanceof StructureCreatorDescriptor) + scc= (StructureCreatorDescriptor) desc; + if (scc == null && type != null) + scc= getStructureCreator(type); // search for old-style type scheme + if (scc != null) { + IStructureCreator sc= scc.createStructureCreator(); + if (sc != null) { + StructureDiffViewer sdv= new StructureDiffViewer(parent, configuration); + sdv.setStructureCreator(sc); + return sdv; + } + } + return null; + } + return getViewer(descriptors[0], oldViewer, parent, configuration); + } + + public ViewerDescriptor[] findContentViewerDescriptor(Viewer oldViewer, Object in, CompareConfiguration cc) { + Set result = new LinkedHashSet(); + if (in instanceof IStreamContentAccessor) { + String type= ITypedElement.TEXT_TYPE; + + if (in instanceof ITypedElement) { + ITypedElement tin= (ITypedElement) in; + + IContentType ct= getContentType(tin); + if (ct != null) { + initializeRegistries(); + List list = fContentViewers.searchAll(ct); + if (list != null) + result.addAll(list); + } + + String ty= tin.getType(); + if (ty != null) + type= ty; + } + + initializeRegistries(); + List list = fContentViewers.searchAll(type); + if (list != null) + result.addAll(list); + // fallback + result.add(fContentViewers.search(Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT))); + return (ViewerDescriptor[]) result.toArray(new ViewerDescriptor[0]); + } + + if (!(in instanceof ICompareInput)) + return null; + + ICompareInput input= (ICompareInput) in; + + IContentType ctype = getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(ctype); + if (list != null) + result.addAll(list); + } + + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) + type= types[0]; + + if (ITypedElement.FOLDER_TYPE.equals(type)) + return null; + + if (type == null) { + int n= 0; + for (int i= 0; i < types.length; i++) + if (!ITypedElement.UNKNOWN_TYPE.equals(types[i])) { + n++; + if (type == null) + type= types[i]; // remember the first known type + } + if (n > 1) // don't use the type if there were more than one + type= null; + } + + if (type != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(type); + if (list != null) + result.addAll(list); + } + + // fallback + String leftType= guessType(input.getLeft()); + String rightType= guessType(input.getRight()); + + if (leftType != null || rightType != null) { + boolean right_text = rightType != null + && ITypedElement.TEXT_TYPE.equals(rightType); + boolean left_text = leftType != null + && ITypedElement.TEXT_TYPE.equals(leftType); + initializeRegistries(); + if ((rightType != null && !right_text) + || (leftType != null && !left_text)) { + List list = fContentMergeViewers.searchAll(BINARY_TYPE); + if (list != null) + result.addAll(list); + } + List list = fContentMergeViewers.searchAll(ITypedElement.TEXT_TYPE); + if (list != null) + result.addAll(list); + + return (ViewerDescriptor[]) result.toArray(new ViewerDescriptor[0]); + } + return result.size() > 0 ? (ViewerDescriptor[])result.toArray(new ViewerDescriptor[0]) : null; + } + + /** + * Returns a content compare viewer based on an old viewer and an input object. + * If the old viewer is suitable for showing the input the old viewer + * is returned. Otherwise the input's type is used to find a viewer descriptor in the registry + * which in turn is used to create a content compare viewer under the given parent composite. + * If no viewer descriptor can be found <code>null</code> is returned. + * + * @param oldViewer a new viewer is only created if this old viewer cannot show the given input + * @param in the input object for which to find a content viewer + * @param parent the SWT parent composite under which the new viewer is created + * @param cc a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public Viewer findContentViewer(Viewer oldViewer, Object in, + Composite parent, CompareConfiguration cc) { + ViewerDescriptor[] descriptors = findContentViewerDescriptor(oldViewer, in, cc); + return getViewer(descriptors != null ? descriptors[0] : null, oldViewer, parent, cc); + } + + private static Viewer getViewer(Object descriptor, Viewer oldViewer, Composite parent, CompareConfiguration cc) { + if (descriptor instanceof IViewerDescriptor) + return ((IViewerDescriptor)descriptor).createViewer(oldViewer, parent, cc); + return null; + } + + private static String[] getTypes(ICompareInput input) { + ITypedElement ancestor= input.getAncestor(); + ITypedElement left= input.getLeft(); + ITypedElement right= input.getRight(); + + ArrayList tmp= new ArrayList(); + if (ancestor != null) { + String type= ancestor.getType(); + if (type != null) + tmp.add(normalizeCase(type)); + } + if (left != null) { + String type= left.getType(); + if (type != null) + tmp.add(normalizeCase(type)); + } + if (right != null) { + String type= right.getType(); + if (type != null) + tmp.add(normalizeCase(type)); + } + return (String[]) tmp.toArray(new String[tmp.size()]); + } + + private static IContentType getContentType(ITypedElement element) { + if (element == null) + return null; + String name= element.getName(); + IContentType ct= null; + if (element instanceof IStreamContentAccessor) { + IStreamContentAccessor isa= (IStreamContentAccessor) element; + try { + InputStream is= isa.getContents(); + if (is != null) { + InputStream bis= new BufferedInputStream(is); + try { + ct= fgContentTypeManager.findContentTypeFor(is, name); + } catch (IOException e) { + // silently ignored + } finally { + try { + bis.close(); + } catch (IOException e2) { + // silently ignored + } + } + } + } catch (CoreException e1) { + // silently ignored + } + } + if (ct == null) + ct= fgContentTypeManager.findContentTypeFor(name); + return ct; + } + + /* + * Returns true if the given types are homogeneous. + */ + private static boolean isHomogenous(String[] types) { + switch (types.length) { + case 1: + return true; + case 2: + return types[0].equals(types[1]); + case 3: + return types[0].equals(types[1]) && types[1].equals(types[2]); + } + return false; + } + + /* + * Returns the most specific content type that is common to the given inputs or null. + */ + private static IContentType getCommonType(ICompareInput input) { + + ITypedElement ancestor= input.getAncestor(); + ITypedElement left= input.getLeft(); + ITypedElement right= input.getRight(); + + int n= 0; + IContentType[] types= new IContentType[3]; + IContentType type= null; + + if (ancestor != null) { + type= getContentType(ancestor); + if (type != null) + types[n++]= type; + } + type= getContentType(left); + if (type != null) + types[n++]= type; + else + return null; + type= getContentType(right); + if (type != null) + types[n++]= type; + else + return null; + + IContentType result= null; + IContentType[] s0, s1, s2; + switch (n) { + case 0: + return null; + case 1: + return types[0]; + case 2: + if (types[0].equals(types[1])) + return types[0]; + s0= toFullPath(types[0]); + s1= toFullPath(types[1]); + for (int i= 0; i < Math.min(s0.length, s1.length); i++) { + if (!s0[i].equals(s1[i])) + break; + result= s0[i]; + } + return result; + case 3: + if (types[0].equals(types[1]) && types[1].equals(types[2])) + return types[0]; + s0= toFullPath(types[0]); + s1= toFullPath(types[1]); + s2= toFullPath(types[2]); + for (int i= 0; i < Math.min(Math.min(s0.length, s1.length), s2.length); i++) { + if (!s0[i].equals(s1[i]) || !s1[i].equals(s2[i])) + break; + result= s0[i]; + } + return result; + } + return null; + } + + private static IContentType[] toFullPath(IContentType ct) { + List l= new ArrayList(); + for (; ct != null; ct= ct.getBaseType()) + l.add(0, ct); + return (IContentType[]) l.toArray(new IContentType[l.size()]); + } + + /* + * Guesses the file type of the given input. + * Returns ITypedElement.TEXT_TYPE if none of the first 10 lines is longer than 1000 bytes. + * Returns ITypedElement.UNKNOWN_TYPE otherwise. + * Returns <code>null</code> if the input isn't an <code>IStreamContentAccessor</code>. + */ + private static String guessType(ITypedElement input) { + if (input instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) input; + InputStream is= null; + try { + is= sca.getContents(); + if (is == null) + return null; + int lineLength= 0; + int lines= 0; + while (lines < 10) { + int c= is.read(); + if (c == -1) // EOF + break; + if (c == '\n' || c == '\r') { // reset line length + lineLength= 0; + lines++; + } else + lineLength++; + if (lineLength > 1000) + return ITypedElement.UNKNOWN_TYPE; + } + return ITypedElement.TEXT_TYPE; + } catch (CoreException ex) { + // be silent and return UNKNOWN_TYPE + } catch (IOException ex) { + // be silent and return UNKNOWN_TYPE + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + return ITypedElement.UNKNOWN_TYPE; + } + return null; + } + + private static String normalizeCase(String s) { + if (NORMALIZE_CASE && s != null) + return s.toUpperCase(); + return s; + } + + //---- alias management + + private String getStructureViewerAlias(String type) { + return (String) getStructureViewerAliases().get(type); + } + + public void addStructureViewerAlias(String type, String alias) { + getStructureViewerAliases().put(normalizeCase(alias), normalizeCase(type)); + } + + private Map getStructureViewerAliases() { + if (fStructureViewerAliases == null) { + fStructureViewerAliases= new Hashtable(10); + String aliases= getPreferenceStore().getString(STRUCTUREVIEWER_ALIASES_PREFERENCE_NAME); + if (aliases != null && aliases.length() > 0) { + StringTokenizer st= new StringTokenizer(aliases, " "); //$NON-NLS-1$ + while (st.hasMoreTokens()) { + String pair= st.nextToken(); + int pos= pair.indexOf('.'); + if (pos > 0) { + String key= pair.substring(0, pos); + String alias= pair.substring(pos+1); + fStructureViewerAliases.put(key, alias); + } + } + } + } + return fStructureViewerAliases; + } + + public void removeAllStructureViewerAliases(String type) { + if (fStructureViewerAliases == null) + return; + String t= normalizeCase(type); + Set entrySet= fStructureViewerAliases.entrySet(); + for (Iterator iter= entrySet.iterator(); iter.hasNext(); ) { + Map.Entry entry= (Map.Entry)iter.next(); + if (entry.getValue().equals(t)) + iter.remove(); + } + } + + /* + * Converts the aliases into a single string before they are stored + * in the preference store. + * The format is: + * <key> '.' <alias> ' ' <key> '.' <alias> ... + */ + private void rememberAliases(IPreferenceStore ps) { + if (fStructureViewerAliases == null) + return; + StringBuffer buffer= new StringBuffer(); + Iterator iter= fStructureViewerAliases.keySet().iterator(); + while (iter.hasNext()) { + String key= (String) iter.next(); + String alias= (String) fStructureViewerAliases.get(key); + buffer.append(key); + buffer.append('.'); + buffer.append(alias); + buffer.append(' '); + } + ps.setValue(STRUCTUREVIEWER_ALIASES_PREFERENCE_NAME, buffer.toString()); + } + + //---- filters + + public boolean filter(String name, boolean isFolder, boolean isArchive) { + if (fFilter == null) { + fFilter= new CompareFilter(); + final IPreferenceStore ps= getPreferenceStore(); + fFilter.setFilters(ps.getString(ComparePreferencePage.PATH_FILTER)); + fPropertyChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + if (ComparePreferencePage.PATH_FILTER.equals(event.getProperty())) + fFilter.setFilters(ps.getString(ComparePreferencePage.PATH_FILTER)); + } + }; + ps.addPropertyChangeListener(fPropertyChangeListener); + } + return fFilter.filter(name, isFolder, isArchive); + } + + private void internalOpenDialog(final CompareEditorInput input) { + Runnable runnable = new Runnable() { + public void run() { + CompareDialog dialog= new CompareDialog(getShell(), input); + dialog.open(); + } + }; + syncExec(runnable); + } + + private void syncExec(Runnable runnable) { + if (Display.getCurrent() == null) { + Display.getDefault().syncExec(runnable); + } else { + runnable.run(); + } + } + + //---- more utilities + + protected void handleNoDifference() { + Runnable runnable = new Runnable() { + public void run() { + MessageDialog.openInformation(getShell(), Utilities.getString("CompareUIPlugin.dialogTitle"), Utilities.getString("CompareUIPlugin.noDifferences")); //$NON-NLS-1$//$NON-NLS-2$ + } + }; + syncExec(runnable); + } + + /** + * Returns an array of all editors that have an unsaved content. If the identical content is + * presented in more than one editor, only one of those editor parts is part of the result. + * + * @return an array of all dirty editor parts. + */ + public static IEditorPart[] getDirtyEditors() { + Set inputs= new HashSet(); + List result= new ArrayList(0); + IWorkbench workbench= getDefault().getWorkbench(); + IWorkbenchWindow[] windows= workbench.getWorkbenchWindows(); + for (int i= 0; i < windows.length; i++) { + IWorkbenchPage[] pages= windows[i].getPages(); + for (int x= 0; x < pages.length; x++) { + IEditorPart[] editors= pages[x].getDirtyEditors(); + for (int z= 0; z < editors.length; z++) { + IEditorPart ep= editors[z]; + IEditorInput input= ep.getEditorInput(); + if (!inputs.contains(input)) { + inputs.add(input); + result.add(ep); + } + } + } + } + return (IEditorPart[])result.toArray(new IEditorPart[result.size()]); + } + + public static void logErrorMessage(String message) { + if (message == null) + message= ""; //$NON-NLS-1$ + log(new Status(IStatus.ERROR, getPluginId(), INTERNAL_ERROR, message, null)); + } + + public static void log(Throwable e) { + log(new Status(IStatus.ERROR, getPluginId(), INTERNAL_ERROR, CompareMessages.ComparePlugin_internal_error, e)); + } + + public static void log(IStatus status) { + getDefault().getLog().log(status); + } + + String findContentTypeNameOrType(ICompareInput input, ViewerDescriptor vd, CompareConfiguration cc) { + IContentType ctype= getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(ctype); + if (list != null) + if (list.contains(vd)) + return ctype.getName(); + } + + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) + type= types[0]; + + if (ITypedElement.FOLDER_TYPE.equals(type)) + return null; + + if (type == null) { + int n= 0; + for (int i= 0; i < types.length; i++) + if (!ITypedElement.UNKNOWN_TYPE.equals(types[i])) { + n++; + if (type == null) + type= types[i]; // remember the first known type + } + if (n > 1) // don't use the type if there were more than one + type= null; + } + + if (type != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(type); + if (list != null) + if (list.contains(vd)) + return type; + } + + // fallback + String leftType= guessType(input.getLeft()); + String rightType= guessType(input.getRight()); + + if (leftType != null || rightType != null) { + boolean right_text = rightType != null + && ITypedElement.TEXT_TYPE.equals(rightType); + boolean left_text = leftType != null + && ITypedElement.TEXT_TYPE.equals(leftType); + initializeRegistries(); + if ((rightType != null && !right_text) + || (leftType != null && !left_text)) { + List list = fContentMergeViewers.searchAll(BINARY_TYPE); + if (list != null) + if (list.contains(vd)) + return type; + } + List list = fContentMergeViewers.searchAll(ITypedElement.TEXT_TYPE); + if (list != null) + if (list.contains(vd)) + return type; + } + return null; + } + + String findStructureTypeNameOrType(ICompareInput input, ViewerDescriptor vd, CompareConfiguration cc) { + if (input == null) + return null; + // we don't show the structure of additions or deletions + if (input == null || input.getLeft() == null || input.getRight() == null) + return null; + + // content type search + IContentType ctype= getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(ctype); + if (list != null) + if (list.contains(vd)) + return ctype.getName(); + } + + // old style search + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) { + type= normalizeCase(types[0]); + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(type); + if (list != null) + if (list.contains(vd)) + return type; + String alias= getStructureViewerAlias(type); + if (alias != null) { + list = fStructureMergeViewers.searchAll(alias); + if (list != null) + if (list.contains(vd)) + return alias; + } + } + + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java new file mode 100644 index 000000000..771aef90e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +public class CompareWithEditionAction extends EditionAction { + + public CompareWithEditionAction() { + super(false, "org.eclipse.compare.internal.CompareWithEditionAction"); //$NON-NLS-1$ + this.fHelpContextId= ICompareContextIds.COMPARE_WITH_EDITION_DIALOG; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties new file mode 100644 index 000000000..ad6aec79f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties @@ -0,0 +1,38 @@ +############################################################################### +# Copyright (c) 2000, 2006 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 +############################################################################### + +# @(#)CompareWithEditionAction.properties +# +# Resources for CompareWithEditionAction.java + +title= Compare with Local History + +treeTitleFormat= Local History of ''{0}'' +dateIcon= obj16/day_obj.gif +timeIcon= obj16/resource_obj.gif + +treeFormat= {0} +workspaceTreeFormat= {0} (Workspace File) +parseErrorFormat= {0} (Parse Error) + +editionLabel= Local History ({0}) +workspaceEditionLabel= Workspace File + +targetLabel= Editor Buffer +workspaceTargetLabel= Workspace File + +todayFormat= Today ({0}) +yesterdayFormat= Yesterday ({0}) +dayFormat= {0} + +closeButton.label=Close + +noLocalHistoryError= No local history available for selected resource. diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java new file mode 100644 index 000000000..e6899c1bf --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Aleksandra Wozniak 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: + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - initial implementation + * IBM Corporation - maintenance + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; + +/** + * The "Compare with other resource" action. + * + * @deprecated Temporarily replaced by CompareWithOtherResourceHandler. See bug + * 264498. + */ +public class CompareWithOtherResourceAction extends CompareAction { + + public void run(ISelection selection) { + // Show CompareWithOtherResourceDialog which return resources to compare + // and ancestor if specified. Don't need to display the other dialog + showSelectAncestorDialog = false; + super.run(selection); + } + + protected boolean isEnabled(ISelection selection) { + int selectionSize = 0; + if (selection instanceof IStructuredSelection) { + selectionSize = ((IStructuredSelection) selection).toArray().length; + } + // enable for a single selection + return super.isEnabled(selection) || selectionSize == 1; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java new file mode 100644 index 000000000..b5debc26f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java @@ -0,0 +1,838 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Aleksandra Wozniak 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: + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - initial implementation + * IBM Corporation - Bug 73923 (major refactoring and adjustments) + * IBM Corporation - Bug 241649 - [Dialogs] Resizing of the "compare with other" dialog + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.FileOutputStream; +import java.io.IOException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DragSource; +import org.eclipse.swt.dnd.DragSourceEvent; +import org.eclipse.swt.dnd.DragSourceListener; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.DropTargetListener; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.DirectoryDialog; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.events.ExpansionAdapter; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.widgets.ExpandableComposite; +import org.eclipse.ui.part.ResourceTransfer; + +/** + * This is a dialog that can invoke the compare editor on chosen files. + */ +public class CompareWithOtherResourceDialog extends TitleAreaDialog { + + private int MIN_WIDTH = 320; + private int MIN_HEIGHT_WITH_ANCESTOR = 320; + private int MIN_HEIGHT_WITHOUT_ANCESTOR = 238; + + private class FileTextDragListener implements DragSourceListener { + + private ContentTypeElement element; + + public FileTextDragListener(ContentTypeElement element) { + this.element = element; + } + + public void dragFinished(DragSourceEvent event) { + element.setText(""); //$NON-NLS-1$ + } + + public void dragSetData(DragSourceEvent event) { + event.data = element.getText(); + } + + public void dragStart(DragSourceEvent event) { + if (element.getText() == null) + event.doit = false; + } + } + + private class FileTextDropListener implements DropTargetListener { + + private ContentTypeElement element; + private ResourceTransfer resourceTransfer; + private TextTransfer textTransfer; + + public FileTextDropListener(ContentTypeElement element) { + this.element = element; + resourceTransfer = ResourceTransfer.getInstance(); + textTransfer = TextTransfer.getInstance(); + } + + public void dragEnter(DropTargetEvent event) { + + if (event.detail == DND.DROP_DEFAULT) { + if ((event.operations & DND.DROP_COPY) != 0) + event.detail = DND.DROP_COPY; + else + event.detail = DND.DROP_NONE; + } + + for (int i = 0; i < event.dataTypes.length; i++) { + if (resourceTransfer.isSupportedType(event.dataTypes[i]) + || textTransfer.isSupportedType(event.dataTypes[i])) { + event.currentDataType = event.dataTypes[i]; + if (event.detail != DND.DROP_COPY) + event.detail = DND.DROP_NONE; + break; + } + } + } + + public void dragLeave(DropTargetEvent event) { + // intentionally empty + } + + public void dragOperationChanged(DropTargetEvent event) { + + if (event.detail == DND.DROP_DEFAULT) { + if ((event.operations & DND.DROP_COPY) != 0) + event.detail = DND.DROP_COPY; + else + event.detail = DND.DROP_NONE; + } else if (resourceTransfer.isSupportedType(event.currentDataType)) { + if (event.detail != DND.DROP_COPY) + event.detail = DND.DROP_NONE; + } + } + + public void dragOver(DropTargetEvent event) { + // intentionally empty + } + + public void drop(DropTargetEvent event) { + + if (textTransfer.isSupportedType(event.currentDataType)) { + String txt = (String) event.data; + IResource r = ResourcesPlugin.getWorkspace().getRoot().findMember(txt); + if (r != null) + element.setResource(r); + } else if (resourceTransfer.isSupportedType(event.currentDataType)) { + IResource[] files = (IResource[]) event.data; + if (files.length > 0) + element.setResource(files[0]); + } + + } + + public void dropAccept(DropTargetEvent event) { + // intentionally empty + } + + } + + private abstract class ContentTypeElement { + + private Button radioButton; + protected Button mainButton; + protected Text text; + private String type; + protected InternalSection section; + private IResource resource; + + public ContentTypeElement(Composite parent, String type, InternalSection section) { + this.type = type; + this.section = section; + createContents(parent); + } + + private void createContents(Composite parent) { + createRadioButton(parent); + createText(parent); + createMainButton(parent); + } + + private void createRadioButton(Composite parent) { + radioButton = new Button(parent, SWT.RADIO); + radioButton.setText(type); + } + + protected void createText(Composite parent) { + text = new Text(parent, SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.setEditable(false); + } + + protected void createMainButton(Composite parent) { + mainButton = new Button(parent, SWT.PUSH); + mainButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + } + + protected Button getRadioButton() { + return radioButton; + } + + protected String getText() { + return text.getText(); + } + + protected void setText(String string) { + text.setText(string); + } + + protected void setEnabled(boolean enabled) { + radioButton.setSelection(enabled); + mainButton.setEnabled(enabled); + text.setEnabled(enabled); + } + + protected void setResource(IResource resource) { + this.resource = resource; + section.setResource(resource); + } + + public IResource getResource() { + return resource; + } + + void clearResource() { + resource = null; + text.setText(""); //$NON-NLS-1$ + } + + } + + private class WorkspaceContent extends ContentTypeElement { + + public WorkspaceContent(Composite parent, InternalSection section) { + super(parent, CompareMessages.CompareWithOtherResourceDialog_workspaceRadioButton, section); + } + + protected void createMainButton(Composite parent) { + super.createMainButton(parent); + mainButton.setText(CompareMessages.CompareWithOtherResourceDialog_workspaceMainButton); + // temporarily hide this button. For more information about supporting for browsing workspace see bug 243744. + mainButton.setVisible(false); + } + + protected void createText(Composite parent) { + + super.createText(parent); + text.setEditable(true); + + text.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + section.setResource(text.getText()); + updateErrorInfo(); + } + }); + + text.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + section.setResource(text.getText()); + updateErrorInfo(); + } + }); + + initDrag(); + initDrop(); + } + + protected void setResource(IResource resource) { + super.setResource(resource); + text.setText(resource.getFullPath().toOSString()); + } + + protected void initDrag() { + DragSource source = new DragSource(text, DND.DROP_MOVE + | DND.DROP_COPY | DND.DROP_DEFAULT); + Transfer[] types = new Transfer[] { TextTransfer.getInstance(), + ResourceTransfer.getInstance() }; + source.setTransfer(types); + source.addDragListener(new FileTextDragListener(this)); + } + + protected void initDrop() { + DropTarget target = new DropTarget(text, DND.DROP_MOVE + | DND.DROP_COPY | DND.DROP_DEFAULT); + Transfer[] types = new Transfer[] { TextTransfer.getInstance(), + ResourceTransfer.getInstance() }; + target.setTransfer(types); + target.addDropListener(new FileTextDropListener(this)); + } + + } + + private class ExternalFileContent extends ContentTypeElement { + + public ExternalFileContent(Composite parent, InternalSection section) { + super(parent, CompareMessages.CompareWithOtherResourceDialog_externalFileRadioButton, section); + } + + protected void createMainButton(Composite parent) { + super.createMainButton(parent); + mainButton.setText(CompareMessages.CompareWithOtherResourceDialog_externalFileMainButton); + mainButton.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + IResource r = tmpProject.getExternalFile(); + if (r == null) + return; + setResource(r); + } + }); + } + + protected void setResource(IResource resource) { + super.setResource(resource); + text.setText(resource.getLocation().toOSString()); + } + + } + + private class ExternalFolderContent extends ContentTypeElement { + + public ExternalFolderContent(Composite parent, InternalSection section) { + super(parent, CompareMessages.CompareWithOtherResourceDialog_externalFolderRadioButton, section); + } + + protected void createMainButton(Composite parent) { + super.createMainButton(parent); + mainButton.setText(CompareMessages.CompareWithOtherResourceDialog_externalFolderMainButton); + mainButton.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + IResource r = tmpProject.getExternalFolder(); + if (r == null) + return; + setResource(r); + } + }); + } + + protected void setResource(IResource resource) { + super.setResource(resource); + text.setText(resource.getLocation().toOSString()); + } + + } + + private abstract class InternalSection { + + // there is no "enum" support in Java 1.4. Sigh... + public static final int WORKSPACE = 0; + public static final int EXTERNAL_FILE = 1; + public static final int EXTERNAL_FOLDER = 2; + + protected Group group; + private IResource resource; + + ExternalFileContent externalFileContent; + ExternalFolderContent externalFolderContent; + WorkspaceContent workspaceContent; + + private InternalSection() { + // not to instantiate + } + + protected void createContents(Composite parent) { + + group = new Group(parent, SWT.NONE); + group.setLayout(new GridLayout(3, false)); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + workspaceContent = new WorkspaceContent(group, this); + externalFileContent = new ExternalFileContent(group, this); + externalFolderContent = new ExternalFolderContent(group, this); + + addListenersToRadioButtons(); + } + + private void addListenersToRadioButtons() { + final ContentTypeElement[] elements = new ContentTypeElement[] { workspaceContent, + externalFileContent, externalFolderContent }; + for (int i = 0; i < elements.length; i++) { + elements[i].getRadioButton().addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + for (int j = 0; j < elements.length; j++) { + if (event.widget != elements[j].getRadioButton()) + elements[j].setEnabled(false); + else { + elements[j].setEnabled(true); + setResource(elements[j].getResource()); + } + } + } + }); + } + } + + protected IResource getResource() { + return resource; + } + + protected void setResource(IResource resource) { + this.resource = resource; + updateErrorInfo(); + } + + protected void setResource(String s) { + IResource tmp = ResourcesPlugin.getWorkspace().getRoot() + .findMember(s); + if (tmp instanceof IWorkspaceRoot) + resource = null; + else + resource = tmp; + updateErrorInfo(); + } + + protected void clearResource() { + resource = null; + workspaceContent.clearResource(); + externalFileContent.clearResource(); + externalFolderContent.clearResource(); + updateErrorInfo(); + } + + protected void setContentType(int type) { + switch(type) { + case WORKSPACE: + workspaceContent.setEnabled(true); + externalFileContent.setEnabled(false); + externalFolderContent.setEnabled(false); + break; + case EXTERNAL_FILE: + workspaceContent.setEnabled(false); + externalFileContent.setEnabled(true); + externalFolderContent.setEnabled(false); + break; + case EXTERNAL_FOLDER: + workspaceContent.setEnabled(false); + externalFileContent.setEnabled(false); + externalFolderContent.setEnabled(true); + } + } + } + + private class InternalGroup extends InternalSection { + + public InternalGroup(Composite parent) { + createContents(parent); + } + + public void setText(String text) { + group.setText(text); + } + + public void setLayoutData(GridData layoutData) { + group.setLayoutData(layoutData); + } + } + + private class InternalExpandable extends InternalSection { + + private ExpandableComposite expandable; + private Button clearButton; + + public InternalExpandable(Composite parent) { + createContents(parent); + } + + protected void createContents(Composite parent) { + final Composite p = parent; + expandable = new ExpandableComposite(parent, SWT.NONE, + ExpandableComposite.TREE_NODE | ExpandableComposite.TWISTIE); + super.createContents(expandable); + createClearButton(group); + expandable.setClient(group); + expandable.addExpansionListener(new ExpansionAdapter() { + public void expansionStateChanged(ExpansionEvent e) { + p.layout(); + adjustSize(e.getState()); + } + }); + } + + private void createClearButton(Composite parent) { + clearButton = new Button(parent, SWT.PUSH); + clearButton.setText(CompareMessages.CompareWithOtherResourceDialog_clear); + clearButton.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + clearResource(); + } + }); + } + + public void setText(String text) { + expandable.setText(text); + group.setText(text); + } + + public void setLayoutData(GridData layoutData) { + expandable.setLayoutData(layoutData); + } + } + + private class ExternalResourcesProject { + + // Implementation based on org.eclipse.jdt.internal.core.ExternalFoldersManager + + private int counter = 0; + + private static final String TMP_PROJECT_NAME = ".org.eclipse.compare.tmp"; //$NON-NLS-1$ + + private final static String TMP_PROJECT_FILE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" //$NON-NLS-1$ + + "<projectDescription>\n" //$NON-NLS-1$ + + "\t<name>" + TMP_PROJECT_NAME + "\t</name>\n" //$NON-NLS-1$ //$NON-NLS-2$ + + "\t<comment></comment>\n" //$NON-NLS-1$ + + "\t<projects>\n" //$NON-NLS-1$ + + "\t</projects>\n" //$NON-NLS-1$ + + "\t<buildSpec>\n" //$NON-NLS-1$ + + "\t</buildSpec>\n" //$NON-NLS-1$ + + "\t<natures>\n" + "\t</natures>\n" //$NON-NLS-1$//$NON-NLS-2$ + + "</projectDescription>"; //$NON-NLS-1$ + + private final static String TMP_FOLDER_NAME = "tmpFolder"; //$NON-NLS-1$ + + private ExternalResourcesProject() { + // nothing to do here + } + + private IProject createTmpProject() throws CoreException { + IProject project = getTmpProject(); + if (!project.isAccessible()) { + try { + IPath stateLocation = CompareUI.getPlugin().getStateLocation(); + if (!project.exists()) { + IProjectDescription desc = project.getWorkspace() + .newProjectDescription(project.getName()); + desc.setLocation(stateLocation.append(TMP_PROJECT_NAME)); + project.create(desc, null); + } + try { + project.open(null); + } catch (CoreException e) { // in case .project file or folder has been deleted + IPath projectPath = stateLocation.append(TMP_PROJECT_NAME); + projectPath.toFile().mkdirs(); + FileOutputStream output = new FileOutputStream( + projectPath.append(".project").toOSString()); //$NON-NLS-1$ + try { + output.write(TMP_PROJECT_FILE.getBytes()); + } finally { + output.close(); + } + project.open(null); + } + getTmpFolder(project); + } catch (IOException ioe) { + return project; + } catch (CoreException ce) { + throw new CoreException(ce.getStatus()); + } + } + project.setHidden(true); + return project; + } + + private IFolder getTmpFolder(IProject project) throws CoreException { + IFolder folder = project.getFolder(TMP_FOLDER_NAME); + if (!folder.exists()) + folder.create(IResource.NONE, true, null); + return folder; + } + + private IFile getExternalFile() { + FileDialog dialog = new FileDialog(getShell()); + String path = dialog.open(); + if (path != null) + return (IFile) linkResource(new Path(path)); + return null; + } + + private IFolder getExternalFolder() { + DirectoryDialog dialog = new DirectoryDialog(getShell()); + String path = dialog.open(); + if (path != null) + return (IFolder) linkResource(new Path(path)); + return null; + } + + private IResource linkResource(IPath path) { + IResource r = null; + String resourceName = path.lastSegment(); + try { + IProject project = createTmpProject(); + if (!project.isOpen()) + project.open(null); + if (path.toFile().isFile()) { + r = getTmpFolder(project).getFile(resourceName); + if (r.exists()) { // add a number to file's name when there already is a file with that name in a folder + String extension = path.getFileExtension(); + String fileName = path.removeFileExtension().lastSegment(); + r = getTmpFolder(project).getFile(getName(fileName, extension)); + } + ((IFile)r).createLink(path, IResource.REPLACE, null); + } else { // isDirectory + r = getTmpFolder(project).getFolder(resourceName); + if (r.exists()) { + r = getTmpFolder(project).getFolder(getName(resourceName, null)); + } + ((IFolder)r).createLink(path, IResource.REPLACE, null); + } + } catch (CoreException e) { + CompareUIPlugin.log(e); + MessageDialog.openError(getShell(), + CompareMessages.CompareWithOtherResourceDialog_externalFile_errorTitle, + CompareMessages.CompareWithOtherResourceDialog_externalFile_errorMessage); + } + return r; + } + + /** + * This method is used to prevent duplicating names of linked resources. + * It adds a suffix based on the <code>counter</code> value. + * + * @param name + * @param extension optional + * @return + */ + private String getName(String name, String extension) { + if (counter != 0) { + name = name + "-" + counter; //$NON-NLS-1$ + } + // at most 3 resources at the same time with the same name: + // left, right, ancestor + counter = (counter + 1) % 3; + if (extension != null) { + name += "." + extension; //$NON-NLS-1$ + } + // don't change the name if counter equals 0 + return name; + } + + private IProject getTmpProject() { + return ResourcesPlugin.getWorkspace().getRoot().getProject( + TMP_PROJECT_NAME); + } + } + + private Button okButton; + private InternalGroup rightPanel, leftPanel; + private InternalExpandable ancestorPanel; + private ISelection selection; + private ExternalResourcesProject tmpProject = new ExternalResourcesProject(); + + /** + * Creates the dialog. + * + * @param shell + * a shell + * @param selection + * if the selection is not null, it will be set as initial files + * for comparison + * @since 3.4 + */ + protected CompareWithOtherResourceDialog(Shell shell, ISelection selection) { + super(shell); + setShellStyle(SWT.MODELESS | SWT.RESIZE | SWT.MAX); + this.selection = selection; + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets + * .Composite) + */ + protected Control createDialogArea(Composite parent) { + + Composite mainPanel = new Composite(parent, SWT.NULL); + mainPanel.setLayout(new GridLayout(1, true)); + mainPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + ancestorPanel = new InternalExpandable(mainPanel); + ancestorPanel.setText(CompareMessages.CompareWithOtherResourceDialog_ancestor); + GridData ancestorGD = new GridData(SWT.FILL, SWT.FILL, true, false); + ancestorPanel.setLayoutData(ancestorGD); + + leftPanel = new InternalGroup(mainPanel); + leftPanel.setText(CompareMessages.CompareWithOtherResourceDialog_leftPanel); + leftPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + rightPanel = new InternalGroup(mainPanel); + rightPanel.setText(CompareMessages.CompareWithOtherResourceDialog_rightPanel); + rightPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + setSelection(selection); + getShell().setText(CompareMessages.CompareWithOtherResourceDialog_dialogTitle); + setTitle(CompareMessages.CompareWithOtherResourceDialog_dialogMessage); + adjustSize(ancestorPanel.expandable.isExpanded()); + + return mainPanel; + } + + private void adjustSize(boolean expanded) { + int minWidth = convertHorizontalDLUsToPixels(MIN_WIDTH); + int minHeight = convertVerticalDLUsToPixels(expanded ? MIN_HEIGHT_WITH_ANCESTOR + : MIN_HEIGHT_WITHOUT_ANCESTOR); + getShell().setMinimumSize(minWidth, minHeight); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse + * .swt.widgets.Composite) + */ + protected void createButtonsForButtonBar(Composite parent) { + super.createButtonsForButtonBar(parent); + okButton = getButton(IDialogConstants.OK_ID); + updateErrorInfo(); + setMessage(CompareMessages.CompareWithOtherResourceDialog_info); + } + + private void setSelection(ISelection selection) { + IResource[] selectedResources = Utilities.getResources(selection); + switch (selectedResources.length) { + case 1: + leftPanel.workspaceContent.setResource(selectedResources[0]); + break; + case 2: + leftPanel.workspaceContent.setResource(selectedResources[0]); + rightPanel.workspaceContent.setResource(selectedResources[1]); + break; + case 3: + ancestorPanel.workspaceContent.setResource(selectedResources[0]); + ancestorPanel.expandable.setExpanded(true); + leftPanel.workspaceContent.setResource(selectedResources[1]); + rightPanel.workspaceContent.setResource(selectedResources[2]); + break; + } + setInitialContentTypes(); + } + + private void setInitialContentTypes() { + ancestorPanel.setContentType(InternalSection.WORKSPACE); + leftPanel.setContentType(InternalSection.WORKSPACE); + rightPanel.setContentType(InternalSection.WORKSPACE); + } + + private boolean isComparePossible() { + IResource[] resources; + if (ancestorPanel.getResource() == null) + resources = new IResource[] { leftPanel.getResource(), + rightPanel.getResource() }; + else + resources = new IResource[] { ancestorPanel.getResource(), + leftPanel.getResource(), rightPanel.getResource() }; + + ResourceCompareInput r = new ResourceCompareInput( + new CompareConfiguration()); + return r.isEnabled(new StructuredSelection(resources)); + } + + private void updateErrorInfo() { + if (okButton != null) { + if (leftPanel.getResource() == null + || rightPanel.getResource() == null) { + setMessage(CompareMessages.CompareWithOtherResourceDialog_error_empty, + IMessageProvider.ERROR); + okButton.setEnabled(false); + } else if (!isComparePossible()) { + setMessage( + CompareMessages.CompareWithOtherResourceDialog_error_not_comparable, + IMessageProvider.ERROR); + okButton.setEnabled(false); + } else { + setMessage(CompareMessages.CompareWithOtherResourceDialog_info); + okButton.setEnabled(true); + } + } + } + + /** + * Returns table with selected resources. If any resource wasn't chosen in + * the ancestor panel, table has only two elements -- resources chosen in + * left and right panel. In the other case table contains all three + * resources. + * + * @return table with selected resources + */ + public IResource[] getResult() { + IResource[] resources; + IResource rightResource = rightPanel.getResource(); + IResource leftResource = leftPanel.getResource(); + IResource ancestorResource = ancestorPanel.getResource(); + if (ancestorResource == null) + resources = new IResource[] { leftResource, rightResource }; + else + resources = new IResource[] { ancestorResource, leftResource, + rightResource }; + return resources; + } + + /* + * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings() + */ + protected IDialogSettings getDialogBoundsSettings() { + String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ + IDialogSettings settings = CompareUIPlugin.getDefault() + .getDialogSettings(); + IDialogSettings section = settings.getSection(sectionName); + if (section == null) + section = settings.addNewSection(sectionName); + return section; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java new file mode 100644 index 000000000..43a55ee83 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2009 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.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * This is a temporary replacement for CompareWithOtherResourceAction which was + * available from "Compare With > Other Resource...". See bug 264498. + */ +public class CompareWithOtherResourceHandler extends AbstractHandler { + + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + IWorkbenchPage workbenchPage = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + + // CompareAction#isEnabled(ISelection) + CompareConfiguration cc = new CompareConfiguration(); + cc.setProperty(CompareEditor.CONFIRM_SAVE_PROPERTY, new Boolean(false)); + ResourceCompareInput input = new ResourceCompareInput(cc); + + int selectionSize = 0; + if (selection instanceof IStructuredSelection) { + selectionSize = ((IStructuredSelection) selection).toArray().length; + } + if (input.isEnabled(selection) || selectionSize == 1) { + + // CompareAction#run(ISelection) + if (!input.setSelection(selection, workbenchPage.getWorkbenchWindow().getShell(), false)) + return null; + input.initializeCompareConfiguration(); + CompareUI.openCompareEditorOnPage(input, workbenchPage); + } + return null; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java new file mode 100644 index 000000000..9aee9c259 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.internal; + +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.swt.widgets.Display; + +/** + * A helper class for managing content change notification. + */ +public class ContentChangeNotifier implements IContentChangeNotifier { + + private ListenerList fListenerList; + private final IContentChangeNotifier element; + + public ContentChangeNotifier(IContentChangeNotifier element) { + this.element = element; + } + + /* (non-Javadoc) + * see IContentChangeNotifier.addChangeListener + */ + public void addContentChangeListener(IContentChangeListener listener) { + if (fListenerList == null) + fListenerList= new ListenerList(); + fListenerList.add(listener); + } + + /* (non-Javadoc) + * see IContentChangeNotifier.removeChangeListener + */ + public void removeContentChangeListener(IContentChangeListener listener) { + if (fListenerList != null) { + fListenerList.remove(listener); + if (fListenerList.isEmpty()) + fListenerList= null; + } + } + + /** + * Notifies all registered <code>IContentChangeListener</code>s of a content change. + */ + public void fireContentChanged() { + if (isEmpty()) { + return; + } + // Legacy listeners may expect to be notified in the UI thread. + Runnable runnable = new Runnable() { + public void run() { + Object[] listeners= fListenerList.getListeners(); + for (int i= 0; i < listeners.length; i++) { + final IContentChangeListener contentChangeListener = (IContentChangeListener)listeners[i]; + SafeRunner.run(new ISafeRunnable() { + public void run() throws Exception { + contentChangeListener.contentChanged(element); + } + public void handleException(Throwable exception) { + // Logged by safe runner + } + }); + } + } + }; + if (Display.getCurrent() == null) { + Display.getDefault().syncExec(runnable); + } else { + runnable.run(); + } + } + + /** + * Return whether this notifier is empty (i.e. has no listeners). + * @return whether this notifier is empty + */ + public boolean isEmpty() { + return fListenerList == null || fListenerList.isEmpty(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java new file mode 100644 index 000000000..a0ac41e04 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.internal; + +import org.eclipse.swt.graphics.*; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; + +/** + * Combines an image with an overlay. + */ +public class DiffImageDescriptor extends CompositeImageDescriptor { + + static final int HEIGHT= 16; + + private final ImageData fBaseImageData; + private final ImageDescriptor fOverlayImage; + private final int fWidth; + private final boolean fLeft; + private final int hashCode; + + public DiffImageDescriptor(Image base, ImageDescriptor overlay, int w, boolean onLeft) { + ImageData data = null; + if (base != null) { + data = base.getImageData(); + if (data == null) + data = DEFAULT_IMAGE_DATA; + } + fBaseImageData = data; + fOverlayImage= overlay; + fWidth= w; + fLeft= onLeft; + hashCode = calculateHashCode(); + } + + private int calculateHashCode() { + int h1 = 0; + int h2 = 0; + if (fBaseImageData != null) { + h1 = calculateHash(fBaseImageData); + } + if (fOverlayImage != null) { + h2 = fOverlayImage.hashCode(); + } + return h1 + h2 + fWidth; + } + + private int calculateHash(ImageData baseImageData) { + byte[] data = baseImageData.data; + int hash = baseImageData.width + baseImageData.height; + for (int i = 0; i < data.length; i++) { + byte b = data[i]; + hash >>>= 1; + hash ^= b; + } + return hash; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.resource.CompositeImageDescriptor#getSize() + */ + protected Point getSize() { + return new Point(fWidth, HEIGHT); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.resource.CompositeImageDescriptor#drawCompositeImage(int, int) + */ + protected void drawCompositeImage(int width, int height) { + if (fLeft) { + if (fBaseImageData != null) { + drawImage(fBaseImageData, fWidth - fBaseImageData.width, 0); + } + + if (fOverlayImage != null) { + ImageData overlay= fOverlayImage.getImageData(); + if (overlay == null) + overlay= DEFAULT_IMAGE_DATA; + drawImage(overlay, 0, (HEIGHT - overlay.height) / 2); + } + } else { + if (fBaseImageData != null) { + drawImage(fBaseImageData, 0, 0); + } + + if (fOverlayImage != null) { + ImageData overlay= fOverlayImage.getImageData(); + if (overlay == null) + overlay= DEFAULT_IMAGE_DATA; + drawImage(overlay, fWidth - overlay.width, (HEIGHT - overlay.height) / 2); + } + } + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return hashCode; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof DiffImageDescriptor) { + DiffImageDescriptor other = (DiffImageDescriptor) obj; + return (other.hashCode == hashCode + && isEqual(other.fOverlayImage, fOverlayImage) + && other.fWidth == fWidth + && other.fLeft == fLeft + && isEqual(other.fBaseImageData, fBaseImageData)); + } + return false; + } + + private boolean isEqual(ImageData i1, ImageData i2) { + if (isEqual((Object) i1, (Object) i2)) { + return true; + } + if (i1 == null || i2 == null) + return false; + return (i1.width == i2.width && i1.height == i2.height + && i1.depth == i2.depth && i1.scanlinePad == i2.scanlinePad + && i1.bytesPerLine == i2.bytesPerLine + /* && i1.palette == i2.palette */ + && i1.transparentPixel == i2.transparentPixel + && i1.maskPad == i2.maskPad + && i1.alpha == i2.alpha + && i1.type == i2.type && i1.x == i2.x && i1.y == i2.y + && i1.disposalMethod == i2.disposalMethod && i1.delayTime == i2.delayTime + && equals(i1.data,i2.data) && equals(i1.maskData, i2.maskData) + && equals(i1.alphaData, i2.alphaData)); + } + + private boolean equals(byte[] data, byte[] data2) { + if (isEqual(data, data2)) + return true; + if (data == null || data2 == null) + return false; + if (data.length != data2.length) + return false; + for (int i = 0; i < data2.length; i++) { + if (data[i] != data2[i]) + return false; + } + return true; + } + + private boolean isEqual(Object o1, Object o2) { + if (o1 == o2) + return true; + if (o1 == null || o2 == null) + return false; + return o1.equals(o2); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java new file mode 100644 index 000000000..8686bbbb3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal; + +import org.eclipse.jface.text.*; +import org.eclipse.compare.contentmergeviewer.ITokenComparator; +import org.eclipse.compare.rangedifferencer.IRangeComparator; + +/** + * Implements the <code>IRangeComparator</code> interface for lines in a document. + * A <code>DocLineComparator</code> is used as the input for the <code>RangeDifferencer</code> + * engine to perform a line oriented compare on documents. + * <p> + * A <code>DocLineComparator</code> doesn't know anything about line separators because + * its notion of lines is solely defined in the underlying <code>IDocument</code>. + */ +public class DocLineComparator implements ITokenComparator { + + private IDocument fDocument; + private int fLineOffset; + private int fLineCount; + private int fLength; + private boolean fIgnoreWhiteSpace; + + /** + * Creates a <code>DocLineComparator</code> for the given document range. + * ignoreWhiteSpace controls whether comparing lines (in method + * <code>rangesEqual<code>) should ignore whitespace. + * + * @param document the document from which the lines are taken + * @param region if non-<code>null</code> only lines within this range are taken + * @param ignoreWhiteSpace if <code>true</code> white space is ignored when comparing lines + */ + public DocLineComparator(IDocument document, IRegion region, + boolean ignoreWhiteSpace) { + fDocument = document; + fIgnoreWhiteSpace = ignoreWhiteSpace; + + fLineOffset = 0; + if (region != null) { + fLength = region.getLength(); + int start = region.getOffset(); + try { + fLineOffset = fDocument.getLineOfOffset(start); + } catch (BadLocationException ex) { + // silently ignored + } + + if (fLength == 0) { + // optimization, empty documents have one line + fLineCount = 1; + } else { + int endLine = fDocument.getNumberOfLines(); + try { + endLine = fDocument.getLineOfOffset(start + fLength); + } catch (BadLocationException ex) { + // silently ignored + } + fLineCount = endLine - fLineOffset + 1; + } + } else { + fLength = document.getLength(); + fLineCount = fDocument.getNumberOfLines(); + } + } + + /** + * Returns the number of lines in the document. + * + * @return number of lines + */ + public int getRangeCount() { + return fLineCount; + } + + /* (non Javadoc) + * see ITokenComparator.getTokenStart + */ + public int getTokenStart(int line) { + try { + IRegion r= fDocument.getLineInformation(fLineOffset + line); + return r.getOffset(); + } catch (BadLocationException ex) { + return fDocument.getLength(); + } + } + + /* (non Javadoc) + * Returns the length of the given line. + * see ITokenComparator.getTokenLength + */ + public int getTokenLength(int line) { + return getTokenStart(line+1) - getTokenStart(line); + } + + /** + * Returns <code>true</code> if a line given by the first index + * matches a line specified by the other <code>IRangeComparator</code> and index. + * + * @param thisIndex the number of the line within this range comparator + * @param otherComparator the range comparator to compare this with + * @param otherIndex the number of the line within the other comparator + * @return <code>true</code> if the lines are equal + */ + public boolean rangesEqual(int thisIndex, IRangeComparator otherComparator, int otherIndex) { + + if (otherComparator != null && otherComparator.getClass() == getClass()) { + DocLineComparator other= (DocLineComparator) otherComparator; + + if (fIgnoreWhiteSpace) { + String s1= extract(thisIndex); + String s2= other.extract(otherIndex); + //return s1.trim().equals(s2.trim()); + return compare(s1, s2); + } + + int tlen= getTokenLength(thisIndex); + int olen= other.getTokenLength(otherIndex); + if (tlen == olen) { + String s1= extract(thisIndex); + String s2= other.extract(otherIndex); + return s1.equals(s2); + } + } + return false; + } + + /** + * Aborts the comparison if the number of tokens is too large. + * + * @param length a number on which to base the decision whether to return + * <code>true</code> or <code>false</code> + * @param maxLength another number on which to base the decision whether to return + * <code>true</code> or <code>false</code> + * @param other the other <code>IRangeComparator</code> to compare with + * @return <code>true</code> to avoid a too lengthy range comparison + */ + public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) { + return false; + } + + //---- private methods + + /** + * Extract a single line from the underlying document without the line separator. + * + * @param line the number of the line to extract + * @return the contents of the line as a String + */ + private String extract(int line) { + if (line < fLineCount) { + try { + IRegion r= fDocument.getLineInformation(fLineOffset + line); + return fDocument.get(r.getOffset(), r.getLength()); + } catch(BadLocationException e) { + // silently ignored + } + } + return ""; //$NON-NLS-1$ + } + + private boolean compare(String s1, String s2) { + int l1= s1.length(); + int l2= s2.length(); + int c1= 0, c2= 0; + int i1= 0, i2= 0; + + while (c1 != -1) { + + c1= -1; + while (i1 < l1) { + char c= s1.charAt(i1++); + if (! Character.isWhitespace(c)) { + c1= c; + break; + } + } + + c2= -1; + while (i2 < l2) { + char c= s2.charAt(i2++); + if (! Character.isWhitespace(c)) { + c2= c; + break; + } + } + + if (c1 != c2) + return false; + } + return true; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java new file mode 100644 index 000000000..7f2dd3e9e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.util.ArrayList; + +import org.eclipse.jface.text.IDocument; + +/** + * No API yet. + */ +public class DocumentManager { + + private static final boolean DEBUG= false; + + private static ArrayList fgKeys= new ArrayList(); + private static ArrayList fgValues= new ArrayList(); + + public static IDocument get(Object o) { + + for (int i= 0; i < fgKeys.size(); i++) { + if (fgKeys.get(i) == o) + return (IDocument) fgValues.get(i); + } + return null; + } + + public static void put(Object o, IDocument document) { + if (DEBUG) System.out.println("DocumentManager.put: " + document); //$NON-NLS-1$ + for (int i= 0; i < fgKeys.size(); i++) { + if (fgKeys.get(i) == o) { + fgValues.set(i, document); + return; + } + } + fgKeys.add(o); + fgValues.add(document); + } + + public static void remove(IDocument document) { + if (document != null) { + if (DEBUG) System.out.println("DocumentManager.remove: " + document); //$NON-NLS-1$ + for (int i= 0; i < fgValues.size(); i++) { + if (fgValues.get(i) == document) { + fgKeys.remove(i); + fgValues.remove(i); + return; + } + } + if (DEBUG) System.out.println("DocumentManager.remove: not found"); //$NON-NLS-1$ + } + } + + public static void dump() { + if (DEBUG) System.out.println("DocumentManager: managed docs:" + fgValues.size()); //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java new file mode 100644 index 000000000..f0adb2cb8 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ResourceBundle; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.graphics.Image; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.BadLocationException; + +import org.eclipse.ui.*; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.actions.WorkspaceModifyOperation; + +import org.eclipse.compare.*; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.IStreamContentAccessor; + + +public class EditionAction extends BaseCompareAction { + + /** + * Implements the IStreamContentAccessor and ITypedElement protocols + * for a Document. + */ + class DocumentBufferNode implements ITypedElement, IEncodedStreamContentAccessor { + private static final String UTF_16= "UTF-16"; //$NON-NLS-1$ + private IDocument fDocument; + private IFile fFile; + + DocumentBufferNode(IDocument document, IFile file) { + fDocument= document; + fFile= file; + } + + public String getName() { + return fFile.getName(); + } + + public String getType() { + return fFile.getFileExtension(); + } + + public Image getImage() { + return null; + } + + public InputStream getContents() { + return new ByteArrayInputStream(Utilities.getBytes(fDocument.get(), UTF_16)); + } + + public String getCharset() { + return UTF_16; + } + } + + private String fBundleName; + private boolean fReplaceMode; + protected boolean fPrevious= false; + protected String fHelpContextId; + + EditionAction(boolean replaceMode, String bundleName) { + fReplaceMode= replaceMode; + fBundleName= bundleName; + } + + protected boolean isEnabled(ISelection selection) { + return Utilities.getFiles(selection).length == 1; // we don't support multiple selection for now + } + + protected void run(ISelection selection) { + IFile[] files= Utilities.getFiles(selection); + for (int i= 0; i < files.length; i++) + doFromHistory(files[i]); + } + + private void doFromHistory(final IFile file) { + + ResourceBundle bundle= ResourceBundle.getBundle(fBundleName); + String title= Utilities.getString(bundle, "title"); //$NON-NLS-1$ + + Shell parentShell= CompareUIPlugin.getShell(); + + IFileState states[]= null; + try { + states= file.getHistory(null); + } catch (CoreException ex) { + MessageDialog.openError(parentShell, title, ex.getMessage()); + return; + } + + if (states == null || states.length <= 0) { + String msg= Utilities.getString(bundle, "noLocalHistoryError"); //$NON-NLS-1$ + MessageDialog.openInformation(parentShell, title, msg); + return; + } + + ITypedElement base= new ResourceNode(file); + + IDocument document= getDocument(file); + ITypedElement target= base; + if (document != null) + target= new DocumentBufferNode(document, file); + + ITypedElement[] editions= new ITypedElement[states.length+1]; + editions[0]= base; + for (int i= 0; i < states.length; i++) + editions[i+1]= new HistoryItem(base, states[i]); + + EditionSelectionDialog d= new EditionSelectionDialog(parentShell, bundle); + d.setEditionTitleArgument(file.getName()); + d.setEditionTitleImage(CompareUIPlugin.getImage(file)); + //d.setHideIdenticalEntries(false); + if (fHelpContextId != null) + d.setHelpContextId(fHelpContextId); + + if (fReplaceMode) { + + ITypedElement ti= null; + if (fPrevious) + ti= d.selectPreviousEdition(target, editions, null); + else + ti= d.selectEdition(target, editions, null); + + if (ti instanceof IStreamContentAccessor) { + IStreamContentAccessor sa= (IStreamContentAccessor)ti; + + if (Utilities.validateResource(file, parentShell, title)) { + try { + + if (document != null) + updateDocument(document, sa); + else + updateWorkspace(bundle, parentShell, sa, file); + + } catch (InterruptedException x) { + // Do nothing. Operation has been canceled by user. + + } catch (InvocationTargetException x) { + String reason= x.getTargetException().getMessage(); + MessageDialog.openError(parentShell, title, Utilities.getFormattedString(bundle, "replaceError", reason)); //$NON-NLS-1$ + } + } + } + } else { + d.setCompareMode(true); + + d.selectEdition(target, editions, null); + } + } + + private void updateWorkspace(final ResourceBundle bundle, Shell shell, + final IStreamContentAccessor sa, final IFile file) + throws InvocationTargetException, InterruptedException { + WorkspaceModifyOperation operation= new WorkspaceModifyOperation() { + public void execute(IProgressMonitor pm) throws InvocationTargetException { + try { + String taskName= Utilities.getString(bundle, "taskName"); //$NON-NLS-1$ + pm.beginTask(taskName, IProgressMonitor.UNKNOWN); + file.setContents(sa.getContents(), false, true, pm); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } finally { + pm.done(); + } + } + }; + + ProgressMonitorDialog pmdialog= new ProgressMonitorDialog(shell); + pmdialog.run(false, true, operation); + } + + private void updateDocument(IDocument document, IStreamContentAccessor sa) throws InvocationTargetException { + try { + String text= Utilities.readString(sa); + document.replace(0, document.getLength(), text); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } catch (BadLocationException e) { + throw new InvocationTargetException(e); + } + } + + private IDocument getDocument(IFile file) { + IWorkbench wb= PlatformUI.getWorkbench(); + if (wb == null) + return null; + IWorkbenchWindow[] ws= wb.getWorkbenchWindows(); + if (ws == null) + return null; + + FileEditorInput test= new FileEditorInput(file); + + for (int i= 0; i < ws.length; i++) { + IWorkbenchWindow w= ws[i]; + IWorkbenchPage[] wps= w.getPages(); + if (wps != null) { + for (int j= 0; j < wps.length; j++) { + IWorkbenchPage wp= wps[j]; + IEditorPart ep= wp.findEditor(test); + if (ep instanceof ITextEditor) { + ITextEditor te= (ITextEditor) ep; + IDocumentProvider dp= te.getDocumentProvider(); + if (dp != null) { + IDocument doc= dp.getDocument(ep); + if (doc != null) + return doc; + } + } + } + } + } + return null; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java new file mode 100644 index 000000000..723d8cfbb --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; + +/** + * The default exception handler shows an error dialog when one of its handle methods + * is called. If the passed exception is a <code>CoreException</code> an error dialog + * pops up showing the exception's status information. For a <code>InvocationTargetException</code> + * a normal message dialog pops up showing the exception's message. Additionally the exception + * is written to the platform log. + */ +public class ExceptionHandler { + + private static ExceptionHandler fgInstance= new ExceptionHandler(); + + /* + * Logs the given exception using the platform's logging mechanism. The exception is + * logged as an error with the error code <code>JavaStatusConstants.INTERNAL_ERROR</code>. + */ + public static void log(Throwable t, String message) { + CompareUIPlugin.log(new Status(IStatus.ERROR, CompareUIPlugin.getPluginId(), + CompareUIPlugin.INTERNAL_ERROR, message, t)); + } + + /** + * Handles the given <code>CoreException</code>. The workbench shell is used as a parent + * for the dialog window. + * + * @param e the <code>CoreException</code> to be handled + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(CoreException e, String title, String message) { + handle(e, CompareUIPlugin.getShell(), title, message); + } + + /** + * Handles the given <code>CoreException</code>. + * + * @param e the <code>CoreException</code> to be handled + * @param parent the dialog window's parent shell + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(CoreException e, Shell parent, String title, String message) { + fgInstance.perform(e, parent, title, message); + } + + /** + * Handles the given <code>InvocationTargetException</code>. The workbench shell is used + * as a parent for the dialog window. + * + * @param e the <code>InvocationTargetException</code> to be handled + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(InvocationTargetException e, String title, String message) { + handle(e, CompareUIPlugin.getShell(), title, message); + } + + /** + * Handles the given <code>InvocationTargetException</code>. + * + * @param e the <code>InvocationTargetException</code> to be handled + * @param parent the dialog window's parent shell + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(InvocationTargetException e, Shell parent, String title, String message) { + fgInstance.perform(e, parent, title, message); + } + + //---- Hooks for subclasses to control exception handling ------------------------------------ + + protected void perform(CoreException e, Shell shell, String title, String message) { + CompareUIPlugin.log(e); + IStatus status= e.getStatus(); + if (status != null) { + ErrorDialog.openError(shell, title, message, status); + } else { + displayMessageDialog(e, e.getMessage(), shell, title, message); + } + } + + protected void perform(InvocationTargetException e, Shell shell, String title, String message) { + Throwable target= e.getTargetException(); + if (target instanceof CoreException) { + perform((CoreException)target, shell, title, message); + } else { + CompareUIPlugin.log(e); + if (e.getMessage() != null && e.getMessage().length() > 0) { + displayMessageDialog(e, e.getMessage(), shell, title, message); + } else { + displayMessageDialog(e, target.getMessage(), shell, title, message); + } + } + } + + //---- Helper methods ----------------------------------------------------------------------- + + private void displayMessageDialog(Throwable t, String exceptionMessage, Shell shell, String title, String message) { + StringWriter msg= new StringWriter(); + if (message != null) { + msg.write(message); + msg.write("\n\n"); //$NON-NLS-1$ + } + if (exceptionMessage == null || exceptionMessage.length() == 0) + msg.write(CompareMessages.ExceptionDialog_seeErrorLogMessage); + else + msg.write(exceptionMessage); + MessageDialog.openError(shell, title, msg.toString()); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java new file mode 100644 index 000000000..e2b36a5bb --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.compare.CompareUI; + +/** + * Help context ids for the Compare UI. + * <p> + * This interface contains constants only; it is not intended to be implemented + * or extended. + * </p> + * + */ +public interface ICompareContextIds { + + public static final String PREFIX= CompareUI.PLUGIN_ID + '.'; + + // Dialogs + public static final String EDITION_DIALOG= PREFIX + "edition_dialog_context"; //$NON-NLS-1$ + + public static final String COMPARE_EDITOR= PREFIX + "compare_editor_context"; //$NON-NLS-1$ + public static final String PATCH_INPUT_WIZARD_PAGE= PREFIX + "patch_input_wizard_page_context"; //$NON-NLS-1$ + public static final String PATCH_PREVIEW_WIZARD_PAGE= PREFIX + "patch_preview_wizard_page_context"; //$NON-NLS-1$ + public static final String ADD_FROM_HISTORY_DIALOG= PREFIX + "add_from_history_dialog_context"; //$NON-NLS-1$ + public static final String COMPARE_DIALOG= PREFIX + "compare_dialog_context"; //$NON-NLS-1$ + public static final String COMPARE_WITH_EDITION_DIALOG= PREFIX + "compare_with_edition_dialog_context"; //$NON-NLS-1$ + public static final String REPLACE_WITH_EDITION_DIALOG= PREFIX + "replace_with_edition_dialog_context"; //$NON-NLS-1$ + + // Viewer + public static final String TEXT_MERGE_VIEW= PREFIX + "text_merge_view_context"; //$NON-NLS-1$ + public static final String IMAGE_COMPARE_VIEW= PREFIX + "image_compare_view_context"; //$NON-NLS-1$ + public static final String BINARY_COMPARE_VIEW= PREFIX + "binary_compare_view_context"; //$NON-NLS-1$ + public static final String DIFF_VIEW= PREFIX + "diff_view_context"; //$NON-NLS-1$ + + // Actions + public static final String GLOBAL_NEXT_DIFF_ACTION= PREFIX + "global_next_diff_action_context"; //$NON-NLS-1$ + public static final String GLOBAL_PREVIOUS_DIFF_ACTION= PREFIX + "global_previous_diff_action_context"; //$NON-NLS-1$ + public static final String NEXT_DIFF_ACTION= PREFIX + "next_diff_action_context"; //$NON-NLS-1$ + public static final String PREVIOUS_DIFF_ACTION= PREFIX + "previous_diff_action_context"; //$NON-NLS-1$ + public static final String IGNORE_WHITESPACE_ACTION= PREFIX + "ignore_whitespace_action_context"; //$NON-NLS-1$ + + // Preference page + public static final String COMPARE_PREFERENCE_PAGE= PREFIX + "compare_preference_page_context"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java new file mode 100644 index 000000000..e36def063 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.internal; + + +public interface ICompareUIConstants { + public final String PREFIX = CompareUIPlugin.getPluginId() + "."; //$NON-NLS-1$ + + public static final String DTOOL_NEXT= "dlcl16/next_nav.gif"; //$NON-NLS-1$ + public static final String ETOOL_NEXT= "elcl16/next_nav.gif"; //$NON-NLS-1$ + public static final String CTOOL_NEXT= ETOOL_NEXT; + + public static final String DTOOL_PREV= "dlcl16/prev_nav.gif"; //$NON-NLS-1$ + public static final String ETOOL_PREV= "elcl16/prev_nav.gif"; //$NON-NLS-1$ + public static final String CTOOL_PREV= ETOOL_PREV; + + public static final String HUNK_OBJ = "obj16/hunk_obj.gif"; //$NON-NLS-1$ + + public static final String ERROR_OVERLAY= "ovr16/error_ov.gif"; //$NON-NLS-1$ + public static final String IS_MERGED_OVERLAY= "ovr16/merged_ov.gif"; //$NON-NLS-1$ + public static final String REMOVED_OVERLAY= "ovr16/removed_ov.gif"; //$NON-NLS-1$ + public static final String WARNING_OVERLAY= "ovr16/warning_ov.gif"; //$NON-NLS-1$ + + public static final String RETARGET_PROJECT= "eview16/compare_view.gif"; //$NON-NLS-1$ + + public static final String IGNORE_WHITESPACE_ENABLED= "etool16/ignorews_edit.gif"; //$NON-NLS-1$ + public static final String IGNORE_WHITESPACE_DISABLED= "dtool16/ignorews_edit.gif"; //$NON-NLS-1$ + + public static final String PROP_ANCESTOR_VISIBLE = PREFIX + "AncestorVisible"; //$NON-NLS-1$ + public static final String PROP_IGNORE_ANCESTOR = PREFIX + "IgnoreAncestor"; //$NON-NLS-1$ + public static final String PROP_TITLE = PREFIX + "Title"; //$NON-NLS-1$ + public static final String PROP_TITLE_IMAGE = PREFIX + "TitleImage"; //$NON-NLS-1$ + public static final String PROP_SELECTED_EDITION = PREFIX + "SelectedEdition"; //$NON-NLS-1$ + + public static final int COMPARE_IMAGE_WIDTH= 22; + + public static final String PREF_NAVIGATION_END_ACTION= PREFIX + "NavigationEndAction"; //$NON-NLS-1$ + public static final String PREF_NAVIGATION_END_ACTION_LOCAL= PREFIX + "NavigationEndActionLocal"; //$NON-NLS-1$ + public static final String PREF_VALUE_PROMPT = "prompt"; //$NON-NLS-1$ + public static final String PREF_VALUE_LOOP = "loop"; //$NON-NLS-1$ + public static final String PREF_VALUE_NEXT = "next"; //$NON-NLS-1$ + public static final String PREF_VALUE_DO_NOTHING = "doNothing"; //$NON-NLS-1$ + + public static final String COMMAND_IGNORE_WHITESPACE = PREFIX + "ignoreWhiteSpace"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java new file mode 100644 index 000000000..47a5b735a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 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.internal; + +import org.eclipse.compare.contentmergeviewer.IFlushable; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Interface which provides the ability to flush the contents from the specified + * side of the viewer. + * + * @see IFlushable + * + * @since 3.7 + */ +public interface IFlushable2 { + void flushLeft(IProgressMonitor monitor); + + void flushRight(IProgressMonitor monitor); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java new file mode 100644 index 000000000..a790eaae6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2006 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.internal; + +import org.eclipse.jface.text.IDocument; + +/** + * An interface that provides access to the internals of a merge viewer for the purposes of testing. + * NOTE: This interface is not to be used for any other purpose. + */ +public interface IMergeViewerTestAdapter { + + /** + * Return the document for the given leg + * @param leg the leg (or side) + * @return the document for that leg of the comparison + */ + public IDocument getDocument(char leg); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java new file mode 100644 index 000000000..287a78c33 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 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.internal; + +import org.eclipse.ui.Saveable; + +/** + * Interface defines API for checking if an object, preferably an instance of + * {@link Saveable}, is being saved. + * + * @since 3.7 + */ +public interface ISavingSaveable { + public boolean isSaving(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java new file mode 100644 index 000000000..69cbbbc77 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.compare.CompareConfiguration; + +/** + * A factory object for creating a <code>Viewer</code>s from a descriptor. + * <p> + * It is used when registering a viewer for a specific type + * in <code>CompareUIPlugin.registerContentViewerDescriptor</code> and + * in <code>CompareUIPlugin.registerStructureViewerDescriptor</code>. + * + * @see org.eclipse.compare.structuremergeviewer.IStructureCreator + * @see CompareUIPlugin + */ +public interface IViewerDescriptor { + + /** + * Creates a new viewer from this descriptor under the given STW parent control. + * If the current viewer has the same type as a new viewer + * the implementation of this method is free to return the current viewer instead. + * + * @param currentViewer the current viewer which is going to be replaced with a new viewer. + * @param parent the SWT parent control under which the new viewer has to be created. + * @param config a compare configuration the new viewer might be interested in. + * @return a new viewer or the current viewer. + */ + Viewer createViewer(Viewer currentViewer, Composite parent, CompareConfiguration config); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java new file mode 100644 index 000000000..53d4165ac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A <code>Canvas</code> showing a single centered SWT <code>Image</code>. + * If the <code>Image</code> is larger than the <code>Canvas<code>, + * <code>Scrollbars</code> will appear. + */ +class ImageCanvas extends Canvas { + + private Image fImage; + + /* + * Create a new ImageCanvas with the given SWT stylebits. + * (SWT.H_SCROLL and SWT.V_SCROLL are automtically added). + */ + public ImageCanvas(Composite parent, int style) { + super(parent, style | SWT.H_SCROLL | SWT.V_SCROLL); + + ScrollBar sb= getHorizontalBar(); + sb.setIncrement(20); + sb.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + repaint(); + } + }); + + sb= getVerticalBar(); + sb.setIncrement(20); + sb.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + repaint(); + } + }); + + addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + updateScrollbars(); + } + }); + + addListener(SWT.Paint, new Listener() { + public void handleEvent(Event event) { + paint(event.gc); + } + }); + } + + /* + * Set the SWT Image to use as the ImageCanvas contents. + */ + public void setImage(Image img) { + fImage= img; + + if (!isDisposed()) { + getHorizontalBar().setSelection(0); + getVerticalBar().setSelection(0); + updateScrollbars(); + getParent().layout(); + redraw(); + } + } + + public void repaint() { + if (!isDisposed()) { + GC gc= new GC(this); + paint(gc); + gc.dispose(); + } + } + + void paint(GC gc) { + if (fImage != null) { + Rectangle bounds= fImage.getBounds(); + Rectangle clientArea= getClientArea(); + + int x; + if (bounds.width < clientArea.width) + x= (clientArea.width - bounds.width) / 2; + else + x= -getHorizontalBar().getSelection(); + + int y; + if (bounds.height < clientArea.height) + y= (clientArea.height - bounds.height) / 2; + else + y= -getVerticalBar().getSelection(); + + gc.drawImage(fImage, x, y); + } + } + + /** + * @private + */ + void updateScrollbars() { + Rectangle bounds= fImage != null ? fImage.getBounds() : new Rectangle(0, 0, 0, 0); + Point size= getSize(); + Rectangle clientArea= getClientArea(); + + ScrollBar horizontal= getHorizontalBar(); + if (bounds.width <= clientArea.width) { + horizontal.setVisible(false); + horizontal.setSelection(0); + } else { + horizontal.setPageIncrement(clientArea.width - horizontal.getIncrement()); + int max= bounds.width + (size.x - clientArea.width); + horizontal.setMaximum(max); + horizontal.setThumb(size.x > max ? max : size.x); + horizontal.setVisible(true); + } + + ScrollBar vertical= getVerticalBar(); + if (bounds.height <= clientArea.height) { + vertical.setVisible(false); + vertical.setSelection(0); + } else { + vertical.setPageIncrement(clientArea.height - vertical.getIncrement()); + int max= bounds.height + (size.y - clientArea.height); + vertical.setMaximum(max); + vertical.setThumb(size.y > max ? max : size.y); + vertical.setVisible(true); + } + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java new file mode 100644 index 000000000..c9cb53956 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.io.InputStream; +import java.io.IOException; +import java.util.ResourceBundle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.PlatformUI; +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.ContentMergeViewer; + +/** + */ +public class ImageMergeViewer extends ContentMergeViewer { + + private static final String BUNDLE_NAME= "org.eclipse.compare.internal.ImageMergeViewerResources"; //$NON-NLS-1$ + + private Object fLeftImage; + private Object fRightImage; + + private ImageCanvas fAncestor; + private ImageCanvas fLeft; + private ImageCanvas fRight; + + + public ImageMergeViewer(Composite parent, int styles, CompareConfiguration mp) { + super(styles, ResourceBundle.getBundle(BUNDLE_NAME), mp); + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICompareContextIds.IMAGE_COMPARE_VIEW); + + buildControl(parent); + String title= Utilities.getString(getResourceBundle(), "title"); //$NON-NLS-1$ + getControl().setData(CompareUI.COMPARE_VIEWER_TITLE, title); + } + + protected void updateContent(Object ancestor, Object left, Object right) { + + setInput(fAncestor, ancestor); + + fLeftImage= left; + setInput(fLeft, left); + + fRightImage= right; + setInput(fRight, right); + } + + /* + * We can't modify the contents of either side we just return null. + */ + protected byte[] getContents(boolean left) { + return null; + } + + public void createControls(Composite composite) { + fAncestor= new ImageCanvas(composite, SWT.NO_FOCUS); + fLeft= new ImageCanvas(composite, SWT.NO_FOCUS); + fRight= new ImageCanvas(composite, SWT.NO_FOCUS); + } + + private static void setInput(ImageCanvas canvas, Object input) { + if (canvas != null) { + + InputStream stream= null; + if (input instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) input; + if (sca != null) { + try { + stream= sca.getContents(); + } catch (CoreException ex) { + // NeedWork + } + } + } + + Image image= null; + Display display= canvas.getDisplay(); + if (stream != null) { + try { + image= new Image(display, stream); + } catch (SWTException ex) { + // silently ignored + } + } + + canvas.setImage(image); + if (image != null) { + canvas.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); + } else { + canvas.setBackground(null); + } + + if (stream != null) { + try { + stream.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + } + + protected void handleResizeAncestor(int x, int y, int width, int height) { + if (width > 0) { + fAncestor.setVisible(true); + fAncestor.setBounds(x, y, width, height); + } else { + fAncestor.setVisible(false); + } + } + + protected void handleResizeLeftRight(int x, int y, int width1, int centerWidth, int width2, int height) { + fLeft.setBounds(x, y, width1, height); + fRight.setBounds(x+width1+centerWidth, y, width2, height); + } + + protected void copy(boolean leftToRight) { + if (leftToRight) { + fRightImage= fLeftImage; + setInput(fRight, fRightImage); + setRightDirty(true); + } else { + fLeftImage= fRightImage; + setInput(fLeft, fLeftImage); + setLeftDirty(true); + } + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java new file mode 100644 index 000000000..d73a961c1 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.compare.*; +import org.eclipse.jface.viewers.Viewer; + +/** + * A factory object for the <code>ImageMergeViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class ImageMergeViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new ImageMergeViewer(parent, SWT.NULL, mp); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties new file mode 100644 index 000000000..7388f9c7a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties @@ -0,0 +1,32 @@ +############################################################################### +# Copyright (c) 2000, 2006 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 +############################################################################### + +# @(#)ImageMergeViewerResources.properties +# +# Resource strings for ImageMergeViewer.java + +title= Image Compare + +##################################################### +# Actions +##################################################### + +action.CopyLeftToRight.label= Copy Left to Right +action.CopyLeftToRight.tooltip= Copy Image from Left to Right +action.CopyLeftToRight.image= elcl16/copy_r_co.gif + +action.CopyRightToLeft.label= Copy Right to Left +action.CopyRightToLeft.tooltip= Copy Image from Right to Left +action.CopyRightToLeft.image= elcl16/copy_l_co.gif + +action.EnableAncestor.label= Enable Ancestor Pane +action.EnableAncestor.tooltip= Control Visibility of Ancestor Pane +action.EnableAncestor.image= elcl16/ancestorpane_co.gif diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java new file mode 100644 index 000000000..86a0d6d45 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.util.List; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * A specialized content provider to show a list of editor parts. + */ +public class ListContentProvider implements IStructuredContentProvider { + List fContents; + + public ListContentProvider() { + // nothing to do + } + + public Object[] getElements(Object input) { + if (fContents != null && fContents == input) + return fContents.toArray(); + return new Object[0]; + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof List) + fContents= (List)newInput; + else + fContents= null; + // we use a fixed set. + } + + public void dispose() { + // empty default implementation + } + + public boolean isDeleted(Object o) { + return fContents != null && !fContents.contains(o); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java new file mode 100644 index 000000000..ec99bd716 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java @@ -0,0 +1,1039 @@ +/******************************************************************************* + * 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 + * Max Weninger (max.weninger@windriver.com) - Bug 131895 [Edit] Undo in compare + * Max Weninger (max.weninger@windriver.com) - Bug 72936 [Viewers] Show line numbers in comparision + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ResourceBundle; + +import org.eclipse.compare.ICompareContainer; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.commands.operations.IOperationHistory; +import org.eclipse.core.commands.operations.IOperationHistoryListener; +import org.eclipse.core.commands.operations.IUndoContext; +import org.eclipse.core.commands.operations.OperationHistoryEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.action.GroupMarker; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextListener; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension5; +import org.eclipse.jface.text.IUndoManager; +import org.eclipse.jface.text.IUndoManagerExtension; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextEvent; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.ISharedTextColors; +import org.eclipse.jface.text.source.LineNumberRulerColumn; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IPropertyListener; +import org.eclipse.ui.IWorkbenchActionConstants; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.menus.CommandContributionItem; +import org.eclipse.ui.menus.CommandContributionItemParameter; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; +import org.eclipse.ui.texteditor.ChangeEncodingAction; +import org.eclipse.ui.texteditor.FindReplaceAction; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.IElementStateListener; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * Wraps a JFace SourceViewer and add some convenience methods. + */ +public class MergeSourceViewer implements ISelectionChangedListener, + ITextListener, IMenuListener, IOperationHistoryListener, IAdaptable { + + public static final String UNDO_ID= "undo"; //$NON-NLS-1$ + public static final String REDO_ID= "redo"; //$NON-NLS-1$ + public static final String CUT_ID= "cut"; //$NON-NLS-1$ + public static final String COPY_ID= "copy"; //$NON-NLS-1$ + public static final String PASTE_ID= "paste"; //$NON-NLS-1$ + public static final String DELETE_ID= "delete"; //$NON-NLS-1$ + public static final String SELECT_ALL_ID= "selectAll"; //$NON-NLS-1$ + public static final String FIND_ID= "find"; //$NON-NLS-1$ + public static final String GOTO_LINE_ID= "gotoLine"; //$NON-NLS-1$ + public static final String CHANGE_ENCODING_ID= "changeEncoding"; //$NON-NLS-1$ + + class TextOperationAction extends MergeViewerAction { + + private int fOperationCode; + + TextOperationAction(int operationCode, boolean mutable, boolean selection, boolean content) { + this(operationCode, null, mutable, selection, content); + } + + public TextOperationAction(int operationCode, String actionDefinitionId, boolean mutable, boolean selection, boolean content) { + super(mutable, selection, content); + if (actionDefinitionId != null) + setActionDefinitionId(actionDefinitionId); + fOperationCode= operationCode; + update(); + } + + public void run() { + if (isEnabled()) + getSourceViewer().doOperation(fOperationCode); + } + + public boolean isEnabled() { + return fOperationCode != -1 && getSourceViewer().canDoOperation(fOperationCode); + } + + public void update() { + this.setEnabled(isEnabled()); + } + } + + /** + * TODO: The only purpose of this class is to provide "Go to Line" action in + * TextMergeViewer. The adapter should be removed as soon as we implement + * embedded TextEditor in a similar way JDT has it done for Java compare. + */ + class TextEditorAdapter implements ITextEditor { + + public void close(boolean save) { + // defining interface method + } + + public void doRevertToSaved() { + // defining interface method + } + + public IAction getAction(String actionId) { + // defining interface method + return null; + } + + public IDocumentProvider getDocumentProvider() { + return new IDocumentProvider(){ + + public void aboutToChange(Object element) { + // defining interface method + } + + public void addElementStateListener( + IElementStateListener listener) { + // defining interface method + } + + public boolean canSaveDocument(Object element) { + // defining interface method + return false; + } + + public void changed(Object element) { + // defining interface method + } + + public void connect(Object element) throws CoreException { + // defining interface method + } + + public void disconnect(Object element) { + // defining interface method + } + + public IAnnotationModel getAnnotationModel(Object element) { + // defining interface method + return null; + } + + public IDocument getDocument(Object element) { + return MergeSourceViewer.this.getSourceViewer().getDocument(); + } + + public long getModificationStamp(Object element) { + // defining interface method + return 0; + } + + public long getSynchronizationStamp(Object element) { + // defining interface method + return 0; + } + + public boolean isDeleted(Object element) { + // defining interface method + return false; + } + + public boolean mustSaveDocument(Object element) { + // defining interface method + return false; + } + + public void removeElementStateListener( + IElementStateListener listener) { + // defining interface method + } + + public void resetDocument(Object element) throws CoreException { + // defining interface method + } + + public void saveDocument(IProgressMonitor monitor, + Object element, IDocument document, boolean overwrite) + throws CoreException { + // defining interface method + }}; + } + + public IRegion getHighlightRange() { + // defining interface method + return null; + } + + public ISelectionProvider getSelectionProvider() { + return MergeSourceViewer.this.getSourceViewer().getSelectionProvider(); + } + + public boolean isEditable() { + // defining interface method + return false; + } + + public void removeActionActivationCode(String actionId) { + // defining interface method + } + + public void resetHighlightRange() { + // defining interface method + } + + public void selectAndReveal(int start, int length) { + selectAndReveal(start, length, start, length); + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#selectAndReveal(int, int, int, int) + */ + private void selectAndReveal(int selectionStart, int selectionLength, int revealStart, int revealLength) { + + ISelection selection = getSelectionProvider().getSelection(); + if (selection instanceof ITextSelection) { + ITextSelection textSelection = (ITextSelection) selection; + if (textSelection.getOffset() != 0 || textSelection.getLength() != 0) + markInNavigationHistory(); + } + + StyledText widget= MergeSourceViewer.this.getSourceViewer().getTextWidget(); + widget.setRedraw(false); + { + adjustHighlightRange(revealStart, revealLength); + MergeSourceViewer.this.getSourceViewer().revealRange(revealStart, revealLength); + + MergeSourceViewer.this.getSourceViewer().setSelectedRange(selectionStart, selectionLength); + + markInNavigationHistory(); + } + widget.setRedraw(true); + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#markInNavigationHistory() + */ + private void markInNavigationHistory() { + getSite().getPage().getNavigationHistory().markLocation(this); + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#adjustHighlightRange(int, int) + */ + private void adjustHighlightRange(int offset, int length) { + + if (MergeSourceViewer.this instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5) MergeSourceViewer.this; + extension.exposeModelRange(new Region(offset, length)); + } else if (!isVisible(MergeSourceViewer.this.getSourceViewer(), offset, length)) { + MergeSourceViewer.this.getSourceViewer().resetVisibleRegion(); + } + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#isVisible(ISourceViewer, int, int) + */ + private /*static*/ final boolean isVisible(ITextViewer viewer, int offset, int length) { + if (viewer instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5) viewer; + IRegion overlap= extension.modelRange2WidgetRange(new Region(offset, length)); + return overlap != null; + } + return viewer.overlapsWithVisibleRegion(offset, length); + } + + public void setAction(String actionID, IAction action) { + // defining interface method + } + + public void setActionActivationCode(String actionId, + char activationCharacter, int activationKeyCode, + int activationStateMask) { + // defining interface method + } + + public void setHighlightRange(int offset, int length, boolean moveCursor) { + // defining interface method + } + + public void showHighlightRangeOnly(boolean showHighlightRangeOnly) { + // defining interface method + } + + public boolean showsHighlightRangeOnly() { + // defining interface method + return false; + } + + public IEditorInput getEditorInput() { + if (MergeSourceViewer.this.fContainer.getWorkbenchPart() instanceof IEditorPart) + return ((IEditorPart) MergeSourceViewer.this.fContainer.getWorkbenchPart()).getEditorInput(); + return null; + } + + public IEditorSite getEditorSite() { + // defining interface method + return null; + } + + public void init(IEditorSite site, IEditorInput input) + throws PartInitException { + // defining interface method + } + + public void addPropertyListener(IPropertyListener listener) { + // defining interface method + } + + public void createPartControl(Composite parent) { + // defining interface method + } + + public void dispose() { + // defining interface method + } + + public IWorkbenchPartSite getSite() { + return MergeSourceViewer.this.fContainer.getWorkbenchPart().getSite(); + } + + public String getTitle() { + // defining interface method + return null; + } + + public Image getTitleImage() { + // defining interface method + return null; + } + + public String getTitleToolTip() { + // defining interface method + return null; + } + + public void removePropertyListener(IPropertyListener listener) { + // defining interface method + } + + public void setFocus() { + // defining interface method + } + + public Object getAdapter(Class adapter) { + // defining interface method + return null; + } + + public void doSave(IProgressMonitor monitor) { + // defining interface method + } + + public void doSaveAs() { + // defining interface method + } + + public boolean isDirty() { + // defining interface method + return false; + } + + public boolean isSaveAsAllowed() { + // defining interface method + return false; + } + + public boolean isSaveOnCloseNeeded() { + // defining interface method + return false; + } + } + + private ResourceBundle fResourceBundle; + private ICompareContainer fContainer; + private SourceViewer fSourceViewer; + private Position fRegion; + private boolean fEnabled= true; + private HashMap fActions= new HashMap(); + private IDocument fRememberedDocument; + + private boolean fAddSaveAction= true; + private boolean isConfigured = false; + + // line number ruler support + private IPropertyChangeListener fPreferenceChangeListener; + private boolean fShowLineNumber=false; + private LineNumberRulerColumn fLineNumberColumn; + private List textActions = new ArrayList(); + private CommandContributionItem fSaveContributionItem; + + public MergeSourceViewer(SourceViewer sourceViewer, ResourceBundle bundle, ICompareContainer container) { + Assert.isNotNull(sourceViewer); + fSourceViewer= sourceViewer; + fResourceBundle= bundle; + fContainer = container; + + MenuManager menu= new MenuManager(); + menu.setRemoveAllWhenShown(true); + menu.addMenuListener(this); + StyledText te= getSourceViewer().getTextWidget(); + te.setMenu(menu.createContextMenu(te)); + fContainer.registerContextMenu(menu, getSourceViewer()); + + // for listening to editor show/hide line number preference value + fPreferenceChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + MergeSourceViewer.this.handlePropertyChangeEvent(event); + } + }; + EditorsUI.getPreferenceStore().addPropertyChangeListener(fPreferenceChangeListener); + fShowLineNumber= EditorsUI.getPreferenceStore().getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); + if(fShowLineNumber){ + updateLineNumberRuler(); + } + + IOperationHistory history = getHistory(); + if (history != null) + history.addOperationHistoryListener(this); + + // don't add save when in a dialog, IWorkbenchPart is null in dialog containers + fAddSaveAction = fContainer.getWorkbenchPart() != null; + } + + public void rememberDocument(IDocument doc) { +// if (doc != null && fRememberedDocument != null) { +// System.err.println("MergeSourceViewer.rememberDocument: fRememberedDocument != null: shouldn't happen"); //$NON-NLS-1$ +// } + fRememberedDocument= doc; + } + + public IDocument getRememberedDocument() { + return fRememberedDocument; + } + + public void hideSaveAction() { + fAddSaveAction= false; + } + + public void setFont(Font font) { + StyledText te= getSourceViewer().getTextWidget(); + if (te != null) + te.setFont(font); + if (fLineNumberColumn != null) { + fLineNumberColumn.setFont(font); + layoutViewer(); + } + } + + public void setBackgroundColor(Color color) { + StyledText te= getSourceViewer().getTextWidget(); + if (te != null) + te.setBackground(color); + if (fLineNumberColumn != null) + fLineNumberColumn.setBackground(color); + } + + public void setForegroundColor(Color color) { + StyledText te= getSourceViewer().getTextWidget(); + if (te != null) + te.setForeground(color); + } + + public void setEnabled(boolean enabled) { + if (enabled != fEnabled) { + fEnabled= enabled; + StyledText c= getSourceViewer().getTextWidget(); + if (c != null) { + c.setEnabled(enabled); + Display d= c.getDisplay(); + c.setBackground(enabled ? d.getSystemColor(SWT.COLOR_LIST_BACKGROUND) : null); + } + } + } + + public boolean getEnabled() { + return fEnabled; + } + + public void setRegion(Position region) { + fRegion= region; + } + + public Position getRegion() { + return fRegion; + } + + public boolean isControlOkToUse() { + StyledText t= getSourceViewer().getTextWidget(); + return t != null && !t.isDisposed(); + } + + public void setSelection(Position position) { + if (position != null) + getSourceViewer().setSelectedRange(position.getOffset(), position.getLength()); + } + + public void setLineBackground(Position position, Color c) { + StyledText t= getSourceViewer().getTextWidget(); + if (t != null && !t.isDisposed()) { + Point region= new Point(0, 0); + getLineRange(position, region); + + region.x-= getDocumentRegionOffset(); + + try { + t.setLineBackground(region.x, region.y, c); + } catch (IllegalArgumentException ex) { + // silently ignored + } + } + } + + public void resetLineBackground() { + StyledText t= getSourceViewer().getTextWidget(); + if (t != null && !t.isDisposed()) { + int lines= getLineCount(); + t.setLineBackground(0, lines, null); + } + } + + /* + * Returns number of lines in document region. + */ + public int getLineCount() { + IRegion region= getSourceViewer().getVisibleRegion(); + + int length= region.getLength(); + if (length == 0) + return 0; + + IDocument doc= getSourceViewer().getDocument(); + int startLine= 0; + int endLine= 0; + + int start= region.getOffset(); + try { + startLine= doc.getLineOfOffset(start); + } catch(BadLocationException ex) { + // silently ignored + } + try { + endLine= doc.getLineOfOffset(start+length); + } catch(BadLocationException ex) { + // silently ignored + } + + return endLine-startLine+1; + } + + public int getViewportLines() { + StyledText te= getSourceViewer().getTextWidget(); + Rectangle clArea= te.getClientArea(); + if (!clArea.isEmpty()) + return clArea.height / te.getLineHeight(); + return 0; + } + + public int getViewportHeight() { + StyledText te= getSourceViewer().getTextWidget(); + Rectangle clArea= te.getClientArea(); + if (!clArea.isEmpty()) + return clArea.height; + return 0; + } + + /* + * Returns lines + */ + public int getDocumentRegionOffset() { + int start= getSourceViewer().getVisibleRegion().getOffset(); + IDocument doc= getSourceViewer().getDocument(); + if (doc != null) { + try { + return doc.getLineOfOffset(start); + } catch(BadLocationException ex) { + // silently ignored + } + } + return 0; + } + + public int getVerticalScrollOffset() { + StyledText st= getSourceViewer().getTextWidget(); + int lineHeight= st.getLineHeight(); + return getSourceViewer().getTopInset() - ((getDocumentRegionOffset()*lineHeight) + st.getTopPixel()); + } + + /* + * Returns the start line and the number of lines which correspond to the given position. + * Starting line number is 0 based. + */ + public Point getLineRange(Position p, Point region) { + + IDocument doc= getSourceViewer().getDocument(); + + if (p == null || doc == null) { + region.x= 0; + region.y= 0; + return region; + } + + int start= p.getOffset(); + int length= p.getLength(); + + int startLine= 0; + try { + startLine= doc.getLineOfOffset(start); + } catch (BadLocationException e) { + // silently ignored + } + + int lineCount= 0; + + if (length == 0) { +// // if range length is 0 and if range starts a new line +// try { +// if (start == doc.getLineStartOffset(startLine)) { +// lines--; +// } +// } catch (BadLocationException e) { +// lines--; +// } + + } else { + int endLine= 0; + try { + endLine= doc.getLineOfOffset(start + length - 1); // why -1? + } catch (BadLocationException e) { + // silently ignored + } + lineCount= endLine-startLine+1; + } + + region.x= startLine; + region.y= lineCount; + return region; + } + + /* + * Scroll TextPart to the given line. + */ + public void vscroll(int line) { + + int srcViewSize= getLineCount(); + int srcExtentSize= getViewportLines(); + + if (srcViewSize > srcExtentSize) { + + if (line < 0) + line= 0; + + int cp= getSourceViewer().getTopIndex(); + if (cp != line) + getSourceViewer().setTopIndex(line + getDocumentRegionOffset()); + } + } + + public void addAction(String actionId, MergeViewerAction action) { + fActions.put(actionId, action); + } + + public IAction getAction(String actionId) { + IAction action= (IAction) fActions.get(actionId); + if (action == null) { + action= createAction(actionId); + if (action == null) + return null; + if (action instanceof MergeViewerAction) { + MergeViewerAction mva = (MergeViewerAction) action; + if (mva.isContentDependent()) + getSourceViewer().addTextListener(this); + if (mva.isSelectionDependent()) + getSourceViewer().addSelectionChangedListener(this); + + Utilities.initAction(action, fResourceBundle, "action." + actionId + "."); //$NON-NLS-1$ //$NON-NLS-2$ + } + addAction(actionId, action); + + } + if (action instanceof MergeViewerAction) { + MergeViewerAction mva = (MergeViewerAction) action; + if (mva.isEditableDependent() && !getSourceViewer().isEditable()) + return null; + } + return action; + } + + protected IAction createAction(String actionId) { + if (UNDO_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.UNDO, IWorkbenchCommandConstants.EDIT_UNDO, true, false, true); + if (REDO_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.REDO, IWorkbenchCommandConstants.EDIT_REDO, true, false, true); + if (CUT_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.CUT, IWorkbenchCommandConstants.EDIT_CUT, true, true, false); + if (COPY_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.COPY, IWorkbenchCommandConstants.EDIT_COPY, false, true, false); + if (PASTE_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.PASTE, IWorkbenchCommandConstants.EDIT_PASTE, true, false, false); + if (DELETE_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.DELETE, IWorkbenchCommandConstants.EDIT_DELETE, true, false, false); + if (SELECT_ALL_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.SELECT_ALL, IWorkbenchCommandConstants.EDIT_SELECT_ALL, false, false, false); + return null; + } + + public void selectionChanged(SelectionChangedEvent event) { + Iterator e= fActions.values().iterator(); + while (e.hasNext()) { + Object next = e.next(); + if (next instanceof MergeViewerAction) { + MergeViewerAction action = (MergeViewerAction) next; + if (action.isSelectionDependent()) + action.update(); + } + } + } + + public void textChanged(TextEvent event) { + updateContentDependantActions(); + } + + void updateContentDependantActions() { + Iterator e= fActions.values().iterator(); + while (e.hasNext()) { + Object next = e.next(); + if (next instanceof MergeViewerAction) { + MergeViewerAction action = (MergeViewerAction) next; + if (action.isContentDependent()) + action.update(); + } + } + } + + /* + * Allows the viewer to add menus and/or tools to the context menu. + */ + public void menuAboutToShow(IMenuManager menu) { + + menu.add(new Separator("undo")); //$NON-NLS-1$ + addMenu(menu, UNDO_ID); + addMenu(menu, REDO_ID); + menu.add(new GroupMarker("save")); //$NON-NLS-1$ + if (fAddSaveAction) + addSave(menu); + menu.add(new Separator("file")); //$NON-NLS-1$ + + menu.add(new Separator("ccp")); //$NON-NLS-1$ + addMenu(menu, CUT_ID); + addMenu(menu, COPY_ID); + addMenu(menu, PASTE_ID); + addMenu(menu, DELETE_ID); + addMenu(menu, SELECT_ALL_ID); + + menu.add(new Separator("edit")); //$NON-NLS-1$ + addMenu(menu, CHANGE_ENCODING_ID); + menu.add(new Separator("find")); //$NON-NLS-1$ + addMenu(menu, FIND_ID); + + menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); + + menu.add(new Separator("text")); //$NON-NLS-1$ + for (Iterator iterator = textActions.iterator(); iterator.hasNext();) { + IAction action = (IAction) iterator.next(); + menu.add(action); + } + + menu.add(new Separator("rest")); //$NON-NLS-1$ + + // update all actions + // to get undo redo right + updateActions(); + } + + private void addMenu(IMenuManager menu, String actionId) { + IAction action= getAction(actionId); + if (action != null) + menu.add(action); + } + + private void addSave(IMenuManager menu) { + ICommandService commandService = (ICommandService) fContainer.getWorkbenchPart().getSite().getService(ICommandService.class); + final Command command= commandService.getCommand(IWorkbenchCommandConstants.FILE_SAVE); + + final IHandler handler = command.getHandler(); + if (handler != null) { + if (fSaveContributionItem == null) { + fSaveContributionItem = new CommandContributionItem( + new CommandContributionItemParameter(fContainer + .getWorkbenchPart().getSite(), null, command + .getId(), CommandContributionItem.STYLE_PUSH)); + } + // save is an editable dependent action, ie add only when edit + // is possible + if (handler.isHandled() && getSourceViewer().isEditable()) + menu.add(fSaveContributionItem); + } + } + + /** + * The viewer is no longer part of the UI, it's a wrapper only. The disposal + * doesn't take place while releasing the editor pane's children. The method + * have to be called it manually. The wrapped viewer is disposed as a + * regular viewer, while disposing the UI. + */ + public void dispose() { + getSourceViewer().removeTextListener(this); + getSourceViewer().removeSelectionChangedListener(this); + EditorsUI.getPreferenceStore().removePropertyChangeListener(fPreferenceChangeListener); + + IOperationHistory history = getHistory(); + if (history != null) + history.removeOperationHistoryListener(this); + } + + /** + * update all actions independent of their type + * + */ + public void updateActions() { + Iterator e= fActions.values().iterator(); + while (e.hasNext()) { + Object next = e.next(); + if (next instanceof MergeViewerAction) { + MergeViewerAction action = (MergeViewerAction) next; + action.update(); + } else if (next instanceof FindReplaceAction) { + FindReplaceAction action = (FindReplaceAction) next; + action.update(); + } else if (next instanceof ChangeEncodingAction) { + ChangeEncodingAction action = (ChangeEncodingAction) next; + action.update(); + } + } + } + + public void configure(SourceViewerConfiguration configuration) { + if (isConfigured ) + getSourceViewer().unconfigure(); + isConfigured = true; + getSourceViewer().configure(configuration); + } + + /** + * specific implementation to support a vertical ruler + * @param x + * @param y + * @param width + * @param height + */ + public void setBounds (int x, int y, int width, int height) { + if(getSourceViewer().getControl() instanceof Composite){ + ((Composite)getSourceViewer().getControl()).setBounds(x, y, width, height); + } else { + getSourceViewer().getTextWidget().setBounds(x, y, width, height); + } + } + + /** + * handle show/hide line numbers from editor preferences + * @param event + */ + protected void handlePropertyChangeEvent(PropertyChangeEvent event) { + + String key= event.getProperty(); + + if(key.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER)){ + boolean b= EditorsUI.getPreferenceStore().getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); + if (b != fShowLineNumber){ + toggleLineNumberRuler(); + } + } else if (key.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR)) { + updateLineNumberColumnPresentation(true); + } + } + + /** + * Hides or shows line number ruler column based of preference setting + */ + private void updateLineNumberRuler() { + if (!fShowLineNumber) { + if (fLineNumberColumn != null) { + getSourceViewer().removeVerticalRulerColumn(fLineNumberColumn); + } + } else { + if (fLineNumberColumn == null) { + fLineNumberColumn = new LineNumberRulerColumn(); + updateLineNumberColumnPresentation(false); + } + getSourceViewer().addVerticalRulerColumn(fLineNumberColumn); + } + } + + private void updateLineNumberColumnPresentation(boolean refresh) { + if (fLineNumberColumn == null) + return; + RGB rgb= getColorFromStore(EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR); + if (rgb == null) + rgb= new RGB(0, 0, 0); + ISharedTextColors sharedColors= getSharedColors(); + fLineNumberColumn.setForeground(sharedColors.getColor(rgb)); + if (refresh) { + fLineNumberColumn.redraw(); + } + } + + private void layoutViewer() { + Control parent= getSourceViewer().getControl(); + if (parent instanceof Composite && !parent.isDisposed()) + ((Composite) parent).layout(true); + } + + private ISharedTextColors getSharedColors() { + return EditorsUI.getSharedTextColors(); + } + + private RGB getColorFromStore(IPreferenceStore store, String key) { + RGB rgb= null; + if (store.contains(key)) { + if (store.isDefault(key)) + rgb= PreferenceConverter.getDefaultColor(store, key); + else + rgb= PreferenceConverter.getColor(store, key); + } + return rgb; + } + + /** + * Toggles line number ruler column. + */ + private void toggleLineNumberRuler() + { + fShowLineNumber=!fShowLineNumber; + + updateLineNumberRuler(); + } + + public void addTextAction(IAction textEditorPropertyAction) { + textActions.add(textEditorPropertyAction); + } + + public void addAction(String id, IAction action) { + fActions.put(id, action); + } + + private IOperationHistory getHistory() { + if (PlatformUI.getWorkbench() == null) { + return null; + } + return PlatformUI.getWorkbench().getOperationSupport() + .getOperationHistory(); + } + + public void historyNotification(OperationHistoryEvent event) { + // This method updates the enablement of all content operations + // when the undo history changes. It could be localized to UNDO and REDO. + IUndoContext context = getUndoContext(); + if (context != null && event.getOperation().hasContext(context)) { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + updateContentDependantActions(); + } + }); + } + } + + private IUndoContext getUndoContext() { + IUndoManager undoManager = getSourceViewer().getUndoManager(); + if (undoManager instanceof IUndoManagerExtension) + return ((IUndoManagerExtension)undoManager).getUndoContext(); + return null; + } + + /** + * @return the wrapped viewer + */ + public SourceViewer getSourceViewer() { + return fSourceViewer; + } + + public Object getAdapter(Class adapter) { + if (adapter == ITextEditor.class) { + return new TextEditorAdapter(); + } + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java new file mode 100644 index 000000000..b1b579c8e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.ui.texteditor.IUpdate; +import org.eclipse.jface.action.Action; + + +public abstract class MergeViewerAction extends Action implements IUpdate { + + private boolean fMutable; + private boolean fSelection; + private boolean fContent; + + public MergeViewerAction(boolean mutable, boolean selection, boolean content) { + fMutable= mutable; + fSelection= selection; + fContent= content; + } + + public boolean isSelectionDependent() { + return fSelection; + } + + public boolean isContentDependent() { + return fContent; + } + + public boolean isEditableDependent() { + return fMutable; + } + + public void update() { + // empty default implementation + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java new file mode 100644 index 000000000..a46445a28 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider; +import org.eclipse.compare.structuremergeviewer.*; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; + +/** + * Adapts any <code>ContentMergeViewer</code> to work on an <code>ICompareInput</code> + * e.g. a <code>DiffNode</code>. + */ +public class MergeViewerContentProvider implements IMergeViewerContentProvider { + + public static final char ANCESTOR_CONTRIBUTOR = 'A'; + public static final char RIGHT_CONTRIBUTOR = 'R'; + public static final char LEFT_CONTRIBUTOR = 'L'; + + private CompareConfiguration fCompareConfiguration; + private String fAncestorError; + private String fLeftError; + private String fRightError; + + public MergeViewerContentProvider(CompareConfiguration cc) { + fCompareConfiguration= cc; + } + + private boolean hasError() { + return fAncestorError != null || fLeftError != null || fRightError != null; + } + + public void dispose() { + // empty default implementation + } + + public void inputChanged(Viewer v, Object o1, Object o2) { + // we are not interested since we have no state + } + + //---- ancestor + + public void setAncestorError(String errorMessage) { + fAncestorError= errorMessage; + } + + public String getAncestorLabel(Object element) { + if (fAncestorError != null) + return fAncestorError; + return fCompareConfiguration.getAncestorLabel(element); + } + + public Image getAncestorImage(Object element) { + if (fAncestorError != null) + return null; + return fCompareConfiguration.getAncestorImage(element); + } + + public Object getAncestorContent(Object element) { + if (element instanceof ICompareInput) + return ((ICompareInput) element).getAncestor(); + return null; + } + + public boolean showAncestor(Object element) { + if (element instanceof ICompareInput) + return true; // fix for #45239: Show ancestor for incoming and outgoing changes + //return (((ICompareInput)element).getKind() & Differencer.DIRECTION_MASK) == Differencer.CONFLICTING; + return false; + } + + //---- left + + public void setLeftError(String errorMessage) { + fLeftError= errorMessage; + } + + public String getLeftLabel(Object element) { + if (fLeftError != null) + return fLeftError; + return fCompareConfiguration.getLeftLabel(element); + } + + public Image getLeftImage(Object element) { + if (fLeftError != null) + return null; + return fCompareConfiguration.getLeftImage(element); + } + + public Object getLeftContent(Object element) { + if (element instanceof ICompareInput) + return ((ICompareInput) element).getLeft(); + return null; + } + + public boolean isLeftEditable(Object element) { + if (hasError()) + return false; + if (element instanceof ICompareInput) { + Object left= ((ICompareInput) element).getLeft(); + if (left == null && element instanceof IDiffElement) { + IDiffElement parent= ((IDiffElement)element).getParent(); + if (parent instanceof ICompareInput) + left= ((ICompareInput) parent).getLeft(); + } + if (left instanceof IEditableContent) + return ((IEditableContent)left).isEditable(); + } + return false; + } + + public void saveLeftContent(Object element, byte[] bytes) { + if (element instanceof ICompareInput) { + ICompareInput node= (ICompareInput) element; + if (bytes != null) { + ITypedElement left= node.getLeft(); + // #9869: problem if left is null (because no resource exists yet) nothing is done! + if (left == null) { + node.copy(false); + left= node.getLeft(); + } + if (left instanceof IEditableContent) + ((IEditableContent)left).setContent(bytes); + if (node instanceof ResourceCompareInput.MyDiffNode) + ((ResourceCompareInput.MyDiffNode)node).fireChange(); + } else { + node.copy(false); + } + } + } + + //---- right + + public void setRightError(String errorMessage) { + fRightError= errorMessage; + } + + public String getRightLabel(Object element) { + if (fRightError != null) + return fRightError; + return fCompareConfiguration.getRightLabel(element); + } + + public Image getRightImage(Object element) { + if (fRightError != null) + return null; + return fCompareConfiguration.getRightImage(element); + } + + public Object getRightContent(Object element) { + if (element instanceof ICompareInput) + return ((ICompareInput) element).getRight(); + return null; + } + + public boolean isRightEditable(Object element) { + if (hasError()) + return false; + if (element instanceof ICompareInput) { + Object right= ((ICompareInput) element).getRight(); + if (right == null && element instanceof IDiffElement) { + IDiffContainer parent= ((IDiffElement)element).getParent(); + if (parent instanceof ICompareInput) + right= ((ICompareInput) parent).getRight(); + } + if (right instanceof IEditableContent) + return ((IEditableContent)right).isEditable(); + } + return false; + } + + public void saveRightContent(Object element, byte[] bytes) { + if (element instanceof ICompareInput) { + ICompareInput node= (ICompareInput) element; + if (bytes != null) { + ITypedElement right= node.getRight(); + // #9869: problem if right is null (because no resource exists yet) nothing is done! + if (right == null) { + node.copy(true); + right= node.getRight(); + } + if (right instanceof IEditableContent) + ((IEditableContent)right).setContent(bytes); + if (node instanceof ResourceCompareInput.MyDiffNode) + ((ResourceCompareInput.MyDiffNode)node).fireChange(); + } else { + node.copy(true); + } + } + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java new file mode 100644 index 000000000..3ca58fe91 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2006 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.internal; + +import org.eclipse.jface.dialogs.*; +import org.eclipse.jface.preference.RadioGroupFieldEditor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.*; + +public class NavigationEndDialog extends MessageDialogWithToggle { + + private final String[][] labelsAndValues; + private RadioGroupFieldEditor editor; + + public NavigationEndDialog(Shell parentShell, String dialogTitle, + Image dialogTitleImage, String dialogMessage, String[][] labelsAndValues) { + super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, + QUESTION, new String[] { IDialogConstants.OK_LABEL , IDialogConstants.CANCEL_LABEL}, 0, + CompareMessages.NavigationEndDialog_0, false); + this.labelsAndValues = labelsAndValues; + } + + protected Control createCustomArea(Composite parent) { + editor = new RadioGroupFieldEditor(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, CompareMessages.NavigationEndDialog_1, 1, + labelsAndValues, + parent, true); + editor.setPreferenceStore(CompareUIPlugin.getDefault().getPreferenceStore()); + editor.fillIntoGrid(parent, 1); + editor.load(); + return parent; + } + + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + editor.store(); + } + super.buttonPressed(buttonId); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java new file mode 100644 index 000000000..f9fce2b90 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.*; + +import org.eclipse.compare.CompareViewerPane; + +/** + * Used whenever the input is null or no viewer can be found. + */ +public class NullViewer extends AbstractViewer { + + private Control fDummy; + + public NullViewer(Composite parent) { + + fDummy= new Tree(parent, SWT.NULL); + + CompareViewerPane.clearToolBar(parent); + } + + public Control getControl() { + return fDummy; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java new file mode 100644 index 000000000..ee303531c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2007 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.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.widgets.Composite; + +/** + * Class which allows content merge viewer to provide a structure viewer that can be used in the outline + * view. + */ +public abstract class OutlineViewerCreator { + + /** + * Property constant that identifies the input of the outline view. + */ + public static final String PROP_INPUT = "org.eclipse.compare.OutlineInput"; //$NON-NLS-1$ + + private ListenerList listeners = new ListenerList(ListenerList.IDENTITY); + + /** + * Method called by the editor to create a structure viewer for the current content merge viewer. + * @param oldViewer the current viewer that is being used to show the structure + * @param input the input + * @param parent the parent composite + * @param configuration the compare configuration + * @return a viewer to be placed in the outline viewer or <code>null</code> + */ + public abstract Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, + Composite parent, CompareConfiguration configuration); + + public abstract boolean hasViewerFor(Object input); + + public void addPropertyChangeListener(IPropertyChangeListener listener) { + listeners.add(listener); + } + + public void removePropertyChangeListener(IPropertyChangeListener listener) { + listeners.remove(listener); + } + + public void fireInputChange(Object oldInput, Object newInput) { + Object[] list = listeners.getListeners(); + final PropertyChangeEvent event = new PropertyChangeEvent(this, PROP_INPUT, oldInput, newInput); + for (int i = 0; i < list.length; i++) { + final IPropertyChangeListener listener = (IPropertyChangeListener)list[i]; + SafeRunner.run(new ISafeRunnable() { + public void run() throws Exception { + listener.propertyChange(event); + } + public void handleException(Throwable exception) { + // Logged by SafeRunner + } + }); + } + } + + public abstract Object getInput(); + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java new file mode 100644 index 000000000..de7ba253a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java @@ -0,0 +1,452 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +/** + * An overlaying preference store. + */ +public class OverlayPreferenceStore implements IPreferenceStore { + + + public static final class TypeDescriptor { + private TypeDescriptor() { + // nothing to do + } + } + + public static final TypeDescriptor BOOLEAN= new TypeDescriptor(); + public static final TypeDescriptor DOUBLE= new TypeDescriptor(); + public static final TypeDescriptor FLOAT= new TypeDescriptor(); + public static final TypeDescriptor INT= new TypeDescriptor(); + public static final TypeDescriptor LONG= new TypeDescriptor(); + public static final TypeDescriptor STRING= new TypeDescriptor(); + + public static class OverlayKey { + + TypeDescriptor fDescriptor; + String fKey; + + public OverlayKey(TypeDescriptor descriptor, String key) { + fDescriptor= descriptor; + fKey= key; + } + } + + private class PropertyListener implements IPropertyChangeListener { + + /* + * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + OverlayKey key= findOverlayKey(event.getProperty()); + if (key != null) + propagateProperty(fParent, key, fStore); + } + } + + + private IPreferenceStore fParent; + private IPreferenceStore fStore; + private OverlayKey[] fOverlayKeys; + + private PropertyListener fPropertyListener; + + + public OverlayPreferenceStore(IPreferenceStore parent, OverlayKey[] overlayKeys) { + fParent= parent; + fOverlayKeys= overlayKeys; + fStore= new PreferenceStore(); + } + + private OverlayKey findOverlayKey(String key) { + for (int i= 0; i < fOverlayKeys.length; i++) { + if (fOverlayKeys[i].fKey.equals(key)) + return fOverlayKeys[i]; + } + return null; + } + + private boolean covers(String key) { + return (findOverlayKey(key) != null); + } + + private void propagateProperty(IPreferenceStore orgin, OverlayKey key, IPreferenceStore target) { + + if (orgin.isDefault(key.fKey)) { + if (!target.isDefault(key.fKey)) + target.setToDefault(key.fKey); + return; + } + + TypeDescriptor d= key.fDescriptor; + if (BOOLEAN == d) { + + boolean originValue= orgin.getBoolean(key.fKey); + boolean targetValue= target.getBoolean(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (DOUBLE == d) { + + double originValue= orgin.getDouble(key.fKey); + double targetValue= target.getDouble(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (FLOAT == d) { + + float originValue= orgin.getFloat(key.fKey); + float targetValue= target.getFloat(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (INT == d) { + + int originValue= orgin.getInt(key.fKey); + int targetValue= target.getInt(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (LONG == d) { + + long originValue= orgin.getLong(key.fKey); + long targetValue= target.getLong(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (STRING == d) { + + String originValue= orgin.getString(key.fKey); + String targetValue= target.getString(key.fKey); + if (targetValue != null && originValue != null && !targetValue.equals(originValue)) + target.setValue(key.fKey, originValue); + + } + } + + public void propagate() { + for (int i= 0; i < fOverlayKeys.length; i++) + propagateProperty(fStore, fOverlayKeys[i], fParent); + } + + private void loadProperty(IPreferenceStore orgin, OverlayKey key, IPreferenceStore target, boolean forceInitialization) { + TypeDescriptor d= key.fDescriptor; + if (BOOLEAN == d) { + + if (forceInitialization) + target.setValue(key.fKey, true); + target.setValue(key.fKey, orgin.getBoolean(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultBoolean(key.fKey)); + + } else if (DOUBLE == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1.0D); + target.setValue(key.fKey, orgin.getDouble(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultDouble(key.fKey)); + + } else if (FLOAT == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1.0F); + target.setValue(key.fKey, orgin.getFloat(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultFloat(key.fKey)); + + } else if (INT == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1); + target.setValue(key.fKey, orgin.getInt(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultInt(key.fKey)); + + } else if (LONG == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1L); + target.setValue(key.fKey, orgin.getLong(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultLong(key.fKey)); + + } else if (STRING == d) { + + if (forceInitialization) + target.setValue(key.fKey, "1"); //$NON-NLS-1$ + target.setValue(key.fKey, orgin.getString(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultString(key.fKey)); + + } + } + + public void load() { + for (int i= 0; i < fOverlayKeys.length; i++) + loadProperty(fParent, fOverlayKeys[i], fStore, true); + } + + public void loadDefaults() { + for (int i= 0; i < fOverlayKeys.length; i++) + setToDefault(fOverlayKeys[i].fKey); + } + + public void start() { + if (fPropertyListener == null) { + fPropertyListener= new PropertyListener(); + fParent.addPropertyChangeListener(fPropertyListener); + } + } + + public void stop() { + if (fPropertyListener != null) { + fParent.removePropertyChangeListener(fPropertyListener); + fPropertyListener= null; + } + } + + /* + * @see IPreferenceStore#addPropertyChangeListener(IPropertyChangeListener) + */ + public void addPropertyChangeListener(IPropertyChangeListener listener) { + fStore.addPropertyChangeListener(listener); + } + + /* + * @see IPreferenceStore#removePropertyChangeListener(IPropertyChangeListener) + */ + public void removePropertyChangeListener(IPropertyChangeListener listener) { + fStore.removePropertyChangeListener(listener); + } + + /* + * @see IPreferenceStore#firePropertyChangeEvent(String, Object, Object) + */ + public void firePropertyChangeEvent(String name, Object oldValue, Object newValue) { + fStore.firePropertyChangeEvent(name, oldValue, newValue); + } + + /* + * @see IPreferenceStore#contains(String) + */ + public boolean contains(String name) { + return fStore.contains(name); + } + + /* + * @see IPreferenceStore#getBoolean(String) + */ + public boolean getBoolean(String name) { + return fStore.getBoolean(name); + } + + /* + * @see IPreferenceStore#getDefaultBoolean(String) + */ + public boolean getDefaultBoolean(String name) { + return fStore.getDefaultBoolean(name); + } + + /* + * @see IPreferenceStore#getDefaultDouble(String) + */ + public double getDefaultDouble(String name) { + return fStore.getDefaultDouble(name); + } + + /* + * @see IPreferenceStore#getDefaultFloat(String) + */ + public float getDefaultFloat(String name) { + return fStore.getDefaultFloat(name); + } + + /* + * @see IPreferenceStore#getDefaultInt(String) + */ + public int getDefaultInt(String name) { + return fStore.getDefaultInt(name); + } + + /* + * @see IPreferenceStore#getDefaultLong(String) + */ + public long getDefaultLong(String name) { + return fStore.getDefaultLong(name); + } + + /* + * @see IPreferenceStore#getDefaultString(String) + */ + public String getDefaultString(String name) { + return fStore.getDefaultString(name); + } + + /* + * @see IPreferenceStore#getDouble(String) + */ + public double getDouble(String name) { + return fStore.getDouble(name); + } + + /* + * @see IPreferenceStore#getFloat(String) + */ + public float getFloat(String name) { + return fStore.getFloat(name); + } + + /* + * @see IPreferenceStore#getInt(String) + */ + public int getInt(String name) { + return fStore.getInt(name); + } + + /* + * @see IPreferenceStore#getLong(String) + */ + public long getLong(String name) { + return fStore.getLong(name); + } + + /* + * @see IPreferenceStore#getString(String) + */ + public String getString(String name) { + return fStore.getString(name); + } + + /* + * @see IPreferenceStore#isDefault(String) + */ + public boolean isDefault(String name) { + return fStore.isDefault(name); + } + + /* + * @see IPreferenceStore#needsSaving() + */ + public boolean needsSaving() { + return fStore.needsSaving(); + } + + /* + * @see IPreferenceStore#putValue(String, String) + */ + public void putValue(String name, String value) { + if (covers(name)) + fStore.putValue(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, double) + */ + public void setDefault(String name, double value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, float) + */ + public void setDefault(String name, float value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, int) + */ + public void setDefault(String name, int value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, long) + */ + public void setDefault(String name, long value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, String) + */ + public void setDefault(String name, String value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, boolean) + */ + public void setDefault(String name, boolean value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setToDefault(String) + */ + public void setToDefault(String name) { + fStore.setToDefault(name); + } + + /* + * @see IPreferenceStore#setValue(String, double) + */ + public void setValue(String name, double value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, float) + */ + public void setValue(String name, float value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, int) + */ + public void setValue(String name, int value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, long) + */ + public void setValue(String name, long value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, String) + */ + public void setValue(String name, String value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, boolean) + */ + public void setValue(String name, boolean value) { + if (covers(name)) + fStore.setValue(name, value); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java new file mode 100644 index 000000000..4d5eca397 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + + +public class ReplaceWithEditionAction extends EditionAction { + + public ReplaceWithEditionAction() { + super(true, "org.eclipse.compare.internal.ReplaceWithEditionAction"); //$NON-NLS-1$ + fHelpContextId= ICompareContextIds.REPLACE_WITH_EDITION_DIALOG; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties new file mode 100644 index 000000000..94d650eac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties @@ -0,0 +1,40 @@ +############################################################################### +# Copyright (c) 2000, 2006 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 +############################################################################### + +# @(#)ReplaceWithEditionAction.properties +# +# Resources for ReplaceWithEditionAction.java + +title= Replace from Local History + +treeTitleFormat= Local History of ''{0}'' +dateIcon= obj16/day_obj.gif +timeIcon= obj16/resource_obj.gif + +treeFormat= {0} +workspaceTreeFormat= {0} (Workspace File) +parseErrorFormat= {0} (Parse Error) + +editionLabel= Local History ({0}) +workspaceEditionLabel= Workspace File + +targetLabel= {0} + +todayFormat= Today ({0}) +yesterdayFormat= Yesterday ({0}) +dayFormat= {0} + +buttonLabel= Replace + +noLocalHistoryError= No local history available for selected resource. +replaceError=Cannot replace resource (reason: {0}). + +taskName=Replacing diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java new file mode 100644 index 000000000..ecd4e87be --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + + +public class ReplaceWithPreviousEditionAction extends EditionAction { + + public ReplaceWithPreviousEditionAction() { + super(true, + "org.eclipse.compare.internal.ReplaceWithEditionAction"); //$NON-NLS-1$ + fPrevious= true; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java new file mode 100644 index 000000000..d18bf6c7c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import java.util.ResourceBundle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.DialogSettings; + + +/** + * Base class for resizable Dialogs with persistent window bounds. + */ +public abstract class ResizableDialog extends Dialog { + + // dialog store id constants + private final static String DIALOG_BOUNDS_KEY= "ResizableDialogBounds"; //$NON-NLS-1$ + private static final String X= "x"; //$NON-NLS-1$ + private static final String Y= "y"; //$NON-NLS-1$ + private static final String WIDTH= "width"; //$NON-NLS-1$ + private static final String HEIGHT= "height"; //$NON-NLS-1$ + + protected ResourceBundle fBundle; + private Rectangle fNewBounds; + private IDialogSettings fSettings; + private String fContextId; + + + public ResizableDialog(Shell parent, ResourceBundle bundle) { + super(parent); + setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX); + + fBundle= bundle; + + fSettings= CompareUIPlugin.getDefault().getDialogSettings(); + } + + public void setHelpContextId(String contextId) { + fContextId= contextId; + } + + /* + * @see org.eclipse.jface.window.Window#configureShell(Shell) + */ + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + if (fContextId != null) + PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, fContextId); + } + + protected Point getInitialSize() { + + int width= 0; + int height= 0; + + final Shell s= getShell(); + if (s != null) { + s.addControlListener( + new ControlListener() { + public void controlMoved(ControlEvent arg0) { + fNewBounds= s.getBounds(); + } + public void controlResized(ControlEvent arg0) { + fNewBounds= s.getBounds(); + } + } + ); + } + + IDialogSettings bounds= fSettings.getSection(DIALOG_BOUNDS_KEY); + if (bounds == null) { + if (fBundle != null) { + width= Utilities.getInteger(fBundle, WIDTH, 0); + height= Utilities.getInteger(fBundle, HEIGHT, 0); + Shell shell= getParentShell(); + if (shell != null) { + Point parentSize= shell.getSize(); + if (width <= 0) + width= parentSize.x-300; + if (height <= 0) + height= parentSize.y-200; + } + } else { + Shell shell= getParentShell(); + if (shell != null) { + Point parentSize= shell.getSize(); + width= parentSize.x-100; + height= parentSize.y-100; + } + } + if (width < 700) + width= 700; + if (height < 500) + height= 500; + } else { + try { + width= bounds.getInt(WIDTH); + } catch (NumberFormatException e) { + width= 700; + } + try { + height= bounds.getInt(HEIGHT); + } catch (NumberFormatException e) { + height= 500; + } + } + + return new Point(width, height); + } + + protected Point getInitialLocation(Point initialSize) { + Point loc= super.getInitialLocation(initialSize); + + IDialogSettings bounds= fSettings.getSection(DIALOG_BOUNDS_KEY); + if (bounds != null) { + try { + loc.x= bounds.getInt(X); + } catch (NumberFormatException e) { + // silently ignored + } + try { + loc.y= bounds.getInt(Y); + } catch (NumberFormatException e) { + // silently ignored + } + } + return loc; + } + + public boolean close() { + boolean closed= super.close(); + if (closed && fNewBounds != null) + saveBounds(fNewBounds); + return closed; + } + + private void saveBounds(Rectangle bounds) { + IDialogSettings dialogBounds= fSettings.getSection(DIALOG_BOUNDS_KEY); + if (dialogBounds == null) { + dialogBounds= new DialogSettings(DIALOG_BOUNDS_KEY); + fSettings.addSection(dialogBounds); + } + dialogBounds.put(X, bounds.x); + dialogBounds.put(Y, bounds.y); + dialogBounds.put(WIDTH, bounds.width); + dialogBounds.put(HEIGHT, bounds.height); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java new file mode 100644 index 000000000..fe72e6b24 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java @@ -0,0 +1,553 @@ +/******************************************************************************* + * 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 + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959 + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.ZipFileStructureCreator; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DiffTreeViewer; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +import com.ibm.icu.text.MessageFormat; + + +/** + * A two-way or three-way compare for arbitrary IResources. + */ +class ResourceCompareInput extends CompareEditorInput { + + private static final boolean NORMALIZE_CASE= true; + + private boolean fThreeWay= false; + private Object fRoot; + private IStructureComparator fAncestor; + private IStructureComparator fLeft; + private IStructureComparator fRight; + private IResource fAncestorResource; + private IResource fLeftResource; + private IResource fRightResource; + private DiffTreeViewer fDiffViewer; + private IAction fOpenAction; + + class MyDiffNode extends DiffNode { + + private boolean fDirty= false; + private ITypedElement fLastId; + private String fLastName; + + + public MyDiffNode(IDiffContainer parent, int description, ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(parent, description, ancestor, left, right); + } + public void fireChange() { + super.fireChange(); + setDirty(true); + fDirty= true; + if (fDiffViewer != null) + fDiffViewer.refresh(this); + } + void clearDirty() { + fDirty= false; + } + public String getName() { + if (fLastName == null) + fLastName= super.getName(); + if (fDirty) + return '<' + fLastName + '>'; + return fLastName; + } + + public ITypedElement getId() { + ITypedElement id= super.getId(); + if (id == null) + return fLastId; + fLastId= id; + return id; + } + } + + static class FilteredBufferedResourceNode extends BufferedResourceNode { + FilteredBufferedResourceNode(IResource resource) { + super(resource); + } + protected IStructureComparator createChild(IResource child) { + String name= child.getName(); + if (CompareUIPlugin.getDefault().filter(name, child instanceof IContainer, false)) + return null; + return new FilteredBufferedResourceNode(child); + } + } + + /* + * Creates an compare editor input for the given selection. + */ + ResourceCompareInput(CompareConfiguration config) { + super(config); + } + + public Viewer createDiffViewer(Composite parent) { + fDiffViewer= new DiffTreeViewer(parent, getCompareConfiguration()) { + protected void fillContextMenu(IMenuManager manager) { + + if (fOpenAction == null) { + fOpenAction= new Action() { + public void run() { + handleOpen(null); + } + }; + Utilities.initAction(fOpenAction, getBundle(), "action.CompareContents."); //$NON-NLS-1$ + } + + boolean enable= false; + ISelection selection= getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection ss= (IStructuredSelection)selection; + if (ss.size() == 1) { + Object element= ss.getFirstElement(); + if (element instanceof MyDiffNode) { + ITypedElement te= ((MyDiffNode) element).getId(); + if (te != null) + enable= !ITypedElement.FOLDER_TYPE.equals(te.getType()); + } else + enable= true; + } + } + fOpenAction.setEnabled(enable); + + manager.add(fOpenAction); + + super.fillContextMenu(manager); + } + }; + return fDiffViewer; + } + + class SelectAncestorDialog extends MessageDialog { + private IResource[] theResources; + IResource ancestorResource; + IResource leftResource; + IResource rightResource; + + private Button[] buttons; + + public SelectAncestorDialog(Shell parentShell, IResource[] theResources) { + super(parentShell, CompareMessages.SelectAncestorDialog_title, + null, CompareMessages.SelectAncestorDialog_message, + MessageDialog.QUESTION, + new String[] { IDialogConstants.OK_LABEL, + IDialogConstants.CANCEL_LABEL }, 0); + this.theResources = theResources; + } + + protected Control createCustomArea(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout()); + buttons = new Button[3]; + for (int i = 0; i < 3; i++) { + buttons[i] = new Button(composite, SWT.RADIO); + buttons[i].addSelectionListener(selectionListener); + buttons[i].setText(NLS.bind(CompareMessages.SelectAncestorDialog_option, + theResources[i].getFullPath().toPortableString())); + buttons[i].setFont(parent.getFont()); + // set initial state + buttons[i].setSelection(i == 0); + } + pickAncestor(0); + return composite; + } + + private void pickAncestor(int i) { + ancestorResource = theResources[i]; + leftResource = theResources[i == 0 ? 1 : 0]; + rightResource = theResources[i == 2 ? 1 : 2]; + } + + private SelectionListener selectionListener = new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + Button selectedButton = (Button) e.widget; + if (!selectedButton.getSelection()) + return; + for (int i = 0; i < 3; i++) + if (selectedButton == buttons[i]) + pickAncestor(i); + } + }; + } + // If the compare is three-way, this method asks the user which resource + // to use as the ancestor. Depending on the value of + // showSelectAncestorDialog flag it uses different dialogs to get the + // feedback from the user. Returns false if the user cancels the prompt, + // true otherwise. + boolean setSelection(ISelection s, Shell shell, boolean showSelectAncestorDialog) { + + if (!showSelectAncestorDialog) + return showCompareWithOtherResourceDialog(shell, s); + + IResource[] selection= Utilities.getResources(s); + + fThreeWay= selection.length == 3; + + if (fThreeWay) { + SelectAncestorDialog dialog = + new SelectAncestorDialog(shell, selection); + int code = dialog.open(); + if (code != Window.OK) + return false; + + fAncestorResource= dialog.ancestorResource; + fAncestor= getStructure(fAncestorResource); + fLeftResource= dialog.leftResource; + fRightResource= dialog.rightResource; + } else { + fAncestorResource= null; + fAncestor= null; + fLeftResource= selection[0]; + fRightResource= selection[1]; + } + fLeft= getStructure(fLeftResource); + fRight= getStructure(fRightResource); + return true; + } + + private boolean showCompareWithOtherResourceDialog(Shell shell, ISelection s) { + CompareWithOtherResourceDialog dialog = new CompareWithOtherResourceDialog(shell, s); + if (dialog.open() != IDialogConstants.OK_ID) + return false; + IResource[] selection = dialog.getResult(); + if (!checkSelection(selection)) + return false; + + fThreeWay = selection.length == 3; + if (fThreeWay) { + fAncestorResource = selection[0]; + fAncestor = getStructure(fAncestorResource); + fLeftResource = selection[1]; + fRightResource = selection[2]; + } else { + fAncestorResource = null; + fAncestor = null; + fLeftResource = selection[0]; + fRightResource = selection[1]; + } + fLeft= getStructure(fLeftResource); + fRight= getStructure(fRightResource); + return true; + } + + private boolean checkSelection(IResource[] resources) { + for (int i = 0; i < resources.length; i++) + if (resources[i] == null) + return false; + return true; + } + + /* + * Returns true if compare can be executed for the given selection. + */ + public boolean isEnabled(ISelection s) { + + IResource[] selection= Utilities.getResources(s); + if (selection.length < 2 || selection.length > 3) + return false; + + boolean threeWay= selection.length == 3; + + if (threeWay) + // It only makes sense if they're all mutually comparable. + // If not, the user should compare two of them. + return comparable(selection[0], selection[1]) + && comparable(selection[0], selection[2]) + && comparable(selection[1], selection[2]); + + return comparable(selection[0], selection[1]); + } + + /** + * Initializes the images in the compare configuration. + */ + void initializeCompareConfiguration() { + CompareConfiguration cc= getCompareConfiguration(); + if (fLeftResource != null) { + cc.setLeftLabel(buildLabel(fLeftResource)); + cc.setLeftImage(CompareUIPlugin.getImage(fLeftResource)); + } + if (fRightResource != null) { + cc.setRightLabel(buildLabel(fRightResource)); + cc.setRightImage(CompareUIPlugin.getImage(fRightResource)); + } + if (fThreeWay && fAncestorResource != null) { + cc.setAncestorLabel(buildLabel(fAncestorResource)); + cc.setAncestorImage(CompareUIPlugin.getImage(fAncestorResource)); + } + } + + /* + * Returns true if both resources are either structured or unstructured. + */ + private boolean comparable(IResource c1, IResource c2) { + return hasStructure(c1) == hasStructure(c2); + } + + /* + * Returns true if the given argument has a structure. + */ + private boolean hasStructure(IResource input) { + + if (input instanceof IContainer) + return true; + + if (input instanceof IFile) { + IFile file= (IFile) input; + String type= file.getFileExtension(); + if (type != null) { + type= normalizeCase(type); + return "JAR".equals(type) || "ZIP".equals(type); //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + return false; + } + + /* + * Creates a <code>IStructureComparator</code> for the given input. + * Returns <code>null</code> if no <code>IStructureComparator</code> + * can be found for the <code>IResource</code>. + */ + private IStructureComparator getStructure(IResource input) { + + if (input instanceof IContainer) + return new FilteredBufferedResourceNode(input); + + if (input instanceof IFile) { + IStructureComparator rn= new FilteredBufferedResourceNode(input); + IFile file= (IFile) input; + String type= normalizeCase(file.getFileExtension()); + if ("JAR".equals(type) || "ZIP".equals(type)) //$NON-NLS-2$ //$NON-NLS-1$ + return new ZipFileStructureCreator().getStructure(rn); + return rn; + } + return null; + } + + /* + * Performs a two-way or three-way diff on the current selection. + */ + public Object prepareInput(IProgressMonitor pm) throws InvocationTargetException { + + try { + // fix for PR 1GFMLFB: ITPUI:WIN2000 - files that are out of sync with the file system appear as empty + fLeftResource.refreshLocal(IResource.DEPTH_INFINITE, pm); + fRightResource.refreshLocal(IResource.DEPTH_INFINITE, pm); + if (fThreeWay && fAncestorResource != null) + fAncestorResource.refreshLocal(IResource.DEPTH_INFINITE, pm); + // end fix + + pm.beginTask(Utilities.getString("ResourceCompare.taskName"), IProgressMonitor.UNKNOWN); //$NON-NLS-1$ + + String leftLabel= fLeftResource.getName(); + String rightLabel= fRightResource.getName(); + + String title; + if (fThreeWay) { + String format= Utilities.getString("ResourceCompare.threeWay.title"); //$NON-NLS-1$ + String ancestorLabel= fAncestorResource.getName(); + title= MessageFormat.format(format, new String[] {ancestorLabel, leftLabel, rightLabel}); + } else { + String format= Utilities.getString("ResourceCompare.twoWay.title"); //$NON-NLS-1$ + title= MessageFormat.format(format, new String[] {leftLabel, rightLabel}); + } + setTitle(title); + + Differencer d= new Differencer() { + protected Object visit(Object parent, int description, Object ancestor, Object left, Object right) { + return new MyDiffNode((IDiffContainer) parent, description, (ITypedElement)ancestor, (ITypedElement)left, (ITypedElement)right); + } + }; + + fRoot= d.findDifferences(fThreeWay, pm, null, fAncestor, fLeft, fRight); + return fRoot; + + } catch (CoreException ex) { + throw new InvocationTargetException(ex); + } finally { + pm.done(); + } + } + + public String getToolTipText() { + if (fLeftResource != null && fRightResource != null) { + String leftLabel= fLeftResource.getFullPath().makeRelative().toString(); + String rightLabel= fRightResource.getFullPath().makeRelative().toString(); + if (fThreeWay) { + String format= Utilities.getString("ResourceCompare.threeWay.tooltip"); //$NON-NLS-1$ + String ancestorLabel= fAncestorResource.getFullPath().makeRelative().toString(); + return MessageFormat.format(format, new String[] {ancestorLabel, leftLabel, rightLabel}); + } + String format= Utilities.getString("ResourceCompare.twoWay.tooltip"); //$NON-NLS-1$ + return MessageFormat.format(format, new String[] {leftLabel, rightLabel}); + } + // fall back + return super.getToolTipText(); + } + + private String buildLabel(IResource r) { + // for a linked resource in a hidden project use its local file system location + if (r.isLinked() && r.getProject().isHidden()) + return r.getLocation().toString(); + String n= r.getFullPath().toString(); + if (n.charAt(0) == IPath.SEPARATOR) + return n.substring(1); + return n; + } + + public void saveChanges(IProgressMonitor pm) throws CoreException { + super.saveChanges(pm); + if (fRoot instanceof DiffNode) { + try { + commit(pm, (DiffNode) fRoot); + } finally { + if (fDiffViewer != null) + fDiffViewer.refresh(); + setDirty(false); + } + } + } + + /* + * Recursively walks the diff tree and commits all changes. + */ + private static void commit(IProgressMonitor pm, DiffNode node) throws CoreException { + + if (node instanceof MyDiffNode) + ((MyDiffNode)node).clearDirty(); + + ITypedElement left= node.getLeft(); + if (left instanceof BufferedResourceNode) + ((BufferedResourceNode) left).commit(pm); + + ITypedElement right= node.getRight(); + if (right instanceof BufferedResourceNode) + ((BufferedResourceNode) right).commit(pm); + + IDiffElement[] children= node.getChildren(); + if (children != null) { + for (int i= 0; i < children.length; i++) { + IDiffElement element= children[i]; + if (element instanceof DiffNode) + commit(pm, (DiffNode) element); + } + } + } + + /* (non Javadoc) + * see IAdaptable.getAdapter + */ + public Object getAdapter(Class adapter) { + if (IFile.class.equals(adapter)) { + IProgressMonitor pm= new NullProgressMonitor(); + // flush changes in any dirty viewer + flushViewers(pm); + IFile[] files= (IFile[]) getAdapter(IFile[].class); + if (files != null && files.length > 0) + return files[0]; // can only return one: limitation on IDE.saveAllEditors; see #64617 + return null; + } + if (IFile[].class.equals(adapter)) { + HashSet collector= new HashSet(); + collectDirtyResources(fRoot, collector); + return collector.toArray(new IFile[collector.size()]); + } + return super.getAdapter(adapter); + } + + private void collectDirtyResources(Object o, Set collector) { + if (o instanceof DiffNode) { + DiffNode node= (DiffNode) o; + + ITypedElement left= node.getLeft(); + if (left instanceof BufferedResourceNode) { + BufferedResourceNode bn= (BufferedResourceNode) left; + if (bn.isDirty()) { + IResource resource= bn.getResource(); + if (resource instanceof IFile) + collector.add(resource); + } + } + + ITypedElement right= node.getRight(); + if (right instanceof BufferedResourceNode) { + BufferedResourceNode bn= (BufferedResourceNode) right; + if (bn.isDirty()) { + IResource resource= bn.getResource(); + if (resource instanceof IFile) + collector.add(resource); + } + } + + IDiffElement[] children= node.getChildren(); + if (children != null) { + for (int i= 0; i < children.length; i++) { + IDiffElement element= children[i]; + if (element instanceof DiffNode) + collectDirtyResources(element, collector); + } + } + } + } + + private static String normalizeCase(String s) { + if (NORMALIZE_CASE && s != null) + return s.toUpperCase(); + return s; + } + + public boolean canRunAsJob() { + return true; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java new file mode 100644 index 000000000..2b2e14771 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.internal; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.WhitespaceCharacterPainter; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +public class ShowWhitespaceAction extends TextEditorPropertyAction { + + private Map fPainters; + private boolean isWhitespaceShowing; + private boolean[] fNeedsPainters; + /** @since 3.7 */ + private boolean fShowLeadingSpaces; + /** @since 3.7 */ + private boolean fShowEnclosedSpaces; + /** @since 3.7 */ + private boolean fShowTrailingSpaces; + /** @since 3.7 */ + private boolean fShowLeadingIdeographicSpaces; + /** @since 3.7 */ + private boolean fShowEnclosedIdeographicSpaces; + /** @since 3.7 */ + private boolean fShowTrailingIdeographicSpace; + /** @since 3.7 */ + private boolean fShowLeadingTabs; + /** @since 3.7 */ + private boolean fShowEnclosedTabs; + /** @since 3.7 */ + private boolean fShowTrailingTabs; + /** @since 3.7 */ + private boolean fShowCarriageReturn; + /** @since 3.7 */ + private boolean fShowLineFeed; + /** @since 3.7 */ + private IPreferenceStore fStore = EditorsUI.getPreferenceStore(); + /** @since 3.7 */ + private int fAlpha; + + public ShowWhitespaceAction(MergeSourceViewer[] viewers, boolean[] needsPainters) { + super(CompareMessages.ShowWhitespaceAction_0, viewers, AbstractTextEditor.PREFERENCE_SHOW_WHITESPACE_CHARACTERS); + fNeedsPainters = needsPainters; + synchronizeWithPreference(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.compare.internal.TextEditorPropertyAction#synchronizeWithPreference() + */ + protected void synchronizeWithPreference() { + boolean checked = false; + if (fStore != null) { + checked = fStore.getBoolean(getPreferenceKey()); + fShowLeadingSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LEADING_SPACES); + fShowEnclosedSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_SPACES); + fShowTrailingSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_TRAILING_SPACES); + fShowLeadingIdeographicSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LEADING_IDEOGRAPHIC_SPACES); + fShowEnclosedIdeographicSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES); + fShowTrailingIdeographicSpace = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_TRAILING_IDEOGRAPHIC_SPACES); + fShowLeadingTabs = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LEADING_TABS); + fShowEnclosedTabs = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_TABS); + fShowTrailingTabs = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_TRAILING_TABS); + fShowCarriageReturn = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_CARRIAGE_RETURN); + fShowLineFeed = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LINE_FEED); + fAlpha = fStore.getInt(AbstractTextEditor.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE); + } + if (checked != isChecked()) { + if (toggleState(checked)) + setChecked(checked); + } else if (fNeedsPainters != null && checked) { + hideWhitespace(); + showWhitespace(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.compare.internal.TextEditorPropertyAction#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + String property = event.getProperty(); + if (property.equals(getPreferenceKey()) || AbstractTextEditor.PREFERENCE_SHOW_LEADING_SPACES.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_SPACES.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_TRAILING_SPACES.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_LEADING_IDEOGRAPHIC_SPACES.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_TRAILING_IDEOGRAPHIC_SPACES.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_LEADING_TABS.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_TABS.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_TRAILING_TABS.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_CARRIAGE_RETURN.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_LINE_FEED.equals(property) || AbstractTextEditor.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE.equals(property)) { + synchronizeWithPreference(); + } + } + + protected boolean toggleState(boolean checked) { + if (fNeedsPainters == null) + return false; // Not initialized yet + if (checked) { + showWhitespace(); + } else { + hideWhitespace(); + } + return true; + } + + private synchronized Map getPainters() { + if (fPainters == null) + fPainters = new HashMap(); + return fPainters; + } + + private void showWhitespace() { + if (isWhitespaceShowing) + return; + try { + Map painters = getPainters(); + MergeSourceViewer[] viewers = getViewers(); + for (int i = 0; i < viewers.length; i++) { + if (fNeedsPainters[i]) { + MergeSourceViewer viewer = viewers[i]; + SourceViewer sourceViewer = viewer.getSourceViewer(); + WhitespaceCharacterPainter painter; + if (fStore != null) { + painter = new WhitespaceCharacterPainter(sourceViewer, fShowLeadingSpaces, fShowEnclosedSpaces, fShowTrailingSpaces, fShowLeadingIdeographicSpaces, + fShowEnclosedIdeographicSpaces, fShowTrailingIdeographicSpace, fShowLeadingTabs, fShowEnclosedTabs, fShowTrailingTabs, fShowCarriageReturn, fShowLineFeed, fAlpha); + } else { + painter = new WhitespaceCharacterPainter(sourceViewer); + } + sourceViewer.addPainter(painter); + painters.put(viewer, painter); + } + } + } finally { + isWhitespaceShowing = true; + } + } + + private void hideWhitespace() { + Map painters = getPainters(); + for (Iterator iterator = painters.keySet().iterator(); iterator.hasNext();) { + MergeSourceViewer viewer = (MergeSourceViewer) iterator.next(); + WhitespaceCharacterPainter painter = (WhitespaceCharacterPainter)painters.get(viewer); + if (painter != null) { + viewer.getSourceViewer().removePainter(painter); + painter.deactivate(true); + } + } + painters.clear(); + isWhitespaceShowing = false; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java new file mode 100644 index 000000000..a3d1bac5d --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.text.Document; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.compare.*; +import org.eclipse.compare.structuremergeviewer.ICompareInput; + + +public class SimpleTextViewer extends AbstractViewer { + + private SourceViewer fSourceViewer; + private ICompareInput fInput; + + + SimpleTextViewer(Composite parent) { + fSourceViewer= new SourceViewer(parent, null, SWT.H_SCROLL | SWT.V_SCROLL); + fSourceViewer.setEditable(false); + } + + public Control getControl() { + return fSourceViewer.getTextWidget(); + } + + public void setInput(Object input) { + if (input instanceof IStreamContentAccessor) { + fSourceViewer.setDocument(new Document(getString(input))); + } else if (input instanceof ICompareInput) { + fInput= (ICompareInput) input; + ITypedElement left= fInput.getLeft(); + fSourceViewer.setDocument(new Document(getString(left))); + } + } + + public Object getInput() { + return fInput; + } + + private String getString(Object input) { + + if (input instanceof IStreamContentAccessor) { + try { + return Utilities.readString((IStreamContentAccessor) input); + } catch (CoreException ex) { + // NeedWork + CompareUIPlugin.log(ex); + } + } + return ""; //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java new file mode 100644 index 000000000..5fc385ed2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 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.internal; + +import org.eclipse.compare.IStreamMerger; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; + +/** + * A factory proxy for creating a StructureCreator. + */ +class StreamMergerDescriptor { + + private final static String CLASS_ATTRIBUTE= "class"; //$NON-NLS-1$ + + private IConfigurationElement fElement; + + /* + * Creates a new sorter node with the given configuration element. + */ + public StreamMergerDescriptor(IConfigurationElement element) { + fElement= element; + } + + /* + * Creates a new stream merger from this node. + */ + public IStreamMerger createStreamMerger() { + try { + return (IStreamMerger)fElement.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException ex) { + //ExceptionHandler.handle(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } catch (ClassCastException ex) { + //ExceptionHandler.displayMessageDialog(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java new file mode 100644 index 000000000..7f9011c55 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 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.internal; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; + +import org.eclipse.compare.structuremergeviewer.IStructureCreator; + +/** + * A factory proxy for creating a StructureCreator. + */ +public class StructureCreatorDescriptor { + + private final static String CLASS_ATTRIBUTE= "class"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE= "extensions"; //$NON-NLS-1$ + + private IConfigurationElement fElement; + + /* + * Creates a new sorter node with the given configuration element. + */ + public StructureCreatorDescriptor(IConfigurationElement element) { + fElement= element; + } + + /* + * Creates a new sorter from this node. + */ + public IStructureCreator createStructureCreator() { + try { + return (IStructureCreator)fElement.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException ex) { + CompareUIPlugin.log(ex.getStatus()); + //ExceptionHandler.handle(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } catch (ClassCastException ex) { + //ExceptionHandler.displayMessageDialog(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } + } + + /* + * Returns the structure creator's extensions. + */ + public String getExtension() { + return fElement.getAttribute(EXTENSIONS_ATTRIBUTE); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java new file mode 100644 index 000000000..fc8e61c1e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Layout; + +public class TabFolderLayout extends Layout { + + protected Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache) { + if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT) + return new Point(wHint, hHint); + + Control [] children = composite.getChildren (); + int count = children.length; + int maxWidth = 0, maxHeight = 0; + for (int i=0; i<count; i++) { + Control child = children [i]; + Point pt = child.computeSize (SWT.DEFAULT, SWT.DEFAULT, flushCache); + maxWidth = Math.max (maxWidth, pt.x); + maxHeight = Math.max (maxHeight, pt.y); + } + + if (wHint != SWT.DEFAULT) + maxWidth= wHint; + if (hHint != SWT.DEFAULT) + maxHeight= hHint; + + return new Point(maxWidth, maxHeight); + + } + + protected void layout (Composite composite, boolean flushCache) { + Rectangle rect= composite.getClientArea(); + + Control[] children = composite.getChildren(); + for (int i = 0; i < children.length; i++) { + children[i].setBounds(rect); + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java new file mode 100644 index 000000000..f7a49847f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 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.internal; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.editors.text.EditorsUI; + +public class TextEditorPropertyAction extends Action implements IPropertyChangeListener { + + private final MergeSourceViewer[] viewers; + private final String preferenceKey; + private IPreferenceStore store; + + public TextEditorPropertyAction(String label, MergeSourceViewer[] viewers, String preferenceKey) { + super(label, IAction.AS_CHECK_BOX); + this.viewers = viewers; + this.preferenceKey = preferenceKey; + this.store = EditorsUI.getPreferenceStore(); + if (store != null) + store.addPropertyChangeListener(this); + synchronizeWithPreference(); + addActionToViewers(); + } + + private void addActionToViewers() { + for (int i = 0; i < viewers.length; i++) { + MergeSourceViewer viewer = viewers[i]; + viewer.addTextAction(this); + } + } + + public MergeSourceViewer[] getViewers() { + return viewers; + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(getPreferenceKey())) { + synchronizeWithPreference(); + } + } + + protected void synchronizeWithPreference() { + boolean checked = false; + if (store != null) { + checked = store.getBoolean(getPreferenceKey()); + } + if (checked != isChecked()) { + if (toggleState(checked)) + setChecked(checked); + } + } + + public String getPreferenceKey() { + return preferenceKey; + } + + public void run() { + toggleState(isChecked()); + if (store != null) + store.setValue(getPreferenceKey(), isChecked()); + } + + public void dispose() { + if (store != null) + store.removePropertyChangeListener(this); + } + + /** + * @param checked + * new state + * @return <code>true</code> if state has been changed, toggle has been + * successful + */ + protected boolean toggleState(boolean checked) { + // No-op by default + return false; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java new file mode 100644 index 000000000..7c92beb49 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.TextMergeViewer; + +/** + * A factory object for the <code>TextMergeViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class TextMergeViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new TextMergeViewer(parent, mp); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java new file mode 100644 index 000000000..e6dbe26d3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.IViewerCreator; + + +/** + * A factory object for the <code>TextMergeViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class TextViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new SimpleTextViewer(parent); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java new file mode 100644 index 000000000..fe48c7219 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java @@ -0,0 +1,915 @@ +/******************************************************************************* + * 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.internal; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ISharedDocumentAdapter; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.SharedDocumentAdapter; +import org.eclipse.compare.contentmergeviewer.IDocumentRange; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.internal.patch.PatchMessages; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourceAttributes; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceMappingContext; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.IWorkbenchSite; +import org.eclipse.ui.progress.IWorkbenchSiteProgressService; +import org.eclipse.ui.texteditor.IDocumentProvider; + +import com.ibm.icu.text.MessageFormat; + +/** + * Convenience and utility methods. + */ +public class Utilities { + + private static final IPath ICONS_PATH= new Path("$nl$/icons/full/"); //$NON-NLS-1$ + + public static IWorkbenchPartSite findSite(Control c) { + while (c != null && !c.isDisposed()) { + Object data= c.getData(); + if (data instanceof IWorkbenchPart) + return ((IWorkbenchPart)data).getSite(); + c= c.getParent(); + } + return null; + } + + public static IActionBars findActionBars(Control c) { + while (c != null && !c.isDisposed()) { + Object data= c.getData(); + if (data instanceof CompareEditor) + return ((CompareEditor)data).getActionBars(); + + // PR 1GDVZV7: ITPVCM:WIN98 - CTRL + C does not work in Java source compare + if (data instanceof IViewPart) + return ((IViewPart)data).getViewSite().getActionBars(); + // end PR 1GDVZV7 + + c= c.getParent(); + } + return null; + } + + public static void setEnableComposite(Composite composite, boolean enable) { + Control[] children= composite.getChildren(); + for (int i= 0; i < children.length; i++) + children[i].setEnabled(enable); + } + + public static boolean getBoolean(CompareConfiguration cc, String key, boolean dflt) { + if (cc != null) { + Object value= cc.getProperty(key); + if (value instanceof Boolean) + return ((Boolean) value).booleanValue(); + } + return dflt; + } + + public static void firePropertyChange(ListenerList listenerList, Object source, String property, Object old, Object newValue) { + PropertyChangeEvent event= new PropertyChangeEvent(source, property, old, newValue); + firePropertyChange(listenerList, event); + } + + public static void firePropertyChange(final ListenerList listenerList, final PropertyChangeEvent event) { + if (listenerList == null || listenerList.isEmpty()) + return; + // Legacy listeners may expect to get notified in the UI thread + Runnable runnable = new Runnable() { + public void run() { + Object[] listeners= listenerList.getListeners(); + for (int i= 0; i < listeners.length; i++) { + final IPropertyChangeListener listener= (IPropertyChangeListener) listeners[i]; + SafeRunner.run(new ISafeRunnable() { + public void run() throws Exception { + listener.propertyChange(event); + } + public void handleException(Throwable exception) { + // Logged by SafeRunner + } + }); + } + } + }; + if (Display.getCurrent() == null) { + Display.getDefault().syncExec(runnable); + } else { + runnable.run(); + } + } + + public static boolean okToUse(Widget widget) { + return widget != null && !widget.isDisposed(); + } + + private static ArrayList internalGetResources(ISelection selection, Class type) { + ArrayList tmp= new ArrayList(); + if (selection instanceof IStructuredSelection) { + Object[] s= ((IStructuredSelection)selection).toArray(); + + for (int i= 0; i < s.length; i++) { + IResource resource= null; + Object o= s[i]; + if (type.isInstance(o)) { + resource= (IResource) o; + + } else if (o instanceof ResourceMapping) { + try { + ResourceTraversal[] travs= ((ResourceMapping)o).getTraversals(ResourceMappingContext.LOCAL_CONTEXT, null); + if (travs != null) { + for (int k= 0; k < travs.length; k++) { + IResource[] resources= travs[k].getResources(); + for (int j= 0; j < resources.length; j++) { + if (type.isInstance(resources[j]) && resources[j].isAccessible()) + tmp.add(resources[j]); + } + } + } + } catch (CoreException ex) { + CompareUIPlugin.log(ex); + } + } else if (o instanceof IAdaptable) { + IAdaptable a= (IAdaptable) o; + Object adapter= a.getAdapter(IResource.class); + if (type.isInstance(adapter)) + resource= (IResource) adapter; + } + + if (resource != null && resource.isAccessible()) + tmp.add(resource); + } + } + return tmp; + } + + + /* + * Convenience method: extract all accessible <code>IResources</code> from given selection. + * Never returns null. + */ + public static IResource[] getResources(ISelection selection) { + ArrayList tmp= internalGetResources(selection, IResource.class); + return (IResource[]) tmp.toArray(new IResource[tmp.size()]); + } + + /* + * Convenience method: extract all accessible <code>IFiles</code> from given selection. + * Never returns null. + */ + public static IFile[] getFiles(ISelection selection) { + ArrayList tmp= internalGetResources(selection, IFile.class); + return (IFile[]) tmp.toArray(new IFile[tmp.size()]); + } + + public static byte[] readBytes(InputStream in) { + ByteArrayOutputStream bos= new ByteArrayOutputStream(); + try { + while (true) { + int c= in.read(); + if (c == -1) + break; + bos.write(c); + } + + } catch (IOException ex) { + return null; + + } finally { + Utilities.close(in); + try { + bos.close(); + } catch (IOException x) { + // silently ignored + } + } + + return bos.toByteArray(); + } + + public static IPath getIconPath(Display display) { + return ICONS_PATH; + } + + /* + * Initialize the given Action from a ResourceBundle. + */ + public static void initAction(IAction a, ResourceBundle bundle, String prefix) { + + String labelKey= "label"; //$NON-NLS-1$ + String tooltipKey= "tooltip"; //$NON-NLS-1$ + String imageKey= "image"; //$NON-NLS-1$ + String descriptionKey= "description"; //$NON-NLS-1$ + + if (prefix != null && prefix.length() > 0) { + labelKey= prefix + labelKey; + tooltipKey= prefix + tooltipKey; + imageKey= prefix + imageKey; + descriptionKey= prefix + descriptionKey; + } + + a.setText(getString(bundle, labelKey, labelKey)); + a.setToolTipText(getString(bundle, tooltipKey, null)); + a.setDescription(getString(bundle, descriptionKey, null)); + + String relPath= getString(bundle, imageKey, null); + if (relPath != null && relPath.trim().length() > 0) { + + String dPath; + String ePath; + + if (relPath.indexOf("/") >= 0) { //$NON-NLS-1$ + String path= relPath.substring(1); + dPath= 'd' + path; + ePath= 'e' + path; + } else { + dPath= "dlcl16/" + relPath; //$NON-NLS-1$ + ePath= "elcl16/" + relPath; //$NON-NLS-1$ + } + + ImageDescriptor id= CompareUIPlugin.getImageDescriptor(dPath); // we set the disabled image first (see PR 1GDDE87) + if (id != null) + a.setDisabledImageDescriptor(id); + id= CompareUIPlugin.getImageDescriptor(ePath); + if (id != null) { + a.setImageDescriptor(id); + a.setHoverImageDescriptor(id); + } + } + } + + public static void initToggleAction(IAction a, ResourceBundle bundle, String prefix, boolean checked) { + + String tooltip= null; + if (checked) + tooltip= getString(bundle, prefix + "tooltip.checked", null); //$NON-NLS-1$ + else + tooltip= getString(bundle, prefix + "tooltip.unchecked", null); //$NON-NLS-1$ + if (tooltip == null) + tooltip= getString(bundle, prefix + "tooltip", null); //$NON-NLS-1$ + + if (tooltip != null) + a.setToolTipText(tooltip); + + String description= null; + if (checked) + description= getString(bundle, prefix + "description.checked", null); //$NON-NLS-1$ + else + description= getString(bundle, prefix + "description.unchecked", null); //$NON-NLS-1$ + if (description == null) + description= getString(bundle, prefix + "description", null); //$NON-NLS-1$ + + if (description != null) + a.setDescription(description); + + } + + public static String getString(ResourceBundle bundle, String key, String dfltValue) { + + if (bundle != null) { + try { + return bundle.getString(key); + } catch (MissingResourceException x) { + // fall through + } + } + return dfltValue; + } + + public static String getFormattedString(ResourceBundle bundle, String key, String arg) { + + if (bundle != null) { + try { + return MessageFormat.format(bundle.getString(key), new String[] { arg }); + } catch (MissingResourceException x) { + CompareUIPlugin.log(x); + } + } + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + + public static String getString(String key) { + try { + return CompareUI.getResourceBundle().getString(key); + } catch (MissingResourceException e) { + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + public static String getFormattedString(String key, String arg) { + try { + return MessageFormat.format(CompareUI.getResourceBundle().getString(key), new String[] { arg }); + } catch (MissingResourceException e) { + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + public static String getFormattedString(String key, String arg0, String arg1) { + try { + return MessageFormat.format(CompareUI.getResourceBundle().getString(key), new String[] { arg0, arg1 }); + } catch (MissingResourceException e) { + return "!" + key + "!";//$NON-NLS-2$ //$NON-NLS-1$ + } + } + + public static String getString(ResourceBundle bundle, String key) { + return getString(bundle, key, key); + } + + public static int getInteger(ResourceBundle bundle, String key, int dfltValue) { + + if (bundle != null) { + try { + String s= bundle.getString(key); + if (s != null) + return Integer.parseInt(s); + } catch (NumberFormatException x) { + CompareUIPlugin.log(x); + } catch (MissingResourceException x) { + // silently ignore Exception + } + } + return dfltValue; + } + + /** + * Answers <code>true</code> if the given selection contains resources that don't + * have overlapping paths and <code>false</code> otherwise. + */ + /* + public static boolean isSelectionNonOverlapping() throws TeamException { + IResource[] resources = getSelectedResources(); + // allow operation for non-overlapping resource selections + if(resources.length>0) { + List validPaths = new ArrayList(2); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + + // only allow cvs resources to be selected + if(RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()) == null) { + return false; + } + + // check if this resource overlaps other selections + IPath resourceFullPath = resource.getFullPath(); + if(!validPaths.isEmpty()) { + for (Iterator it = validPaths.iterator(); it.hasNext();) { + IPath path = (IPath) it.next(); + if(path.isPrefixOf(resourceFullPath) || + resourceFullPath.isPrefixOf(path)) { + return false; + } + } + } + validPaths.add(resourceFullPath); + + // ensure that resources are managed + ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource); + if(cvsResource.isFolder()) { + if( ! ((ICVSFolder)cvsResource).isCVSFolder()) return false; + } else { + if( ! cvsResource.isManaged()) return false; + } + } + return true; + } + return false; + } + */ + + /* validate edit utilities */ + + /** + * Status constant indicating that an validateEdit call has changed the + * content of a file on disk. + */ + private static final int VALIDATE_EDIT_PROBLEM= 10004; + + /** + * Constant used to indicate that tests are being run. + */ + public static boolean RUNNING_TESTS = false; + + /** + * Constant used while testing the indicate that changes should be flushed + * when the compare input changes and a viewer is dirty. + */ + public static boolean TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE = false; + + /* + * Makes the given resources committable. Committable means that all + * resources are writeable and that the content of the resources hasn't + * changed by calling <code>validateEdit</code> for a given file on + * <tt>IWorkspace</tt>. + * + * @param resources the resources to be checked + * @param shell the Shell passed to <code>validateEdit</code> as a context + * @return returns <code>true</code> if all resources are committable, <code>false</code> otherwise + * + * @see org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object) + */ + public static boolean validateResource(IResource resource, Shell shell, String title) { + return validateResources(new IResource[] { resource }, shell, title); + } + + /* + * Makes the given resources committable. Committable means that all + * resources are writeable and that the content of the resources hasn't + * changed by calling <code>validateEdit</code> for a given file on + * <tt>IWorkspace</tt>. + * + * @param resources the resources to be checked + * @param shell the Shell passed to <code>validateEdit</code> as a context + * @return returns <code>true</code> if all resources are committable, <code>false</code> otherwise + * + * @see org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object) + */ + public static boolean validateResources(List resources, Shell shell, String title) { + IResource r[]= (IResource[]) resources.toArray(new IResource[resources.size()]); + return validateResources(r, shell, title); + } + + /* + * Makes the given resources committable. Committable means that all + * resources are writeable and that the content of the resources hasn't + * changed by calling <code>validateEdit</code> for a given file on + * <tt>IWorkspace</tt>. + * + * @param resources the resources to be checked + * @param shell the Shell passed to <code>validateEdit</code> as a context + * @return returns <code>true</code> if all resources are committable, <code>false</code> otherwise + * + * @see org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object) + */ + public static boolean validateResources(IResource[] resources, Shell shell, String title) { + + // get all readonly files + List readOnlyFiles= getReadonlyFiles(resources); + if (readOnlyFiles.size() == 0) + return true; + + // get timestamps of readonly files before validateEdit + Map oldTimeStamps= createModificationStampMap(readOnlyFiles); + + IFile[] files= (IFile[]) readOnlyFiles.toArray(new IFile[readOnlyFiles.size()]); + IStatus status= ResourcesPlugin.getWorkspace().validateEdit(files, shell); + if (! status.isOK()) { + String message= getString("ValidateEdit.error.unable_to_perform"); //$NON-NLS-1$ + displayError(shell, title, status, message); + return false; + } + + IStatus modified= null; + Map newTimeStamps= createModificationStampMap(readOnlyFiles); + for (Iterator iter= oldTimeStamps.keySet().iterator(); iter.hasNext();) { + IFile file= (IFile) iter.next(); + if (file.isReadOnly()) { + IStatus entry= new Status(IStatus.ERROR, + CompareUIPlugin.getPluginId(), + VALIDATE_EDIT_PROBLEM, + getFormattedString("ValidateEdit.error.stillReadonly", file.getFullPath().toString()), //$NON-NLS-1$ + null); + modified= addStatus(modified, entry); + } else if (! oldTimeStamps.get(file).equals(newTimeStamps.get(file))) { + IStatus entry= new Status(IStatus.ERROR, + CompareUIPlugin.getPluginId(), + VALIDATE_EDIT_PROBLEM, + getFormattedString("ValidateEdit.error.fileModified", file.getFullPath().toString()), //$NON-NLS-1$ + null); + modified= addStatus(modified, entry); + } + } + if (modified != null) { + String message= getString("ValidateEdit.error.unable_to_perform"); //$NON-NLS-1$ + displayError(shell, title, modified, message); + return false; + } + return true; + } + + private static void displayError(final Shell shell, final String title, final IStatus status, final String message) { + if (Display.getCurrent() != null) + ErrorDialog.openError(shell, title, message, status); + else { + Display.getDefault().syncExec(new Runnable() { + public void run() { + ErrorDialog.openError(shell, title, message, status); + } + }); + } + } + + private static List getReadonlyFiles(IResource[] resources) { + List readOnlyFiles= new ArrayList(); + for (int i= 0; i < resources.length; i++) { + IResource resource= resources[i]; + ResourceAttributes resourceAttributes= resource.getResourceAttributes(); + if (resource.getType() == IResource.FILE && resourceAttributes != null && resourceAttributes.isReadOnly()) + readOnlyFiles.add(resource); + } + return readOnlyFiles; + } + + private static Map createModificationStampMap(List files) { + Map map= new HashMap(); + for (Iterator iter= files.iterator(); iter.hasNext(); ) { + IFile file= (IFile)iter.next(); + map.put(file, new Long(file.getModificationStamp())); + } + return map; + } + + private static IStatus addStatus(IStatus status, IStatus entry) { + + if (status == null) + return entry; + + if (status.isMultiStatus()) { + ((MultiStatus)status).add(entry); + return status; + } + + MultiStatus result= new MultiStatus(CompareUIPlugin.getPluginId(), + VALIDATE_EDIT_PROBLEM, + getString("ValidateEdit.error.unable_to_perform"), null); //$NON-NLS-1$ + result.add(status); + result.add(entry); + return result; + } + + // encoding + + public static String readString(IStreamContentAccessor sca, String encoding) throws CoreException { + String s = null; + try { + try { + s= Utilities.readString(sca.getContents(), encoding); + } catch (UnsupportedEncodingException e) { + if (!encoding.equals(ResourcesPlugin.getEncoding())) { + s = Utilities.readString(sca.getContents(), ResourcesPlugin.getEncoding()); + } + } + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, e.getMessage(), e)); + } + return s; + } + + /* + * Returns null if an error occurred. + */ + public static String readString(InputStream is, String encoding) throws IOException { + return readString(is, encoding, -1, null); + } + + public static String readString(InputStream is, String encoding, int length, IProgressMonitor monitor) throws IOException { + SubMonitor progress = SubMonitor.convert(monitor); + progress.setWorkRemaining(length); + if (is == null) + return null; + BufferedReader reader= null; + try { + StringBuffer buffer= new StringBuffer(); + char[] part= new char[2048]; + int read= 0; + reader= new BufferedReader(new InputStreamReader(is, encoding)); + while ((read= reader.read(part)) != -1) { + buffer.append(part, 0, read); + progress.worked(2048); + if (progress.isCanceled()) + throw new OperationCanceledException(); + } + + return buffer.toString(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + } + + public static String getCharset(Object resource) { + if (resource instanceof IEncodedStorage) { + try { + return ((IEncodedStorage)resource).getCharset(); + } catch (CoreException ex) { + CompareUIPlugin.log(ex); + } + } + return ResourcesPlugin.getEncoding(); + } + + public static byte[] getBytes(String s, String encoding) { + byte[] bytes= null; + if (s != null) { + try { + bytes= s.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + bytes= s.getBytes(); + } + } + return bytes; + } + + public static String readString(IStreamContentAccessor sa) throws CoreException { + String encoding= null; + if (sa instanceof IEncodedStreamContentAccessor) + encoding= ((IEncodedStreamContentAccessor)sa).getCharset(); + if (encoding == null) + encoding= ResourcesPlugin.getEncoding(); + return Utilities.readString(sa, encoding); + } + + public static void close(InputStream is) { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + + public static IResource getFirstResource(ISelection selection) { + IResource[] resources = getResources(selection); + if (resources.length > 0) + return resources[0]; + return null; + } + + public static Object getAdapter(Object element, Class adapterType, boolean load) { + if (adapterType.isInstance(element)) + return element; + if (element instanceof IAdaptable) { + Object adapted = ((IAdaptable) element).getAdapter(adapterType); + if (adapterType.isInstance(adapted)) + return adapted; + } + if (load) { + Object adapted = Platform.getAdapterManager().loadAdapter(element, adapterType.getName()); + if (adapterType.isInstance(adapted)) + return adapted; + } else { + Object adapted = Platform.getAdapterManager().getAdapter(element, adapterType); + if (adapterType.isInstance(adapted)) + return adapted; + } + return null; + } + + public static Object getAdapter(Object element, Class adapterType) { + return getAdapter(element, adapterType, false); + } + + public static ITypedElement getLeg(char type, Object input) { + if (input instanceof ICompareInput) { + switch (type) { + case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR: + return ((ICompareInput)input).getAncestor(); + case MergeViewerContentProvider.LEFT_CONTRIBUTOR: + return ((ICompareInput)input).getLeft(); + case MergeViewerContentProvider.RIGHT_CONTRIBUTOR: + return ((ICompareInput)input).getRight(); + } + } + return null; + } + + public static IDocument getDocument(char type, Object element, boolean isUsingDefaultContentProvider, boolean canHaveSharedDocument) { + ITypedElement te= getLeg(type, element); + if (te == null) + return null; + if (te instanceof IDocument) + return (IDocument) te; + if (te instanceof IDocumentRange) + return ((IDocumentRange) te).getDocument(); + + if (isUsingDefaultContentProvider && canHaveSharedDocument) { + ISharedDocumentAdapter sda = (ISharedDocumentAdapter)Utilities.getAdapter(te, ISharedDocumentAdapter.class, true); + if (sda != null) { + IEditorInput input= sda.getDocumentKey(te); + if (input != null) { + IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(input); + if (provider != null) + return provider.getDocument(input); + } + } + } + + if (te instanceof IStreamContentAccessor) + return DocumentManager.get(te); + + return null; + } + + /** + * Return whether either the left or right sides of the given input + * represents a hunk. A hunk is a portion of a file. + * @param input the compare input + * @return whether the left or right side of the input represents a hunk + */ + public static boolean isHunk(Object input) { + if (input != null && input instanceof DiffNode){ + ITypedElement right = ((DiffNode) input).getRight(); + if (right != null) { + Object element = Utilities.getAdapter(right, IHunk.class); + if (element instanceof IHunk) + return true; + } + ITypedElement left = ((DiffNode) input).getLeft(); + if (left != null) { + Object element = Utilities.getAdapter(left, IHunk.class); + if (element instanceof IHunk) + return true; + } + } + return false; + } + + public static boolean isHunkOk(Object input) { + if (input != null && input instanceof DiffNode){ + ITypedElement right = ((DiffNode) input).getRight(); + if (right != null) { + Object element = Utilities.getAdapter(right, HunkResult.class); + if (element instanceof HunkResult) { + return ((HunkResult)element).isOK(); + } + } + ITypedElement left = ((DiffNode) input).getLeft(); + if (left != null) { + Object element = Utilities.getAdapter(left, HunkResult.class); + if (element instanceof HunkResult) + return ((HunkResult)element).isOK(); + } + } + return false; + } + + public static void schedule(Job job, IWorkbenchSite site) { + if (site != null) { + IWorkbenchSiteProgressService siteProgress = (IWorkbenchSiteProgressService) site.getAdapter(IWorkbenchSiteProgressService.class); + if (siteProgress != null) { + siteProgress.schedule(job, 0, true /* use half-busy cursor */); + return; + } + } + job.schedule(); + } + + public static void runInUIThread(final Runnable runnable) { + if (Display.getCurrent() != null) { + BusyIndicator.showWhile(Display.getCurrent(), runnable); + } else { + Display.getDefault().syncExec(new Runnable() { + public void run() { + BusyIndicator.showWhile(Display.getCurrent(), runnable); + } + }); + } + } + + /** + * @param connection a connection for which the timeout is set + * @param timeout an int that specifies the connect timeout value in milliseconds + * @return whether the timeout has been successfully set + */ + public static boolean setReadTimeout(URLConnection connection, int timeout) { + Method[] methods = connection.getClass().getMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals("setReadTimeout")) //$NON-NLS-1$ + try { + methods[i].invoke(connection, new Object[] {new Integer(timeout)}); + return true; + } catch (IllegalArgumentException e) { // ignore + } catch (IllegalAccessException e) { // ignore + } catch (InvocationTargetException e) { // ignore + } + } + return false; + } + + /** + * Load content of file under <code>url</code> displaying progress on given + * context. + * + * @param url + * @param context + * @return the content of file under given URL, or <code>null</code> if URL + * could not be loaded + * @throws InvocationTargetException + * thrown on errors while URL loading + * @throws OperationCanceledException + * @throws InterruptedException + */ + public static String getURLContents(final URL url, IRunnableContext context) + throws InvocationTargetException, OperationCanceledException, + InterruptedException { + final String[] result = new String[1]; + context.run(true, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) + throws InvocationTargetException, InterruptedException { + SubMonitor progress = SubMonitor.convert(monitor, + PatchMessages.InputPatchPage_URLConnecting, 100); + try { + URLConnection connection = url.openConnection(); + progress.worked(10); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + setReadTimeout(connection, 60 * 1000); + progress.setTaskName(PatchMessages.InputPatchPage_URLFetchingContent); + String enc = connection.getContentEncoding(); + if (enc == null) + enc = ResourcesPlugin.getEncoding(); + result[0] = Utilities.readString( + connection.getInputStream(), enc, + connection.getContentLength(), + progress.newChild(90)); + } catch (SocketTimeoutException e) { + throw new InvocationTargetException(e); + } catch (IOException e) { + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + } + }); + return result[0]; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java new file mode 100644 index 000000000..e5d35bcf9 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.IViewerCreator; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.widgets.Composite; + +/** + * Creates <code>Viewer</code>s from an <code>IConfigurationElement</code>. + */ +public class ViewerDescriptor implements IViewerDescriptor { + + private final static String CLASS_ATTRIBUTE= "class"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE= "extensions"; //$NON-NLS-1$ + private final static String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$ + + private IConfigurationElement fConfiguration; + private IViewerCreator fViewerCreator; + private Class fViewerClass; + + public ViewerDescriptor(IConfigurationElement config) { + fConfiguration= config; + } + + public Viewer createViewer(Viewer currentViewer, Composite parent, CompareConfiguration mp) { + + if (currentViewer != null && currentViewer.getClass() == fViewerClass) { + //System.out.println("reused viewer: " + currentViewer.getClass().getName()); + return currentViewer; + } + + if (fViewerCreator == null) { + try { + fViewerCreator= (IViewerCreator) fConfiguration.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + } + + if (fViewerCreator != null) { + // If we are going to return a new viewer, we want to preemptively deregister + // any handlers to avoid the logging of conflict warnings + if (currentViewer != null) { + CompareHandlerService[] compareHandlerService = (CompareHandlerService[]) Utilities.getAdapter(currentViewer, CompareHandlerService[].class); + if (compareHandlerService != null) { + for (int i = 0; i < compareHandlerService.length; i++) { + compareHandlerService[i].dispose(); + } + } + } + Viewer viewer= fViewerCreator.createViewer(parent, mp); + if (viewer != null) + fViewerClass= viewer.getClass(); + return viewer; + } + + return null; + } + + public String getExtension() { + return fConfiguration.getAttribute(EXTENSIONS_ATTRIBUTE); + } + + String getLabel() { + return fConfiguration.getAttribute(LABEL_ATTRIBUTE); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java new file mode 100644 index 000000000..6bd7a541f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal; + + +public class ViewerSwitchingCancelled extends RuntimeException { + + private static final long serialVersionUID = 1L; + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java new file mode 100644 index 000000000..14ebbc46a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2006 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.internal; + +import java.util.*; + +import org.eclipse.jface.operation.IRunnableWithProgress; + +/** + * A work queue maintains a list of tasks that need to be run. + * If the same task is added multiple times, the last occurrence of + * the task will be run(i.e. the task will be removed from it's + * previous location and aded to the end of the queue. + */ +public class WorkQueue { + + private List runnables = new ArrayList(); + + public boolean add(IRunnableWithProgress runnable) { + if (runnables.contains(runnable)) + runnables.remove(runnable); + return runnables.add(runnable); + } + + public void clear() { + runnables.clear(); + } + + public boolean contains(IRunnableWithProgress runnable) { + return runnables.contains(runnable); + } + + public boolean isEmpty() { + return runnables.isEmpty(); + } + + public boolean remove(IRunnableWithProgress runnable) { + return runnables.remove(runnable); + } + + public int size() { + return runnables.size(); + } + public IRunnableWithProgress remove() { + return (IRunnableWithProgress)runnables.remove(0); + } + + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java new file mode 100644 index 000000000..69455f981 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.*; +import org.eclipse.jface.operation.IRunnableWithProgress; + +/** + * A worker performs a set of tasks in order and accumulates any errors + * that may have occurred. If the same task is queued multiple times, + * the last occurrence will be run. If a task is queued while it is + * running, the running task will be canceled and the task added + * to the end of the work queue. + */ +public class Worker implements IRunnableWithProgress { + + private final WorkQueue work = new WorkQueue(); + private boolean isWorking; + private final List errors = new ArrayList(); + private WorkProgressMonitor currentMonitor; + private IRunnableWithProgress currentTask; + private final String taskName; + + /** + * Progress monitor that supports local cancelation of a task. + */ + private static class WorkProgressMonitor extends ProgressMonitorWrapper { + private boolean localCancel; + protected WorkProgressMonitor(IProgressMonitor monitor) { + super(monitor); + } + public void cancelTask() { + localCancel = true; + } + public boolean isCanceled() { + return localCancel || super.isCanceled(); + } + } + + public Worker(String taskName) { + this.taskName = taskName; + } + + public void run(IProgressMonitor monitor) { + errors.clear(); + SubMonitor pm = SubMonitor.convert(monitor, getTaskName(), 100); + try { + isWorking = true; + while (!work.isEmpty()) { + try { + performNextTask(pm); + checkCancelled(pm); + } catch (OperationCanceledException e) { + // Only cancel all the work if the outer monitor is canceled + checkCancelled(pm); + } catch (InterruptedException e) { + // Only cancel all the work if the outer monitor is canceled + checkCancelled(pm); + } catch (InvocationTargetException e) { + handleError(e.getTargetException()); + } + pm.setWorkRemaining(100); + } + } catch (OperationCanceledException e) { + // The user chose to cancel + work.clear(); + } finally { + isWorking = false; + if (monitor!= null) + monitor.done(); + currentMonitor = null; + currentTask = null; + } + } + + private WorkProgressMonitor subMonitorFor(SubMonitor pm, int ticks) { + return new WorkProgressMonitor(pm.newChild(ticks)); + } + + private void handleError(Throwable targetException) { + errors.add(targetException); + } + + public Throwable[] getErrors() { + return (Throwable[]) errors.toArray(new Throwable[errors.size()]); + } + + private void checkCancelled(SubMonitor pm) { + if (pm.isCanceled()) + throw new OperationCanceledException(); + } + + protected String getTaskName() { + return taskName; + } + + private void performNextTask(SubMonitor pm) throws InvocationTargetException, InterruptedException { + synchronized (this) { + if (work.isEmpty()) + return; + currentTask = work.remove(); + } + currentMonitor = subMonitorFor(pm, 10); + currentTask.run(currentMonitor); + } + + public synchronized void add(IRunnableWithProgress r) { + if (currentTask != null && currentTask.equals(r)) { + currentMonitor.cancelTask(); + } + work.add(r); + } + + public boolean isWorking() { + return isWorking; + } + + public boolean hasWork() { + return isWorking() || !work.isEmpty(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java new file mode 100644 index 000000000..7dd38c9d4 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2006 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.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.operation.IRunnableWithProgress; + +public class WorkerJob extends Job { + + private final Worker worker; + + public WorkerJob(String name) { + super(name); + worker = new Worker(name); + } + + protected IStatus run(IProgressMonitor monitor) { + worker.run(monitor); + // reschedule to ensure we don't miss a task + IStatus result = getResult(worker); + schedule(); + return result; + } + + private IStatus getResult(Worker w) { + Throwable[] errors = w.getErrors(); + if (errors.length == 0) + return Status.OK_STATUS; + if (errors.length == 1) + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, errors[0].getMessage(), errors[0]); + List statii = new ArrayList(); + for (int i = 0; i < errors.length; i++) { + Throwable throwable = errors[i]; + statii.add(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, errors[0].getMessage(), throwable)); + } + return new MultiStatus(CompareUIPlugin.PLUGIN_ID, 0, (IStatus[]) statii.toArray(new IStatus[statii.size()]), CompareMessages.WorkerJob_0, null); + } + + public boolean shouldRun() { + return worker.hasWork(); + } + + public void add(IRunnableWithProgress runnable) { + worker.add(runnable); + schedule(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java new file mode 100644 index 000000000..463d8d159 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java @@ -0,0 +1,1409 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.internal.merge; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import org.eclipse.jface.operation.IRunnableWithProgress; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; + +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressService; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.contentmergeviewer.ITokenComparator; +import org.eclipse.compare.internal.CompareContentViewerSwitchingPane; +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.DocLineComparator; +import org.eclipse.compare.internal.MergeViewerContentProvider; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.core.LCS; +import org.eclipse.compare.rangedifferencer.IRangeComparator; +import org.eclipse.compare.rangedifferencer.RangeDifference; +import org.eclipse.compare.rangedifferencer.RangeDifferencer; +import org.eclipse.compare.structuremergeviewer.Differencer; + +/** + * A document merger manages the differences between two documents + * for either a 2-way or 3-way comparison. + * <p> + * This class should not have any UI dependencies. + */ +public class DocumentMerger { + + private static final String DIFF_RANGE_CATEGORY = CompareUIPlugin.PLUGIN_ID + ".DIFF_RANGE_CATEGORY"; //$NON-NLS-1$ + + /** Selects between smartTokenDiff and mergingTokenDiff */ + private static final boolean USE_MERGING_TOKEN_DIFF= false; + + /** if true copying conflicts from one side to other concatenates both sides */ + private static final boolean APPEND_CONFLICT= true; + + /** All diffs for calculating scrolling position (includes line ranges without changes) */ + private ArrayList fAllDiffs; + /** Subset of above: just real differences. */ + private ArrayList fChangeDiffs; + + private final boolean fLeftIsLocal; + + private IDocumentMergerInput fInput; + + /** + * Interface that defines that input to the document merge process + */ + public interface IDocumentMergerInput { + + IDocument getDocument(char contributor); + + Position getRegion(char contributor); + + boolean isIgnoreAncestor(); + + boolean isThreeWay(); + + CompareConfiguration getCompareConfiguration(); + + ITokenComparator createTokenComparator(String s); + + boolean isHunkOnLeft(); + + int getHunkStart(); + + boolean isPatchHunk(); + + boolean isShowPseudoConflicts(); + + boolean isPatchHunkOk(); + } + + public class Diff { + /** character range in ancestor document */ + Position fAncestorPos; + /** character range in left document */ + Position fLeftPos; + /** character range in right document */ + Position fRightPos; + /** if this is a TokenDiff fParent points to the enclosing LineDiff */ + Diff fParent; + /** if Diff has been resolved */ + boolean fResolved; + int fDirection; + boolean fIsToken= false; + /** child token diffs */ + ArrayList fDiffs; + boolean fIsWhitespace= false; + + /* + * Create Diff from two ranges and an optional parent diff. + */ + Diff(Diff parent, int dir, IDocument ancestorDoc, Position aRange, int ancestorStart, int ancestorEnd, + IDocument leftDoc, Position lRange, int leftStart, int leftEnd, + IDocument rightDoc, Position rRange, int rightStart, int rightEnd) { + fParent= parent != null ? parent : this; + fDirection= dir; + + fLeftPos= createPosition(leftDoc, lRange, leftStart, leftEnd); + fRightPos= createPosition(rightDoc, rRange, rightStart, rightEnd); + if (ancestorDoc != null) + fAncestorPos= createPosition(ancestorDoc, aRange, ancestorStart, ancestorEnd); + } + + public Position getPosition(char type) { + switch (type) { + case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR: + return fAncestorPos; + case MergeViewerContentProvider.LEFT_CONTRIBUTOR: + return fLeftPos; + case MergeViewerContentProvider.RIGHT_CONTRIBUTOR: + return fRightPos; + } + return null; + } + + boolean isInRange(char type, int pos) { + Position p= getPosition(type); + return (pos >= p.offset) && (pos < (p.offset+p.length)); + } + + public String changeType() { + boolean leftEmpty= fLeftPos.length == 0; + boolean rightEmpty= fRightPos.length == 0; + + if (fDirection == RangeDifference.LEFT) { + if (!leftEmpty && rightEmpty) + return CompareMessages.TextMergeViewer_changeType_addition; + if (leftEmpty && !rightEmpty) + return CompareMessages.TextMergeViewer_changeType_deletion; + } else { + if (leftEmpty && !rightEmpty) + return CompareMessages.TextMergeViewer_changeType_addition; + if (!leftEmpty && rightEmpty) + return CompareMessages.TextMergeViewer_changeType_deletion; + } + return CompareMessages.TextMergeViewer_changeType_change; + } + + public Image getImage() { + int code= Differencer.CHANGE; + switch (fDirection) { + case RangeDifference.RIGHT: + code+= Differencer.LEFT; + break; + case RangeDifference.LEFT: + code+= Differencer.RIGHT; + break; + case RangeDifference.ANCESTOR: + case RangeDifference.CONFLICT: + code+= Differencer.CONFLICTING; + break; + } + if (code != 0) + return getCompareConfiguration().getImage(code); + return null; + } + + Position createPosition(IDocument doc, Position range, int start, int end) { + try { + int l= end-start; + if (range != null) { + int dl= range.length; + if (l > dl) + l= dl; + } else { + int dl= doc.getLength(); + if (start+l > dl) + l= dl-start; + } + + Position p= null; + try { + p= new Position(start, l); + } catch (RuntimeException ex) { + p= new Position(0, 0); + } + + try { + doc.addPosition(DIFF_RANGE_CATEGORY, p); + } catch (BadPositionCategoryException ex) { + // silently ignored + } + return p; + } catch (BadLocationException ee) { + // silently ignored + } + return null; + } + + void add(Diff d) { + if (fDiffs == null) + fDiffs= new ArrayList(); + fDiffs.add(d); + } + + public boolean isDeleted() { + if (fAncestorPos != null && fAncestorPos.isDeleted()) + return true; + return fLeftPos.isDeleted() || fRightPos.isDeleted(); + } + + void setResolved(boolean r) { + fResolved= r; + if (r) + fDiffs= null; + } + + public boolean isResolved() { + if (!fResolved && fDiffs != null) { + Iterator e= fDiffs.iterator(); + while (e.hasNext()) { + Diff d= (Diff) e.next(); + if (!d.isResolved()) + return false; + } + return true; + } + return fResolved; + } + +// private boolean isIncoming() { +// switch (fDirection) { +// case RangeDifference.RIGHT: +// if (fLeftIsLocal) +// return true; +// break; +// case RangeDifference.LEFT: +// if (!fLeftIsLocal) +// return true; +// break; +// } +// return false; +// } + + public boolean isIncomingOrConflicting() { + switch (fDirection) { + case RangeDifference.RIGHT: + if (fLeftIsLocal) + return true; + break; + case RangeDifference.LEFT: + if (!fLeftIsLocal) + return true; + break; + case RangeDifference.CONFLICT: + return true; + } + return false; + } + +// private boolean isUnresolvedIncoming() { +// if (fResolved) +// return false; +// return isIncoming(); +// } + + public boolean isUnresolvedIncomingOrConflicting() { + if (fResolved) + return false; + return isIncomingOrConflicting(); + } + + Position getPosition(int contributor) { + if (contributor == MergeViewerContentProvider.LEFT_CONTRIBUTOR) + return fLeftPos; + if (contributor == MergeViewerContentProvider.RIGHT_CONTRIBUTOR) + return fRightPos; + if (contributor == MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR) + return fAncestorPos; + return null; + } + + /* + * Returns true if given character range overlaps with this Diff. + */ + public boolean overlaps(int contributor, int start, int end, int docLength) { + Position h= getPosition(contributor); + if (h != null) { + int ds= h.getOffset(); + int de= ds + h.getLength(); + if ((start < de) && (end >= ds)) + return true; + if ((start == docLength) && (start <= de) && (end >= ds)) + return true; + } + return false; + } + + public int getMaxDiffHeight() { + Point region= new Point(0, 0); + int h= getLineRange(getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR), fLeftPos, region).y; + if (isThreeWay()) + h= Math.max(h, getLineRange(getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR), fAncestorPos, region).y); + return Math.max(h, getLineRange(getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR), fRightPos, region).y); + } + + public int getAncestorHeight() { + Point region= new Point(0, 0); + return getLineRange(getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR), fAncestorPos, region).y; + } + + public int getLeftHeight() { + Point region= new Point(0, 0); + return getLineRange(getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR), fLeftPos, region).y; + } + + public int getRightHeight() { + Point region= new Point(0, 0); + return getLineRange(getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR), fRightPos, region).y; + } + + public Diff[] getChangeDiffs(int contributor, IRegion region) { + if (fDiffs != null && intersectsRegion(contributor, region)) { + List result = new ArrayList(); + for (Iterator iterator = fDiffs.iterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.intersectsRegion(contributor, region)) { + result.add(diff); + } + } + return (Diff[]) result.toArray(new Diff[result.size()]); + } + return new Diff[0]; + } + + private boolean intersectsRegion(int contributor, IRegion region) { + Position p = getPosition(contributor); + if (p != null) + return p.overlapsWith(region.getOffset(), region.getLength()); + return false; + } + + public boolean hasChildren() { + return fDiffs != null && !fDiffs.isEmpty(); + } + + public int getKind() { + return fDirection; + } + + public boolean isToken() { + return fIsToken; + } + + public Diff getParent() { + return fParent; + } + + public Iterator childIterator() { + if (fDiffs == null) + return new ArrayList().iterator(); + return fDiffs.iterator(); + } + } + + public DocumentMerger(IDocumentMergerInput input) { + this.fInput = input; + fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$ + } + + /** + * Perform a two level 2- or 3-way diff. + * The first level is based on line comparison, the second level on token comparison. + * @throws CoreException + */ + public void doDiff() throws CoreException { + + fChangeDiffs= new ArrayList(); + IDocument lDoc = getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + IDocument rDoc = getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + + if (lDoc == null || rDoc == null) + return; + + Position lRegion= getRegion(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + Position rRegion= getRegion(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + + IDocument aDoc = null; + Position aRegion= null; + if (isThreeWay() && !isIgnoreAncestor()) { + aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); + aRegion= getRegion(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); + } + + resetPositions(lDoc); + resetPositions(rDoc); + resetPositions(aDoc); + + boolean ignoreWhiteSpace= isIgnoreWhitespace(); + + DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace); + DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace); + DocLineComparator sancestor= null; + if (aDoc != null) { + sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + /*if (isPatchHunk()) { + if (isHunkOnLeft()) { + sright= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + } else { + sleft= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + } + }*/ + } + + final Object[] result= new Object[1]; + final DocLineComparator sa= sancestor, sl= sleft, sr= sright; + IRunnableWithProgress runnable= new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException { + monitor.beginTask(CompareMessages.DocumentMerger_0, maxWork(sa, sl, sr)); + try { + result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr); + } catch (OutOfMemoryError ex) { + System.gc(); + throw new InvocationTargetException(ex); + } + if (monitor.isCanceled()) { // canceled + throw new InterruptedException(); + } + monitor.done(); + } + }; + + RangeDifference[] e= null; + try { + getCompareConfiguration().getContainer().run(true, true, runnable); + e= (RangeDifference[]) result[0]; + } catch (InvocationTargetException ex) { + // we create a NOCHANGE range for the whole document + Diff diff= new Diff(null, RangeDifference.NOCHANGE, + aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0, + lDoc, lRegion, 0, lDoc.getLength(), + rDoc, rRegion, 0, rDoc.getLength()); + + fAllDiffs = new ArrayList(); + fAllDiffs.add(diff); + throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, CompareMessages.DocumentMerger_1, ex.getTargetException())); + } catch (InterruptedException ex) { + // we create a NOCHANGE range for the whole document + Diff diff= new Diff(null, RangeDifference.NOCHANGE, + aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0, + lDoc, lRegion, 0, lDoc.getLength(), + rDoc, rRegion, 0, rDoc.getLength()); + + fAllDiffs = new ArrayList(); + fAllDiffs.add(diff); + return; + } + + if (isCapped(sa, sl, sr)) + fInput.getCompareConfiguration().setProperty( + CompareContentViewerSwitchingPane.OPTIMIZED_ALGORITHM_USED, + new Boolean(true)); + else + fInput.getCompareConfiguration().setProperty( + CompareContentViewerSwitchingPane.OPTIMIZED_ALGORITHM_USED, + new Boolean(false)); + + ArrayList newAllDiffs = new ArrayList(); + for (int i= 0; i < e.length; i++) { + RangeDifference es= e[i]; + + int ancestorStart= 0; + int ancestorEnd= 0; + if (sancestor != null) { + ancestorStart= sancestor.getTokenStart(es.ancestorStart()); + ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength()); + } + + int leftStart= sleft.getTokenStart(es.leftStart()); + int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength()); + + int rightStart= sright.getTokenStart(es.rightStart()); + int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength()); + + /*if (isPatchHunk()) { + if (isHunkOnLeft()) { + rightStart = rightEnd = getHunkStart(); + } else { + leftStart = leftEnd = getHunkStart(); + } + }*/ + + Diff diff= new Diff(null, es.kind(), + aDoc, aRegion, ancestorStart, ancestorEnd, + lDoc, lRegion, leftStart, leftEnd, + rDoc, rRegion, rightStart, rightEnd); + + newAllDiffs.add(diff); // remember all range diffs for scrolling + + if (isPatchHunk()) { + if (useChange(diff)) { + recordChangeDiff(diff); + } + } else { + if (ignoreWhiteSpace || useChange(es.kind())) { + + // Extract the string for each contributor. + String a= null; + if (sancestor != null) + a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength()); + String s= extract2(lDoc, sleft, es.leftStart(), es.leftLength()); + String d= extract2(rDoc, sright, es.rightStart(), es.rightLength()); + + // Indicate whether all contributors are whitespace + if (ignoreWhiteSpace + && (a == null || a.trim().length() == 0) + && s.trim().length() == 0 + && d.trim().length() == 0) { + diff.fIsWhitespace= true; + } + + // If the diff is of interest, record it and generate the token diffs + if (useChange(diff)) { + recordChangeDiff(diff); + if (s.length() > 0 && d.length() > 0) { + if (a == null && sancestor != null) + a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength()); + if (USE_MERGING_TOKEN_DIFF) + mergingTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s); + else + simpleTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s); + } + } + } + } + } + fAllDiffs = newAllDiffs; + } + + private boolean isCapped(DocLineComparator ancestor, + DocLineComparator left, DocLineComparator right) { + if (isCappingDisabled()) + return false; + int aLength = ancestor == null? 0 : ancestor.getRangeCount(); + int lLength = left.getRangeCount(); + int rLength = right.getRangeCount(); + if ((double) aLength * (double) lLength > LCS.TOO_LONG + || (double) aLength * (double) rLength > LCS.TOO_LONG + || (double) lLength * (double) rLength > LCS.TOO_LONG) + return true; + return false; + } + + public Diff findDiff(char type, int pos) throws CoreException { + + IDocument aDoc= null; + IDocument lDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + IDocument rDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + if (lDoc == null || rDoc == null) + return null; + + Position aRegion= null; + Position lRegion= null; + Position rRegion= null; + + boolean threeWay= isThreeWay(); + + if (threeWay && !isIgnoreAncestor()) + aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); + + boolean ignoreWhiteSpace= isIgnoreWhitespace(); + + DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace); + DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace); + DocLineComparator sancestor= null; + if (aDoc != null) + sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + + final Object[] result= new Object[1]; + final DocLineComparator sa= sancestor, sl= sleft, sr= sright; + IRunnableWithProgress runnable= new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException { + monitor.beginTask(CompareMessages.DocumentMerger_2, maxWork(sa, sl, sr)); + try { + result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr); + } catch (OutOfMemoryError ex) { + System.gc(); + throw new InvocationTargetException(ex); + } + if (monitor.isCanceled()) { // canceled + throw new InterruptedException(); + } + monitor.done(); + } + }; + IProgressService progressService= PlatformUI.getWorkbench().getProgressService(); + + RangeDifference[] e= null; + try { + progressService.run(true, true, runnable); + e= (RangeDifference[]) result[0]; + } catch (InvocationTargetException ex) { + throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, CompareMessages.DocumentMerger_3, ex.getTargetException())); + } catch (InterruptedException ex) { + // + } + + if (e != null) { + for (int i= 0; i < e.length; i++) { + RangeDifference es= e[i]; + + int kind= es.kind(); + + int ancestorStart= 0; + int ancestorEnd= 0; + if (sancestor != null) { + ancestorStart= sancestor.getTokenStart(es.ancestorStart()); + ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength()); + } + + int leftStart= sleft.getTokenStart(es.leftStart()); + int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength()); + + int rightStart= sright.getTokenStart(es.rightStart()); + int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength()); + + Diff diff= new Diff(null, kind, + aDoc, aRegion, ancestorStart, ancestorEnd, + lDoc, lRegion, leftStart, leftEnd, + rDoc, rRegion, rightStart, rightEnd); + + if (diff.isInRange(type, pos)) + return diff; + } + } + + return null; + } + + private void recordChangeDiff(Diff diff) { + fChangeDiffs.add(diff); // here we remember only the real diffs + } + + /*private boolean isHunkOnLeft() { + return fInput.isHunkOnLeft(); + } + + private int getHunkStart() { + return fInput.getHunkStart(); + }*/ + + private boolean isPatchHunk() { + return fInput.isPatchHunk(); + } + + private boolean isIgnoreWhitespace() { + return Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false); + } + + private boolean isCappingDisabled() { + return CompareUIPlugin.getDefault().getPreferenceStore().getBoolean(ComparePreferencePage.CAPPING_DISABLED); + } + + private IDocument getDocument(char contributor) { + return fInput.getDocument(contributor); + } + + private Position getRegion(char contributor) { + return fInput.getRegion(contributor); + } + + public boolean isIgnoreAncestor() { + return fInput.isIgnoreAncestor(); + } + + public boolean isThreeWay() { + return fInput.isThreeWay(); + } + + /** + * Return the compare configuration associated with this merger. + * @return the compare configuration associated with this merger + */ + public CompareConfiguration getCompareConfiguration() { + return fInput.getCompareConfiguration(); + } + + /* + * Returns true if kind of change should be shown. + */ + public boolean useChange(Diff diff) { + if (diff.fIsWhitespace) + return false; + int kind = diff.getKind(); + return useChange(kind); + } + + private boolean useChange(int kind) { + if (kind == RangeDifference.NOCHANGE) + return false; + if (fInput.getCompareConfiguration().isChangeIgnored(kind)) + return false; + if (kind == RangeDifference.ANCESTOR) + return fInput.isShowPseudoConflicts(); + return true; + } + + private int getTokenEnd(ITokenComparator tc, int start, int count) { + if (count <= 0) + return tc.getTokenStart(start); + int index= start + count - 1; + return tc.getTokenStart(index) + tc.getTokenLength(index); + } + + private static int getTokenEnd2(ITokenComparator tc, int start, int length) { + return tc.getTokenStart(start + length); + } + + /** + * Returns the content of lines in the specified range as a String. + * This includes the line separators. + * + * @param doc the document from which to extract the characters + * @param start index of first line + * @param length number of lines + * @return the contents of the specified line range as a String + */ + private String extract2(IDocument doc, ITokenComparator tc, int start, int length) { + int count= tc.getRangeCount(); + if (length > 0 && count > 0) { + +// +// int startPos= tc.getTokenStart(start); +// int endPos= startPos; +// +// if (length > 1) +// endPos= tc.getTokenStart(start + (length-1)); +// endPos+= tc.getTokenLength(start + (length-1)); +// + + int startPos= tc.getTokenStart(start); + int endPos; + + if (length == 1) { + endPos= startPos + tc.getTokenLength(start); + } else { + endPos= tc.getTokenStart(start + length); + } + + try { + return doc.get(startPos, endPos - startPos); + } catch (BadLocationException e) { + // silently ignored + } + + } + return ""; //$NON-NLS-1$ + } + + private static IRegion toRegion(Position position) { + if (position != null) + return new Region(position.getOffset(), position.getLength()); + return null; + } + + /* + * Performs a "smart" token based 3-way diff on the character range specified by the given baseDiff. + * It is "smart" because it tries to minimize the number of token diffs by merging them. + */ + private void mergingTokenDiff(Diff baseDiff, + IDocument ancestorDoc, String a, + IDocument rightDoc, String d, + IDocument leftDoc, String s) { + ITokenComparator sa= null; + int ancestorStart= 0; + if (ancestorDoc != null) { + sa= createTokenComparator(a); + ancestorStart= baseDiff.fAncestorPos.getOffset(); + } + + int rightStart= baseDiff.fRightPos.getOffset(); + ITokenComparator sm= createTokenComparator(d); + + int leftStart= baseDiff.fLeftPos.getOffset(); + ITokenComparator sy= createTokenComparator(s); + + RangeDifference[] r= RangeDifferencer.findRanges(sa, sy, sm); + for (int i= 0; i < r.length; i++) { + RangeDifference es= r[i]; + // determine range of diffs in one line + int start= i; + int leftLine= -1; + int rightLine= -1; + try { + leftLine= leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart())); + rightLine= rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart())); + } catch (BadLocationException e) { + // silently ignored + } + i++; + for (; i < r.length; i++) { + es= r[i]; + try { + if (leftLine != leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart()))) + break; + if (rightLine != rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart()))) + break; + } catch (BadLocationException e) { + // silently ignored + } + } + int end= i; + + // find first diff from left + RangeDifference first= null; + for (int ii= start; ii < end; ii++) { + es= r[ii]; + if (useChange(es.kind())) { + first= es; + break; + } + } + + // find first diff from mine + RangeDifference last= null; + for (int ii= end-1; ii >= start; ii--) { + es= r[ii]; + if (useChange(es.kind())) { + last= es; + break; + } + } + + if (first != null && last != null) { + + int ancestorStart2= 0; + int ancestorEnd2= 0; + if (ancestorDoc != null) { + ancestorStart2= ancestorStart+sa.getTokenStart(first.ancestorStart()); + ancestorEnd2= ancestorStart+getTokenEnd(sa, last.ancestorStart(), last.ancestorLength()); + } + + int leftStart2= leftStart+sy.getTokenStart(first.leftStart()); + int leftEnd2= leftStart+getTokenEnd(sy, last.leftStart(), last.leftLength()); + + int rightStart2= rightStart+sm.getTokenStart(first.rightStart()); + int rightEnd2= rightStart+getTokenEnd(sm, last.rightStart(), last.rightLength()); + Diff diff= new Diff(baseDiff, first.kind(), + ancestorDoc, null, ancestorStart2, ancestorEnd2, + leftDoc, null, leftStart2, leftEnd2, + rightDoc, null, rightStart2, rightEnd2); + diff.fIsToken= true; + baseDiff.add(diff); + } + } + } + + /* + * Performs a token based 3-way diff on the character range specified by the given baseDiff. + */ + private void simpleTokenDiff(final Diff baseDiff, + IDocument ancestorDoc, String a, + IDocument rightDoc, String d, + IDocument leftDoc, String s) { + + int ancestorStart= 0; + ITokenComparator sa= null; + if (ancestorDoc != null) { + ancestorStart= baseDiff.fAncestorPos.getOffset(); + sa= createTokenComparator(a); + } + + int rightStart= baseDiff.fRightPos.getOffset(); + ITokenComparator sm= createTokenComparator(d); + + int leftStart= baseDiff.fLeftPos.getOffset(); + ITokenComparator sy= createTokenComparator(s); + + RangeDifference[] e= RangeDifferencer.findRanges(sa, sy, sm); + for (int i= 0; i < e.length; i++) { + RangeDifference es= e[i]; + int kind= es.kind(); + if (kind != RangeDifference.NOCHANGE) { + + int ancestorStart2= ancestorStart; + int ancestorEnd2= ancestorStart; + if (ancestorDoc != null) { + ancestorStart2 += sa.getTokenStart(es.ancestorStart()); + ancestorEnd2 += getTokenEnd(sa, es.ancestorStart(), es.ancestorLength()); + } + + int leftStart2= leftStart + sy.getTokenStart(es.leftStart()); + int leftEnd2= leftStart + getTokenEnd(sy, es.leftStart(), es.leftLength()); + + int rightStart2= rightStart + sm.getTokenStart(es.rightStart()); + int rightEnd2= rightStart + getTokenEnd(sm, es.rightStart(), es.rightLength()); + + Diff diff= new Diff(baseDiff, kind, + ancestorDoc, null, ancestorStart2, ancestorEnd2, + leftDoc, null, leftStart2, leftEnd2, + rightDoc, null, rightStart2, rightEnd2); + + // ensure that token diff is smaller than basediff + int leftS= baseDiff.fLeftPos.offset; + int leftE= baseDiff.fLeftPos.offset+baseDiff.fLeftPos.length; + int rightS= baseDiff.fRightPos.offset; + int rightE= baseDiff.fRightPos.offset+baseDiff.fRightPos.length; + if (leftS != leftStart2 || leftE != leftEnd2 || + rightS != rightStart2 || rightE != rightEnd2) { + diff.fIsToken= true; + // add to base Diff + baseDiff.add(diff); + } + } + } + } + + private ITokenComparator createTokenComparator(String s) { + return fInput.createTokenComparator(s); + } + + private static int maxWork(IRangeComparator a, IRangeComparator l, IRangeComparator r) { + int ln= l.getRangeCount(); + int rn= r.getRangeCount(); + if (a != null) { + int an= a.getRangeCount(); + return (2 * Math.max(an, ln)) + (2 * Math.max(an, rn)); + } + return 2 * Math.max(ln, rn); + } + + private void resetPositions(IDocument doc) { + if (doc == null) + return; + try { + doc.removePositionCategory(DIFF_RANGE_CATEGORY); + } catch (BadPositionCategoryException e) { + // Ignore + } + doc.addPositionCategory(DIFF_RANGE_CATEGORY); + } + + /* + * Returns the start line and the number of lines which correspond to the given position. + * Starting line number is 0 based. + */ + protected Point getLineRange(IDocument doc, Position p, Point region) { + + if (p == null || doc == null) { + region.x= 0; + region.y= 0; + return region; + } + + int start= p.getOffset(); + int length= p.getLength(); + + int startLine= 0; + try { + startLine= doc.getLineOfOffset(start); + } catch (BadLocationException e) { + // silently ignored + } + + int lineCount= 0; + + if (length == 0) { +// // if range length is 0 and if range starts a new line +// try { +// if (start == doc.getLineStartOffset(startLine)) { +// lines--; +// } +// } catch (BadLocationException e) { +// lines--; +// } + + } else { + int endLine= 0; + try { + endLine= doc.getLineOfOffset(start + length - 1); // why -1? + } catch (BadLocationException e) { + // silently ignored + } + lineCount= endLine-startLine+1; + } + + region.x= startLine; + region.y= lineCount; + return region; + } + + public Diff findDiff(Position p, boolean left) { + for (Iterator iterator = fAllDiffs.iterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + Position diffPos; + if (left) { + diffPos = diff.fLeftPos; + } else { + diffPos = diff.fRightPos; + } + // If the element falls within a diff, highlight that diff + if (diffPos.offset + diffPos.length >= p.offset && diff.fDirection != RangeDifference.NOCHANGE) + return diff; + // Otherwise, highlight the first diff after the elements position + if (diffPos.offset >= p.offset) + return diff; + } + return null; + } + + public void reset() { + fChangeDiffs= null; + fAllDiffs= null; + } + + /** + * Returns the virtual position for the given view position. + * @param contributor + * @param vpos + * @return the virtual position for the given view position + */ + public int realToVirtualPosition(char contributor, int vpos) { + + if (fAllDiffs == null) + return vpos; + + int viewPos= 0; // real view position + int virtualPos= 0; // virtual position + Point region= new Point(0, 0); + + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + Position pos= diff.getPosition(contributor); + getLineRange(getDocument(contributor),pos, region); + int realHeight= region.y; + int virtualHeight= diff.getMaxDiffHeight(); + if (vpos <= viewPos + realHeight) { // OK, found! + vpos-= viewPos; // make relative to this slot + // now scale position within this slot to virtual slot + if (realHeight <= 0) + vpos= 0; + else + vpos= (vpos*virtualHeight)/realHeight; + return virtualPos+vpos; + } + viewPos+= realHeight; + virtualPos+= virtualHeight; + } + return virtualPos; + } + + /** + * maps given virtual position into a real view position of this view. + * @param contributor + * @param v + * @return the real view position + */ + public int virtualToRealPosition(char contributor, int v) { + + if (fAllDiffs == null) + return v; + + int virtualPos= 0; + int viewPos= 0; + Point region= new Point(0, 0); + + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + Position pos= diff.getPosition(contributor); + int viewHeight= getLineRange(getDocument(contributor), pos, region).y; + int virtualHeight= diff.getMaxDiffHeight(); + if (v < (virtualPos + virtualHeight)) { + v-= virtualPos; // make relative to this slot + if (viewHeight <= 0) { + v= 0; + } else { + v= (int) (v * ((double)viewHeight/virtualHeight)); + } + return viewPos+v; + } + virtualPos+= virtualHeight; + viewPos+= viewHeight; + } + return viewPos; + } + + /* + * Calculates virtual height (in lines) of views by adding the maximum of corresponding diffs. + */ + public int getVirtualHeight() { + int h= 1; + if (fAllDiffs != null) { + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + h+= diff.getMaxDiffHeight(); + } + } + return h; + } + + /* + * Calculates height (in lines) of right view by adding the height of the right diffs. + */ + public int getRightHeight() { + int h= 1; + if (fAllDiffs != null) { + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + h+= diff.getRightHeight(); + } + } + return h; + } + + public int findInsertionPoint(Diff diff, char type) { + if (diff != null) { + switch (type) { + case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR: + if (diff.fAncestorPos != null) + return diff.fAncestorPos.offset; + break; + case MergeViewerContentProvider.LEFT_CONTRIBUTOR: + if (diff.fLeftPos != null) + return diff.fLeftPos.offset; + break; + case MergeViewerContentProvider.RIGHT_CONTRIBUTOR: + if (diff.fRightPos != null) + return diff.fRightPos.offset; + break; + } + } + return 0; + } + + public Diff[] getChangeDiffs(char contributor, IRegion region) { + if (fChangeDiffs == null) + return new Diff[0]; + List intersectingDiffs = new ArrayList(); + for (Iterator iterator = fChangeDiffs.iterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + Diff[] changeDiffs = diff.getChangeDiffs(contributor, region); + for (int i = 0; i < changeDiffs.length; i++) { + Diff changeDiff = changeDiffs[i]; + intersectingDiffs.add(changeDiff); + } + } + return (Diff[]) intersectingDiffs.toArray(new Diff[intersectingDiffs.size()]); + } + + public Diff findDiff(int viewportHeight, boolean synchronizedScrolling, Point size, int my) { + int virtualHeight= synchronizedScrolling ? getVirtualHeight() : getRightHeight(); + if (virtualHeight < viewportHeight) + return null; + + int yy, hh; + int y= 0; + if (fAllDiffs != null) { + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + int h= synchronizedScrolling ? diff.getMaxDiffHeight() + : diff.getRightHeight(); + if (useChange(diff.getKind()) && !diff.fIsWhitespace) { + + yy= (y*size.y)/virtualHeight; + hh= (h*size.y)/virtualHeight; + if (hh < 3) + hh= 3; + + if (my >= yy && my < yy+hh) + return diff; + } + y+= h; + } + } + return null; + } + + public boolean hasChanges() { + return fChangeDiffs != null && !fChangeDiffs.isEmpty(); + } + + public Iterator changesIterator() { + if (fChangeDiffs == null) + return new ArrayList().iterator(); + return fChangeDiffs.iterator(); + } + + public Iterator rangesIterator() { + if (fAllDiffs == null) + return new ArrayList().iterator(); + return fAllDiffs.iterator(); + } + + public boolean isFirstChildDiff(char contributor, int startOffset, + Diff diff) { + if (!diff.hasChildren()) + return false; + Diff d = (Diff)diff.fDiffs.get(0); + Position p= d.getPosition(contributor); + return (p.getOffset() >= startOffset); + } + + public Diff getWrappedDiff(Diff diff, boolean down) { + if (fChangeDiffs != null && fChangeDiffs.size() > 0) { + if (down) + return (Diff) fChangeDiffs.get(0); + return (Diff) fChangeDiffs.get(fChangeDiffs.size()-1); + } + return null; + } + + /* + * Copy the contents of the given diff from one side to the other but + * doesn't reveal anything. + * Returns true if copy was successful. + */ + public boolean copy(Diff diff, boolean leftToRight) { + + if (diff != null && !diff.isResolved()) { + Position fromPos= null; + Position toPos= null; + IDocument fromDoc= null; + IDocument toDoc= null; + + if (leftToRight) { + fromPos= diff.getPosition(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + toPos= diff.getPosition(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + fromDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + toDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + } else { + fromPos= diff.getPosition(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + toPos= diff.getPosition(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + fromDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + toDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + } + + if (fromDoc != null) { + + int fromStart= fromPos.getOffset(); + int fromLen= fromPos.getLength(); + + int toStart= toPos.getOffset(); + int toLen= toPos.getLength(); + + try { + String s= null; + + switch (diff.getKind()) { + case RangeDifference.RIGHT: + case RangeDifference.LEFT: + s= fromDoc.get(fromStart, fromLen); + break; + case RangeDifference.ANCESTOR: + break; + case RangeDifference.CONFLICT: + if (APPEND_CONFLICT) { + s= toDoc.get(toStart, toLen); + String ls = TextUtilities.getDefaultLineDelimiter(toDoc); + if (!s.endsWith(ls)) + s += ls; + s+= fromDoc.get(fromStart, fromLen); + } else + s= fromDoc.get(fromStart, fromLen); + break; + } + if (s != null) { + toDoc.replace(toStart, toLen, s); + toPos.setOffset(toStart); + toPos.setLength(s.length()); + } + + } catch (BadLocationException e) { + // silently ignored + } + } + + diff.setResolved(true); + return true; + } + return false; + } + + public int changesCount() { + if (fChangeDiffs == null) + return 0; + return fChangeDiffs.size(); + } + + public Diff findDiff(char contributor, int rangeStart, int rangeEnd) { + if (hasChanges()) { + for (Iterator iterator = changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted() || diff.getKind() == RangeDifference.NOCHANGE) + continue; + if (diff.overlaps(contributor, rangeStart, rangeEnd, getDocument(contributor).getLength())) + return diff; + } + } + return null; + } + + public Diff findDiff(char contributor, Position range) { + int start= range.getOffset(); + int end= start + range.getLength(); + return findDiff(contributor, start, end); + } + + public Diff findNext(char contributor, int start, int end, boolean deep) { + return findNext(contributor, fChangeDiffs, start, end, deep); + } + + private Diff findNext(char contributor, List v, int start, int end, boolean deep) { + if (v == null) + return null; + for (int i= 0; i < v.size(); i++) { + Diff diff= (Diff) v.get(i); + Position p= diff.getPosition(contributor); + if (p != null) { + int startOffset= p.getOffset(); + if (end < startOffset) // <= + return diff; + if (deep && diff.hasChildren()) { + Diff d= null; + int endOffset= startOffset + p.getLength(); + if (start == startOffset && (end == endOffset || end == endOffset-1)) { + d= findNext(contributor, diff.fDiffs, start-1, start-1, deep); + } else if (end < endOffset) { + d= findNext(contributor, diff.fDiffs, start, end, deep); + } + if (d != null) + return d; + } + } + } + return null; + } + + public Diff findPrev(char contributor, int start, int end, boolean deep) { + return findPrev(contributor, fChangeDiffs, start, end, deep); + } + + private Diff findPrev(char contributor, List v, int start, int end, boolean deep) { + if (v == null) + return null; + for (int i= v.size()-1; i >= 0; i--) { + Diff diff= (Diff) v.get(i); + Position p= diff.getPosition(contributor); + if (p != null) { + int startOffset= p.getOffset(); + int endOffset= startOffset + p.getLength(); + if (start > endOffset) { + if (deep && diff.hasChildren()) { + // If we are going deep, find the last change in the diff + return findPrev(contributor, diff.fDiffs, end, end, deep); + } + return diff; + } + if (deep && diff.hasChildren()) { + Diff d= null; + if (start == startOffset && end == endOffset) { + // A whole diff is selected so we'll fall through + // and go the the last change in the previous diff + } else if (start >= startOffset) { + // If we are at or before the first diff, select the + // entire diff so next and previous are symmetrical + if (isFirstChildDiff(contributor, startOffset, diff)) { + return diff; + } + d= findPrev(contributor, diff.fDiffs, start, end, deep); + } + if (d != null) + return d; + } + } + } + return null; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java new file mode 100644 index 000000000..4b4891637 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.merge; + +import java.io.*; +import java.util.ArrayList; +import org.eclipse.compare.rangedifferencer.IRangeComparator; + +/** + * This implementation of IRangeComparator breaks an input stream into lines. + */ +class LineComparator implements IRangeComparator { + + private String[] fLines; + + public LineComparator(InputStream is, String encoding) throws IOException { + + BufferedReader br = new BufferedReader(new InputStreamReader(is, encoding)); + String line; + ArrayList ar = new ArrayList(); + while ((line = br.readLine()) != null) { + ar.add(line); + } + // It is the responsibility of the caller to close the stream + fLines = (String[]) ar.toArray(new String[ar.size()]); + } + + String getLine(int ix) { + return fLines[ix]; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#getRangeCount() + */ + public int getRangeCount() { + return fLines.length; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#rangesEqual(int, org.eclipse.compare.rangedifferencer.IRangeComparator, int) + */ + public boolean rangesEqual(int thisIndex, IRangeComparator other, + int otherIndex) { + String s1 = fLines[thisIndex]; + String s2 = ((LineComparator) other).fLines[otherIndex]; + return s1.equals(s2); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#skipRangeComparison(int, int, org.eclipse.compare.rangedifferencer.IRangeComparator) + */ + public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) { + return false; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java new file mode 100644 index 000000000..f3fa54dea --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.internal.merge; + +import org.eclipse.osgi.util.NLS; + +public final class MergeMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.merge.MergeMessages";//$NON-NLS-1$ + + private MergeMessages() { + // Do not instantiate + } + + public static String TextAutoMerge_inputEncodingError; + public static String TextAutoMerge_outputEncodingError; + public static String TextAutoMerge_outputIOError; + public static String TextAutoMerge_conflict; + + static { + NLS.initializeMessages(BUNDLE_NAME, MergeMessages.class); + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties new file mode 100644 index 000000000..a548ea8e6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2004, 2006 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 +############################################################################### + +TextAutoMerge_inputEncodingError= Unsupported encoding for input streams +TextAutoMerge_outputEncodingError= Unsupported encoding for output streams +TextAutoMerge_outputIOError= IO error on writing +TextAutoMerge_conflict= Conflict: cannot auto merge diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java new file mode 100644 index 000000000..2aaecd12b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2003, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.merge; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.eclipse.compare.*; +import org.eclipse.compare.rangedifferencer.RangeDifference; +import org.eclipse.compare.rangedifferencer.RangeDifferencer; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * A simple merger for streams containing text lines. + */ +public class TextStreamMerger implements IStreamMerger { + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.internal.merge.IAutoMerger#automerge(java.io.OutputStream, + * org.eclipse.core.resources.IEncodedStorage, + * org.eclipse.core.resources.IEncodedStorage, + * org.eclipse.core.resources.IEncodedStorage, + * org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus merge(OutputStream output, String outputEncoding, InputStream ancestor, String ancestorEncoding, InputStream target, String targetEncoding, InputStream other, String otherEncoding, IProgressMonitor monitor) { + + LineComparator a, t, o; + + try { + a= new LineComparator(ancestor, ancestorEncoding); + t= new LineComparator(target, targetEncoding); + o= new LineComparator(other, otherEncoding); + } catch (UnsupportedEncodingException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, MergeMessages.TextAutoMerge_inputEncodingError, e); + } catch (IOException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, e.getMessage(), e); + } + + try { + String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$ + if (lineSeparator == null) + lineSeparator= "\n"; //$NON-NLS-1$ + + RangeDifference[] diffs= RangeDifferencer.findRanges(monitor, a, t, o); + + for (int i= 0; i < diffs.length; i++) { + RangeDifference rd= diffs[i]; + switch (rd.kind()) { + case RangeDifference.ANCESTOR: // pseudo conflict + case RangeDifference.NOCHANGE: + case RangeDifference.RIGHT: + for (int j= rd.rightStart(); j < rd.rightEnd(); j++) { + String s= o.getLine(j); + output.write(s.getBytes(outputEncoding)); + output.write(lineSeparator.getBytes(outputEncoding)); + } + break; + + case RangeDifference.LEFT: + for (int j= rd.leftStart(); j < rd.leftEnd(); j++) { + String s= t.getLine(j); + output.write(s.getBytes(outputEncoding)); + output.write(lineSeparator.getBytes(outputEncoding)); + } + break; + + case RangeDifference.CONFLICT: + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, CONFLICT, MergeMessages.TextAutoMerge_conflict, null); + + default: + break; + } + } + + } catch (UnsupportedEncodingException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, MergeMessages.TextAutoMerge_outputEncodingError, e); + } catch (IOException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, MergeMessages.TextAutoMerge_outputIOError, e); + } + + return Status.OK_STATUS; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java new file mode 100644 index 000000000..1a8e3d44c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal.patch; + +import java.util.Arrays; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.swt.graphics.*; + +/** + * An DecoratorOverlayIcon consists of a main icon and several adornments. + * Copied until bug 164394 is resolved. + */ +class DecoratorOverlayIcon extends CompositeImageDescriptor { + // the base image + private Image base; + + // the overlay images + private ImageDescriptor[] overlays; + + // the size + private Point size; + + + /** + * OverlayIcon constructor. + * + * @param baseImage the base image + * @param overlaysArray the overlay images + * @param sizeValue the size + */ + public DecoratorOverlayIcon(Image baseImage, + ImageDescriptor[] overlaysArray, Point sizeValue) { + this.base = baseImage; + this.overlays = overlaysArray; + this.size = sizeValue; + } + + /** + * Draw the overlays for the receiver. + * @param overlaysArray the overlay images + */ + protected void drawOverlays(ImageDescriptor[] overlaysArray) { + + for (int i = 0; i < overlays.length; i++) { + ImageDescriptor overlay = overlaysArray[i]; + if (overlay == null) { + continue; + } + ImageData overlayData = overlay.getImageData(); + //Use the missing descriptor if it is not there. + if (overlayData == null) { + overlayData = ImageDescriptor.getMissingImageDescriptor() + .getImageData(); + } + switch (i) { + case IDecoration.TOP_LEFT: + drawImage(overlayData, 0, 0); + break; + case IDecoration.TOP_RIGHT: + drawImage(overlayData, size.x - overlayData.width, 0); + break; + case IDecoration.BOTTOM_LEFT: + drawImage(overlayData, 0, size.y - overlayData.height); + break; + case IDecoration.BOTTOM_RIGHT: + drawImage(overlayData, size.x - overlayData.width, size.y + - overlayData.height); + break; + } + } + } + + public boolean equals(Object o) { + if (!(o instanceof DecoratorOverlayIcon)) { + return false; + } + DecoratorOverlayIcon other = (DecoratorOverlayIcon) o; + return base.equals(other.base) + && Arrays.equals(overlays, other.overlays); + } + + public int hashCode() { + int code = base.hashCode(); + for (int i = 0; i < overlays.length; i++) { + if (overlays[i] != null) { + code ^= overlays[i].hashCode(); + } + } + return code; + } + + protected void drawCompositeImage(int width, int height) { + ImageDescriptor underlay = overlays[IDecoration.UNDERLAY]; + if (underlay != null) { + drawImage(underlay.getImageData(), 0, 0); + } + drawImage(base.getImageData(), 0, 0); + drawOverlays(overlays); + } + + protected Point getSize() { + return size; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.resource.CompositeImageDescriptor#getTransparentPixel() + */ + protected int getTransparentPixel() { + return base.getImageData().transparentPixel; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java new file mode 100644 index 000000000..5a0134edd --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 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.internal.patch; + +import java.util.Comparator; +import java.util.regex.Pattern; + +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DocumentRangeNode; +import org.eclipse.jface.util.Policy; +import org.eclipse.jface.viewers.ViewerSorter; + +public class DiffViewerComparator extends ViewerSorter { + + public boolean isSorterProperty(Object element, Object property) { + return false; + } + + public int category(Object node) { + if (node instanceof DiffNode) { + Object o= ((DiffNode) node).getId(); + if (o instanceof DocumentRangeNode) + return ((DocumentRangeNode) o).getTypeCode(); + } + return 0; + } + + protected Comparator getComparator() { + return new Comparator() { + public int compare(Object arg0, Object arg1) { + String label0 = arg0 == null ? "" : arg0.toString(); //$NON-NLS-1$ + String label1 = arg1 == null ? "" : arg1.toString(); //$NON-NLS-1$ + + // see org.eclipse.compare.internal.patch.Hunk.getDescription() + String pattern = "\\d+,\\d+ -> \\d+,\\d+.*"; //$NON-NLS-1$ + + if (Pattern.matches(pattern, label0) + && Pattern.matches(pattern, label1)) { + int oldStart0 = Integer.parseInt(label0.split(",")[0]); //$NON-NLS-1$ + int oldStart1 = Integer.parseInt(label1.split(",")[0]); //$NON-NLS-1$ + + return oldStart0 - oldStart1; + } + return Policy.getComparator().compare(arg0, arg1); + } + }; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java new file mode 100644 index 000000000..5841f269e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2009 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.internal.patch; + +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.patch.IFilePatch; +import org.eclipse.compare.patch.IFilePatchResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +public class FilePatch extends FilePatch2 implements IFilePatch { + + public FilePatch(IPath oldPath, long oldDate, IPath newPath, + long newDate) { + super(oldPath, oldDate, newPath, newDate); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.compare.patch.IFilePatch#apply(org.eclipse.core.resources + * .IStorage, org.eclipse.compare.patch.PatchConfiguration, + * org.eclipse.core.runtime.IProgressMonitor) + */ + public IFilePatchResult apply(IStorage content, + PatchConfiguration configuration, IProgressMonitor monitor) { + return apply(content != null ? Utilities.getReaderCreator(content) + : null, configuration, monitor); + } + + protected FilePatch2 create(IPath oldPath, long oldDate, IPath newPath, + long newDate) { + return new FilePatch(oldPath, oldDate, newPath, newDate); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java new file mode 100644 index 000000000..1fa0f3a79 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.internal.patch; + +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.core.resources.IResource; + +public class HunkDiffNode extends PatchDiffNode { + + private final HunkResult result; + + public static HunkDiffNode createDiffNode(PatchFileDiffNode parent, HunkResult result, boolean fullContext) { + return createDiffNode(parent, result, fullContext, fullContext, fullContext); + } + + public static HunkDiffNode createDiffNode(PatchFileDiffNode parent, HunkResult result, boolean ancestorFullContext, boolean leftFullContext, boolean rightFullContext) { + return new HunkDiffNode(result, parent, Differencer.CHANGE, getAncestorElement(result, ancestorFullContext), getLeftElement(result, leftFullContext), getRightElement(result, rightFullContext)); + } + + public static ITypedElement getRightElement(HunkResult result, boolean fullContext) { + return new HunkTypedElement(result, true /* isResult */, fullContext); + } + + private static ITypedElement getLeftElement(HunkResult result, + boolean fullContext) { + if (fullContext && !result.isOK()) + return new UnmatchedHunkTypedElement(result); + return new HunkTypedElement(result, false /* before state */, fullContext); + } + + public static ITypedElement getAncestorElement(HunkResult result, boolean fullContext) { + if (!fullContext && result.isOK()) { + return new HunkTypedElement(result, false /* before state */, fullContext); + } + if (!fullContext) { + // Don't provide an ancestor if the hunk didn't match or we're not doing fullContext + return null; + } + // Make the ancestor the same as the left so we have an incoming change + return new HunkTypedElement(result, false /* before state */, result.isOK()); + } + + public HunkDiffNode(HunkResult result, PatchFileDiffNode parent, int kind, ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(result.getHunk(), parent, kind, ancestor, left, right); + this.result = result; + } + + public HunkResult getHunkResult() { + return result; + } + + protected PatchConfiguration getConfiguration() { + return result.getDiffResult().getConfiguration(); + } + + public boolean isManuallyMerged() { + Object left = getLeft(); + if (left instanceof UnmatchedHunkTypedElement) { + UnmatchedHunkTypedElement element = (UnmatchedHunkTypedElement) left; + return element.isManuallyMerged(); + } + return false; + } + + public boolean isFuzzUsed() { + return result.getFuzz() > 0; + } + + public boolean isAllContextIgnored() { + int fuzz = result.getFuzz(); + if (fuzz > 0) { + String[] lines = result.getHunk().getLines(); + int contextLines = 0; + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + char c = line.charAt(0); + if (c == ' ') { + contextLines++; + } else { + if (contextLines > 0 && fuzz >= contextLines) { + return true; + } + contextLines = 0; + } + } + if (contextLines > 0 && fuzz >= contextLines) { + return true; + } + + } + return false; + } + + public IResource getResource() { + return ((PatchFileDiffNode)getParent()).getResource(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java new file mode 100644 index 000000000..4ff4ab349 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.internal.patch; + +import java.io.InputStream; + +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.DiffImageDescriptor; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.graphics.Image; + +public class HunkTypedElement implements ITypedElement, IEncodedStreamContentAccessor, IAdaptable { + + private final HunkResult fHunkResult; + private final boolean fIsAfterState; + private final boolean fFullContext; + + public HunkTypedElement(HunkResult result, boolean isAfterState, boolean fullContext) { + this.fHunkResult = result; + this.fIsAfterState = isAfterState; + this.fFullContext = fullContext; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getImage() + */ + public Image getImage() { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(fHunkResult.getDiffResult().getConfiguration()); + ImageDescriptor imageDesc = CompareUIPlugin.getImageDescriptor(ICompareUIConstants.HUNK_OBJ); + Image image = imageCache.createImage(imageDesc); + if (!fHunkResult.isOK()) { + return getHunkErrorImage(image, imageCache, true); + } else if (fHunkResult.getFuzz() > 0) { + return getHunkOverlayImage(image, imageCache, ICompareUIConstants.WARNING_OVERLAY, true); + } + return image; + } + + public static Image getHunkErrorImage(Image baseImage, LocalResourceManager imageCache, boolean onLeft) { + return getHunkOverlayImage(baseImage, imageCache, ICompareUIConstants.ERROR_OVERLAY, onLeft); + } + + private static Image getHunkOverlayImage(Image baseImage, LocalResourceManager imageCache, String path, boolean onLeft) { + ImageDescriptor desc = new DiffImageDescriptor(baseImage, CompareUIPlugin.getImageDescriptor(path), ICompareUIConstants.COMPARE_IMAGE_WIDTH, onLeft); + Image image = imageCache.createImage(desc); + return image; + } + + public boolean isManuallyMerged() { + return getPatcher().isManuallyMerged(getHunkResult().getHunk()); + } + + private Patcher getPatcher() { + return Patcher.getPatcher(fHunkResult.getDiffResult().getConfiguration()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getName() + */ + public String getName() { + return fHunkResult.getHunk().getLabel(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getType() + */ + public String getType() { + return fHunkResult.getDiffResult().getDiff().getTargetPath(fHunkResult.getDiffResult().getConfiguration()).getFileExtension(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IStreamContentAccessor#getContents() + */ + public InputStream getContents() throws CoreException { + String contents = fHunkResult.getContents(fIsAfterState, fFullContext); + return FileDiffResult.asInputStream(contents, fHunkResult.getCharset()); + } + + public String getCharset() throws CoreException { + return fHunkResult.getCharset(); + } + + public HunkResult getHunkResult() { + return fHunkResult; + } + + public Object getAdapter(Class adapter) { + if (adapter == IHunk.class) + return fHunkResult; + if (adapter == HunkResult.class) + return fHunkResult; + return Platform.getAdapterManager().getAdapter(this, adapter); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java new file mode 100644 index 000000000..7248fccb3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java @@ -0,0 +1,990 @@ +/******************************************************************************* + * 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 + * Sebastian Davids <sdavids@gmx.de> - layout tweaks + * Matt McCutchen <hashproduct+eclipse@gmail.com> - Bug 180358 [Apply Patch] Cursor jumps to beginning of filename field on keystroke + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.compare.internal.ICompareContextIds; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; + +import com.ibm.icu.text.MessageFormat; + +public class InputPatchPage extends WizardPage { + + // constants + protected static final int SIZING_TEXT_FIELD_WIDTH= 250; + protected static final int COMBO_HISTORY_LENGTH= 5; + + // dialog store id constants + private final static String PAGE_NAME= "PatchWizardPage1"; //$NON-NLS-1$ + private final static String STORE_PATCH_FILES_ID= PAGE_NAME+".PATCH_FILES"; //$NON-NLS-1$ + private final static String STORE_PATCH_URLS_ID= PAGE_NAME+".PATCH_URLS"; //$NON-NLS-1$ + private final static String STORE_INPUT_METHOD_ID= PAGE_NAME+".INPUT_METHOD"; //$NON-NLS-1$ + private final static String STORE_WORKSPACE_PATH_ID= PAGE_NAME+".WORKSPACE_PATH"; //$NON-NLS-1$ + + //patch input constants + protected final static int CLIPBOARD= 1; + protected final static int FILE= 2; + protected final static int WORKSPACE= 3; + protected final static int URL= 4; + + protected final static String INPUTPATCHPAGE_NAME= "InputPatchPage"; //$NON-NLS-1$ + + static final char SEPARATOR= System.getProperty("file.separator").charAt(0); //$NON-NLS-1$ + + private boolean fShowError= false; + private String fPatchSource; + private boolean fPatchRead= false; + private PatchWizard fPatchWizard; + private ActivationListener fActivationListener= new ActivationListener(); + + // SWT widgets + private Button fUseClipboardButton; + private Combo fPatchFileNameField; + private Button fPatchFileBrowseButton; + private Button fUsePatchFileButton; + private Button fUseWorkspaceButton; + private Button fUseURLButton; + private Combo fPatchURLField; + private Label fWorkspaceSelectLabel; + private TreeViewer fTreeViewer; + + class ActivationListener extends ShellAdapter { + public void shellActivated(ShellEvent e) { + // allow error messages if the selected input actually has something selected in it + fShowError=true; + switch(getInputMethod()) { + case FILE: + fShowError= (fPatchFileNameField.getText() != ""); //$NON-NLS-1$ + break; + case URL: + fShowError = (fPatchURLField.getText() != ""); //$NON-NLS-1$ + break; + case WORKSPACE: + fShowError= (!fTreeViewer.getSelection().isEmpty()); + break; + } + updateWidgetEnablements(); + } + } + + public InputPatchPage(PatchWizard pw) { + super(INPUTPATCHPAGE_NAME, PatchMessages.InputPatchPage_title, null); + fPatchWizard= pw; + setMessage(PatchMessages.InputPatchPage_message); + } + + /* + * Get a path from the supplied text widget. + * @return org.eclipse.core.runtime.IPath + */ + protected IPath getPathFromText(Text textField) { + return (new Path(textField.getText())).makeAbsolute(); + } + + /* package */ String getPatchName() { + if (getInputMethod() == CLIPBOARD) + return PatchMessages.InputPatchPage_Clipboard; + return getPatchFilePath(); + } + + public void createControl(Composite parent) { + + Composite composite= new Composite(parent, SWT.NULL); + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); + setControl(composite); + + initializeDialogUnits(parent); + + buildPatchFileGroup(composite); + + // by default, whatever was used last was selected or + // default to File if nothing has been selected + restoreWidgetValues(); + + // see if there are any better options presently selected (i.e workspace + // or clipboard or URL from clipboard) + adjustToCurrentTarget(); + + // No error for dialog opening + fShowError= false; + clearErrorMessage(); + updateWidgetEnablements(); + + Shell shell= getShell(); + shell.addShellListener(fActivationListener); + + Dialog.applyDialogFont(composite); + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.PATCH_INPUT_WIZARD_PAGE); + } + + /** + * Returns the next page depending on what type of patch is being applied: + * i) If the patch is a Workspace patch then it will proceed right to the PreviewPatchPage + * ii) If the patch is a single project patch then it will proceed to the PatchTargetPage, which + * allows the user to specify where to root the patch + * @return PreviewPatchPage if multi-project patch, PatchTargetPage if single project patch + */ + public IWizardPage getNextPage() { + + WorkspacePatcher patcher= ((PatchWizard) getWizard()).getPatcher(); + + // Read in the patch + readInPatch(); + + FilePatch2[] diffs= patcher.getDiffs(); + if (diffs == null || diffs.length == 0) { + String format= PatchMessages.InputPatchPage_NoDiffsFound_format; + String message= MessageFormat.format(format, new String[] { fPatchSource }); + MessageDialog.openInformation(null, + PatchMessages.InputPatchPage_PatchErrorDialog_title, message); + return this; + } + + // guess prefix count + int guess= 0; // guessPrefix(diffs); + patcher.setStripPrefixSegments(guess); + + // If this is a workspace patch we don't need to set a target as the targets will be figured out from + // all of the projects that make up the patch and continue on to final preview page + // else go on to target selection page + if (patcher.isWorkspacePatch()) { + // skip 'Patch Target' page + IWizardPage page = super.getNextPage(); + if (page.getName().equals(PatchTargetPage.PATCHTARGETPAGE_NAME)) + return page.getNextPage(); + } + + return super.getNextPage(); + } + + /* + * Reads in the patch contents + */ + public void readInPatch(){ + WorkspacePatcher patcher= ((PatchWizard) getWizard()).getPatcher(); + // Create a reader for the input + Reader reader= null; + try { + int inputMethod= getInputMethod(); + if (inputMethod == CLIPBOARD) { + Control c= getControl(); + if (c != null) { + Clipboard clipboard= new Clipboard(c.getDisplay()); + Object o= clipboard.getContents(TextTransfer.getInstance()); + clipboard.dispose(); + if (o instanceof String) + reader= new StringReader((String)o); + } + fPatchSource= PatchMessages.InputPatchPage_Clipboard_title; + } else if (inputMethod==FILE) { + String patchFilePath= getPatchFilePath(); + if (patchFilePath != null) { + try { + reader= new FileReader(patchFilePath); + } catch (FileNotFoundException ex) { + MessageDialog.openError(null, + PatchMessages.InputPatchPage_PatchErrorDialog_title, + PatchMessages.InputPatchPage_PatchFileNotFound_message); + } + } + fPatchSource= PatchMessages.InputPatchPage_PatchFile_title; + } else if (inputMethod==URL) { + String patchFileURL = fPatchURLField.getText(); + if (patchFileURL != null) { + try { + String contents = Utilities.getURLContents(new URL( + patchFileURL), getContainer()); + if (contents != null) + reader = new StringReader(contents); + } catch (MalformedURLException e) { + // ignore as we tested it with modify listener on combo + } catch (InvocationTargetException e) { // ignore + } catch (OperationCanceledException e) { // ignore + } catch (InterruptedException e) { // ignore + } + } + fPatchSource= PatchMessages.InputPatchPage_URL_title; + } else if (inputMethod==WORKSPACE) { + // Get the selected patch file (tree will only allow for one selection) + IResource[] resources= Utilities.getResources(fTreeViewer.getSelection()); + IResource patchFile= resources[0]; + if (patchFile != null) { + try { + reader= new FileReader(patchFile.getLocation().toFile()); + } catch (FileNotFoundException ex) { + MessageDialog.openError(null, PatchMessages.InputPatchPage_PatchErrorDialog_title, PatchMessages.InputPatchPage_PatchFileNotFound_message); + } catch (NullPointerException nex) { + //in case the path doesn't exist (eg. getLocation() returned null) + MessageDialog.openError(null, PatchMessages.InputPatchPage_PatchErrorDialog_title, PatchMessages.InputPatchPage_PatchFileNotFound_message); + } + } + fPatchSource= PatchMessages.InputPatchPage_WorkspacePatch_title; + } + + // parse the input + if (reader != null) { + try { + patcher.parse(new BufferedReader(reader)); + //report back to the patch wizard that the patch has been read in + fPatchWizard.patchReadIn(); + fPatchRead=true; + } catch (Exception ex) { + // Ignore. User will be informed of error since patcher contains no diffs + } + } + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException x) { + // silently ignored + } + } + } + } + + /* (non-JavaDoc) + * Method declared in IWizardPage. + */ + public boolean canFlipToNextPage() { + // we can't call getNextPage to determine if flipping is allowed since computing + // the next page is quite expensive. So we say yes if the page is complete. + return isPageComplete(); + } + + private void setEnablePatchFile(boolean enable) { + fPatchFileNameField.setEnabled(enable); + fPatchFileBrowseButton.setEnabled(enable); + } + + private void setEnableWorkspacePatch(boolean enable) { + fWorkspaceSelectLabel.setEnabled(enable); + fTreeViewer.getTree().setEnabled(enable); + } + + private void setEnableURLPatch(boolean enable) { + fPatchURLField.setEnabled(enable); + } + + /* + * Create the group for selecting the patch file + */ + private void buildPatchFileGroup(Composite parent) { + + final Composite composite= new Composite(parent, SWT.NULL); + GridLayout gridLayout= new GridLayout(); + gridLayout.numColumns= 3; + composite.setLayout(gridLayout); + composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + // 1st row + GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + gd.horizontalSpan= 3; + fUseClipboardButton= new Button(composite, SWT.RADIO); + fUseClipboardButton.setText(PatchMessages.InputPatchPage_UseClipboardButton_text); + fUseClipboardButton.setLayoutData(gd); + + // 2nd row + fUsePatchFileButton= new Button(composite, SWT.RADIO); + fUsePatchFileButton.setText(PatchMessages.InputPatchPage_FileButton_text); + + fPatchFileNameField= new Combo(composite, SWT.BORDER); + gd= new GridData(GridData.FILL_HORIZONTAL); + gd.widthHint= SIZING_TEXT_FIELD_WIDTH; + fPatchFileNameField.setLayoutData(gd); + + fPatchFileBrowseButton= new Button(composite, SWT.PUSH); + fPatchFileBrowseButton.setText(PatchMessages.InputPatchPage_ChooseFileButton_text); + GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL); + int widthHint= convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); + Point minSize= fPatchFileBrowseButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + data.widthHint= Math.max(widthHint, minSize.x); + fPatchFileBrowseButton.setLayoutData(data); + + //3rd row + fUseURLButton = new Button(composite, SWT.RADIO); + fUseURLButton.setText(PatchMessages.InputPatchPage_URLButton_text); + + fPatchURLField = new Combo(composite, SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + fPatchURLField.setLayoutData(gd); + + //4th row + fUseWorkspaceButton= new Button(composite, SWT.RADIO); + fUseWorkspaceButton.setText(PatchMessages.InputPatchPage_UseWorkspaceButton_text); + gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + fUseWorkspaceButton.setLayoutData(gd); + + addWorkspaceControls(parent); + + // Add listeners + fUseClipboardButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (!fUseClipboardButton.getSelection()) + return; + + clearErrorMessage(); + fShowError= true; + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + fPatchRead = false; + } + }); + + fUsePatchFileButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (!fUsePatchFileButton.getSelection()) + return; + //If there is anything typed in at all + clearErrorMessage(); + fShowError= (fPatchFileNameField.getText() != ""); //$NON-NLS-1$ + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + fPatchRead = false; + } + }); + fPatchFileNameField.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + updateWidgetEnablements(); + } + }); + fPatchFileNameField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + clearErrorMessage(); + fShowError= true; + updateWidgetEnablements(); + } + }); + fPatchFileBrowseButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + clearErrorMessage(); + fShowError= true; + handlePatchFileBrowseButtonPressed(); + updateWidgetEnablements(); + } + }); + fUseURLButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + clearErrorMessage(); + fShowError= (fPatchURLField.getText() != ""); //$NON-NLS-1$ + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + } + }); + fPatchURLField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + clearErrorMessage(); + fShowError = true; + updateWidgetEnablements(); + } + }); + fUseWorkspaceButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (!fUseWorkspaceButton.getSelection()) + return; + clearErrorMessage(); + // If there is anything typed in at all + fShowError= (!fTreeViewer.getSelection().isEmpty()); + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + fPatchRead = false; + } + }); + + fTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + clearErrorMessage(); + updateWidgetEnablements(); + } + }); + + fTreeViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ISelection selection= event.getSelection(); + if (selection instanceof TreeSelection) { + TreeSelection treeSel= (TreeSelection) selection; + Object res= treeSel.getFirstElement(); + if (res != null) { + if (res instanceof IProject || res instanceof IFolder) { + if (fTreeViewer.getExpandedState(res)) + fTreeViewer.collapseToLevel(res, 1); + else + fTreeViewer.expandToLevel(res, 1); + } else if (res instanceof IFile) + fPatchWizard.showPage(getNextPage()); + } + } + } + }); + } + + private void addWorkspaceControls(Composite composite) { + + Composite newComp= new Composite(composite, SWT.NONE); + GridLayout layout= new GridLayout(1, false); + layout.marginLeft= 16; // align w/ lable of check button + newComp.setLayout(layout); + newComp.setLayoutData(new GridData(GridData.FILL_BOTH)); + + fWorkspaceSelectLabel= new Label(newComp, SWT.LEFT); + fWorkspaceSelectLabel.setText(PatchMessages.InputPatchPage_WorkspaceSelectPatch_text); + + fTreeViewer= new TreeViewer(newComp, SWT.BORDER); + fTreeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + fTreeViewer.setLabelProvider(new WorkbenchLabelProvider()); + fTreeViewer.setContentProvider(new WorkbenchContentProvider()); + fTreeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME)); + fTreeViewer.setInput(ResourcesPlugin.getWorkspace().getRoot()); + } + + + /** + * Updates the enable state of this page's controls. + */ + private void updateWidgetEnablements() { + + String error= null; + + boolean gotPatch= false; + int inputMethod= getInputMethod(); + if (inputMethod==CLIPBOARD) { + Control c= getControl(); + if (c != null) { + Clipboard clipboard= new Clipboard(c.getDisplay()); + Object o= clipboard.getContents(TextTransfer.getInstance()); + clipboard.dispose(); + if (o instanceof String) { + String s= ((String) o).trim(); + if (s.length() > 0) + gotPatch= true; + else + error= PatchMessages.InputPatchPage_ClipboardIsEmpty_message; + } else + error= PatchMessages.InputPatchPage_NoTextInClipboard_message; + } else + error= PatchMessages.InputPatchPage_CouldNotReadClipboard_message; + } else if (inputMethod==FILE) { + String path= fPatchFileNameField.getText(); + if (path != null && path.length() > 0) { + File file= new File(path); + gotPatch= file.exists() && file.isFile() && file.length() > 0; + if (!gotPatch) + error= PatchMessages.InputPatchPage_CannotLocatePatch_message + path; + } else { + error= PatchMessages.InputPatchPage_NoFileName_message; + } + } else if (inputMethod == URL) { + String urlText = fPatchURLField.getText(); + if(urlText != null) { + try { + new URL(urlText); + // Checking the URL is a bit too heavy for each keystroke. + // Let's assume it contains a valid patch. + gotPatch = true; + } catch (MalformedURLException e) { + error= PatchMessages.InputPatchPage_MalformedURL; + } + } else { + error= PatchMessages.InputPatchPage_NoURL; + } + } else if (inputMethod == WORKSPACE) { + //Get the selected patch file (tree will only allow for one selection) + IResource[] resources= Utilities.getResources(fTreeViewer.getSelection()); + if (resources != null && resources.length > 0) { + IResource patchFile= resources[0]; + if (patchFile != null && patchFile.getType() == IResource.FILE) { + IPath location = patchFile.getLocation(); + if (location == null) { + error = PatchMessages.InputPatchPage_PatchFileNotFound_message; + } else { + File actualFile= location.toFile(); + gotPatch= actualFile.exists()&&actualFile.isFile()&&actualFile.length() > 0; + if (!gotPatch) + error= PatchMessages.InputPatchPage_FileSelectedNotPatch_message; + } + } + } else { + error= PatchMessages.InputPatchPage_NoFileName_message; + } + } + + setPageComplete(gotPatch); + + if (fShowError) + setErrorMessage(error); + } + + protected void handlePatchFileBrowseButtonPressed() { + FileDialog dialog= new FileDialog(getShell(), SWT.NONE); + dialog.setText(PatchMessages.InputPatchPage_SelectPatchFileDialog_title); + String patchFilePath= getPatchFilePath(); + if (patchFilePath != null) { + int lastSegment= patchFilePath.lastIndexOf(SEPARATOR); + if (lastSegment > 0) { + patchFilePath= patchFilePath.substring(0, lastSegment); + } + } + dialog.setFilterPath(patchFilePath); + String res= dialog.open(); + if (res == null) + return; + + patchFilePath= dialog.getFileName(); + IPath filterPath= new Path(dialog.getFilterPath()); + IPath path= filterPath.append(patchFilePath).makeAbsolute(); + patchFilePath= path.toOSString(); + //fDialogSettings.put(IUIConstants.DIALOGSTORE_LASTEXTJAR, filterPath.toOSString()); + + fPatchFileNameField.setText(patchFilePath); + //setSourceName(patchFilePath); + } + + /** + * Sets the source name of the import to be the supplied path. + * Adds the name of the path to the list of items in the + * source combo and selects it. + * + * @param path the path to be added + */ + protected void setSourceName(String path) { + + if (path.length() > 0) { + + String[] currentItems= fPatchFileNameField.getItems(); + int selectionIndex= -1; + for (int i= 0; i < currentItems.length; i++) + if (currentItems[i].equals(path)) + selectionIndex= i; + + if (selectionIndex < 0) { // not found in history + int oldLength= currentItems.length; + String[] newItems= new String[oldLength + 1]; + System.arraycopy(currentItems, 0, newItems, 0, oldLength); + newItems[oldLength]= path; + fPatchFileNameField.setItems(newItems); + selectionIndex= oldLength; + } + fPatchFileNameField.select(selectionIndex); + + //resetSelection(); + } + } + + /** + * The Finish button was pressed. Try to do the required work now and answer + * a boolean indicating success. If false is returned then the wizard will + * not close. + * + * @return boolean + */ + public boolean finish() { +// if (!ensureSourceIsValid()) +// return false; + + saveWidgetValues(); + +// Iterator resourcesEnum= getSelectedResources().iterator(); +// List fileSystemObjects= new ArrayList(); +// while (resourcesEnum.hasNext()) { +// fileSystemObjects.add( +// ((FileSystemElement) resourcesEnum.next()).getFileSystemObject()); +// } +// +// if (fileSystemObjects.size() > 0) +// return importResources(fileSystemObjects); +// +// MessageDialog +// .openInformation( +// getContainer().getShell(), +// DataTransferMessages.getString("DataTransfer.information"), //$NON-NLS-1$ +// DataTransferMessages.getString("FileImport.noneSelected")); //$NON-NLS-1$ +// +// return false; + + return true; + } + + /** + * Use the dialog store to restore widget values to the values that they held + * last time this wizard was used to completion + */ + private void restoreWidgetValues() { + + int inputMethod= FILE; + + IDialogSettings settings= getDialogSettings(); + if (settings != null) { + + try { + inputMethod= settings.getInt(STORE_INPUT_METHOD_ID); + } catch (NumberFormatException ex) { + //OK - no value stored in settings; just use CLIPBOARD + } + + // set filenames history + String[] sourceNames= settings.getArray(STORE_PATCH_FILES_ID); + if (sourceNames != null) + for (int i= 0; i < sourceNames.length; i++) + if (sourceNames[i] != null && sourceNames[i].length() > 0) + fPatchFileNameField.add(sourceNames[i]); + + // set patch file path + String patchFilePath= settings.get(STORE_PATCH_FILES_ID); + if (patchFilePath != null) + setSourceName(patchFilePath); + + // set URLs history + String[] sourceURLs= settings.getArray(STORE_PATCH_URLS_ID); + if (sourceURLs != null) + for (int i= 0; i < sourceURLs.length; i++) + if (sourceURLs[i] != null && sourceURLs[i].length() > 0) + fPatchURLField.add(sourceURLs[i]); + + // If the previous apply patch was used with a clipboard, we need to check + // if there is a valid patch on the clipboard. This will be done in adjustToCurrentTarget() + // so just set it to FILE now and, if there exists a patch on the clipboard, then clipboard + // will be selected automatically + if (inputMethod == CLIPBOARD){ + inputMethod= FILE; + fPatchFileNameField.deselectAll(); + } + + //set the workspace patch selection + String workspaceSetting= settings.get(STORE_WORKSPACE_PATH_ID); + if (workspaceSetting != null && workspaceSetting.length() > 0) { + // See if this resource still exists in the workspace + try { + IPath path= new Path(workspaceSetting); + IFile targetFile= ResourcesPlugin.getWorkspace().getRoot().getFile(path); + if (fTreeViewer != null && targetFile.exists()){ + fTreeViewer.expandToLevel(targetFile, 0); + fTreeViewer.setSelection(new StructuredSelection(targetFile)); + } + } catch (RuntimeException e) { + // Ignore. The setting was invalid + } + } else { + //check to see if the current input is set to workspace - if it is switch it + //back to clipboard since there is no corresponding element to go along with + //the tree viewer + if (inputMethod == WORKSPACE) + inputMethod= FILE; + } + } + + // set radio buttons state + setInputButtonState(inputMethod); + } + + /** + * Since Finish was pressed, write widget values to the dialog store so that they + * will persist into the next invocation of this wizard page + */ + void saveWidgetValues() { + IDialogSettings settings= getDialogSettings(); + if (settings != null) { + + settings.put(STORE_INPUT_METHOD_ID, getInputMethod()); + settings.put(STORE_PATCH_FILES_ID, getPatchFilePath()); + + // update source names history + String[] sourceNames= settings.getArray(STORE_PATCH_FILES_ID); + if (sourceNames == null) + sourceNames= new String[0]; + + sourceNames= addToHistory(sourceNames, getPatchFilePath()); + settings.put(STORE_PATCH_FILES_ID, sourceNames); + + // update source URLs history + String[] sourceURLs= settings.getArray(STORE_PATCH_URLS_ID); + if (sourceURLs == null) + sourceURLs= new String[0]; + + sourceURLs= addToHistory(sourceURLs, fPatchURLField.getText()); + settings.put(STORE_PATCH_URLS_ID, sourceURLs); + + // save the workspace selection + settings.put(STORE_WORKSPACE_PATH_ID, getWorkspacePath()); + + } + } + + private String getWorkspacePath() { + if (fTreeViewer != null){ + IResource[] resources= Utilities.getResources(fTreeViewer.getSelection()); + if (resources.length > 0) { + IResource patchFile= resources[0]; + return patchFile.getFullPath().toString(); + } + + } + return ""; //$NON-NLS-1$ + } + + // static helpers + + /** + * Checks to see if the file that has been selected for Apply Patch is + * actually a patch + * + * @return true if the file selected to run Apply Patch on in the workspace + * is a patch file or if the clipboard contains a patch or if the + * clipboard contains an URL (we assume it points to a patch ) + */ + private boolean adjustToCurrentTarget() { + // readjust selection if there is a patch selected in the workspace or on the clipboard + // check workspace first + IResource patchTarget= fPatchWizard.getTarget(); + if (patchTarget instanceof IFile) { + Reader reader= null; + try { + try { + reader= new FileReader(patchTarget.getLocation().toFile()); + if (isPatchFile(reader)) { + // set choice to workspace + setInputButtonState(WORKSPACE); + if (fTreeViewer != null && patchTarget.exists()) { + fTreeViewer.expandToLevel(patchTarget, 0); + fTreeViewer.setSelection(new StructuredSelection(patchTarget)); + } + return true; + } + } catch (FileNotFoundException ex) { + // silently ignored + } catch (NullPointerException nex) { + // silently ignored + } + + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException x) { + // silently ignored + } + } + } + } + // check out clipboard contents + Reader reader = null; + Control c = getControl(); + if (c != null) { + Clipboard clipboard= new Clipboard(c.getDisplay()); + Object o= clipboard.getContents(TextTransfer.getInstance()); + clipboard.dispose(); + try { + if (o instanceof String) { + reader= new StringReader((String) o); + if (isPatchFile(reader)) { + setInputButtonState(CLIPBOARD); + return true; + } + // maybe it's an URL + try { + URL url = new URL((String)o); + if(url != null) { + setInputButtonState(URL); + fPatchURLField.setText((String)o); + return true; + } + } catch (MalformedURLException e) { + // ignore + } + } + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException x) { + // silently ignored + } + } + } + } + return false; + } + + private boolean isPatchFile(Reader reader) { + WorkspacePatcher patcher= ((PatchWizard) getWizard()).getPatcher(); + + try { + patcher.parse(new BufferedReader(reader)); + } catch (Exception ex) { + return false; + } + + FilePatch2[] diffs= patcher.getDiffs(); + if (diffs == null || diffs.length == 0) + return false; + return true; + } + + /* + * Clears the dialog message box + */ + private void clearErrorMessage(){ + setErrorMessage(null); + } + + private void setInputButtonState(int state) { + + switch (state) { + case CLIPBOARD: + fUseClipboardButton.setSelection(true); + fUsePatchFileButton.setSelection(false); + fUseURLButton.setSelection(false); + fUseWorkspaceButton.setSelection(false); + break; + + case FILE: + fUseClipboardButton.setSelection(false); + fUsePatchFileButton.setSelection(true); + fUseURLButton.setSelection(false); + fUseWorkspaceButton.setSelection(false); + break; + + case URL: + fUseClipboardButton.setSelection(false); + fUsePatchFileButton.setSelection(false); + fUseURLButton.setSelection(true); + fUseWorkspaceButton.setSelection(false); + break; + + case WORKSPACE: + fUseClipboardButton.setSelection(false); + fUsePatchFileButton.setSelection(false); + fUseURLButton.setSelection(false); + fUseWorkspaceButton.setSelection(true); + break; + } + + setEnablePatchFile(state == FILE); + setEnableWorkspacePatch(state == WORKSPACE); + setEnableURLPatch(state == URL); + } + + protected int getInputMethod() { + if (fUseClipboardButton.getSelection()) + return CLIPBOARD; + if (fUsePatchFileButton.getSelection()) + return FILE; + if(fUseURLButton.getSelection()) + return URL; + return WORKSPACE; + } + + private String getPatchFilePath() { + if (fPatchFileNameField != null) + return fPatchFileNameField.getText(); + return ""; //$NON-NLS-1$ + } + + /* + * Adds an entry to a history, while taking care of duplicate history items + * and excessively long histories. The assumption is made that all histories + * should be of length <code>COMBO_HISTORY_LENGTH</code>. + * + * @param history the current history + * @param newEntry the entry to add to the history + */ + protected static String[] addToHistory(String[] history, String newEntry) { + java.util.ArrayList l= new java.util.ArrayList(java.util.Arrays.asList(history)); + + l.remove(newEntry); + l.add(0,newEntry); + + // since only one new item was added, we can be over the limit + // by at most one item + if (l.size() > COMBO_HISTORY_LENGTH) + l.remove(COMBO_HISTORY_LENGTH); + + return (String[]) l.toArray(new String[l.size()]); + } + + public boolean isPatchRead() { + return fPatchRead; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java new file mode 100644 index 000000000..735b1c3d3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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 + * Brock Janiczak <brockj@tpg.com.au> - Bug 181919 LineReader creating unneeded garbage + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.Platform; + +public class LineReader { + + /* + * Reads the contents from the given file and returns them as a List of + * lines. + */ + public static List load(IStorage file, boolean create) { + List lines = null; + if (!create && file != null && exists(file)) { + // read current contents + String charset = Utilities.getCharset(file); + InputStream is = null; + try { + is = file.getContents(); + + Reader streamReader = null; + try { + streamReader = new InputStreamReader(is, charset); + } catch (UnsupportedEncodingException x) { + // use default encoding + streamReader = new InputStreamReader(is); + } + + BufferedReader reader = new BufferedReader(streamReader); + lines = readLines(reader); + } catch (CoreException ex) { + // TODO + CompareUIPlugin.log(ex); + } finally { + if (is != null) + try { + is.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + + if (lines == null) + lines = new ArrayList(); + return lines; + } + + private static boolean exists(IStorage file) { + if (file instanceof IFile) { + return ((IFile) file).exists(); + } + return true; + } + + public static List readLines(BufferedReader reader) { + List lines; + LineReader lr = new LineReader(reader); + if (!Platform.WS_CARBON.equals(Platform.getWS())) + // Don't treat single CRs as line feeds to be consistent with command line patch + lr.ignoreSingleCR(); + lines = lr.readLines(); + return lines; + } + + /* + * Concatenates all strings found in the given List. + */ + public static String createString(boolean preserveLineDelimeters, List lines) { + StringBuffer sb = new StringBuffer(); + Iterator iter = lines.iterator(); + if (preserveLineDelimeters) { + while (iter.hasNext()) + sb.append((String) iter.next()); + } else { + String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ + while (iter.hasNext()) { + String line = (String) iter.next(); + int l = length(line); + if (l < line.length()) { // line has delimiter + sb.append(line.substring(0, l)); + sb.append(lineSeparator); + } else { + sb.append(line); + } + } + } + return sb.toString(); + } + + /* + * Returns the length (excluding a line delimiter CR, LF, CR/LF) of the + * given string. + */ + /* package */static int length(String s) { + int l = s.length(); + if (l > 0) { + char c = s.charAt(l - 1); + if (c == '\r') + return l - 1; + if (c == '\n') { + if (l > 1 && s.charAt(l - 2) == '\r') + return l - 2; + return l - 1; + } + } + return l; + } + + private boolean fHaveChar = false; + private int fLastChar; + private boolean fSawEOF = false; + private BufferedReader fReader; + private boolean fIgnoreSingleCR = false; + private StringBuffer fBuffer = new StringBuffer(); + + public LineReader(BufferedReader reader) { + fReader = reader; + Assert.isNotNull(reader); + } + + public void ignoreSingleCR() { + fIgnoreSingleCR = true; + } + + /** + * Reads a line of text. A line is considered to be terminated by any one of + * a line feed ('\n'), a carriage return ('\r'), or a carriage return + * followed immediately by a line-feed. + * + * @return A string containing the contents of the line including the + * line-termination characters, or <code>null</code> if the end of + * the stream has been reached + * @exception IOException + * If an I/O error occurs + */ + /* package */String readLine() throws IOException { + try { + while (!fSawEOF) { + int c = readChar(); + if (c == -1) { + fSawEOF = true; + break; + } + fBuffer.append((char) c); + if (c == '\n') + break; + if (c == '\r') { + c = readChar(); + if (c == -1) { + fSawEOF = true; + break; // EOF + } + if (c != '\n') { + if (fIgnoreSingleCR) { + fBuffer.append((char) c); + continue; + } + fHaveChar = true; + fLastChar = c; + } else + fBuffer.append((char) c); + break; + } + } + + if (fBuffer.length() != 0) { + return fBuffer.toString(); + } + return null; + } finally { + fBuffer.setLength(0); + } + } + + /* package */void close() { + try { + fReader.close(); + } catch (IOException ex) { + // silently ignored + } + } + + public List readLines() { + try { + List lines = new ArrayList(); + String line; + while ((line = readLine()) != null) + lines.add(line); + return lines; + } catch (IOException ex) { + // NeedWork + // System.out.println("error while reading file: " + fileName + "(" + // + ex + ")"); + } finally { + close(); + } + return null; + } + + /* + * Returns the number of characters in the given string without counting a + * trailing line separator. + */ + /* package */int lineContentLength(String line) { + if (line == null) + return 0; + int length = line.length(); + for (int i = length - 1; i >= 0; i--) { + char c = line.charAt(i); + if (c == '\n' || c == '\r') + length--; + else + break; + } + return length; + } + + // ---- private + + private int readChar() throws IOException { + if (fHaveChar) { + fHaveChar = false; + return fLastChar; + } + return fReader.read(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java new file mode 100644 index 000000000..c8bc3da77 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java @@ -0,0 +1,433 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.internal.patch; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.CompareViewerPane; +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DiffTreeViewer; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IOpenListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.OpenEvent; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; + +public abstract class PatchCompareEditorInput extends CompareEditorInput { + + private static final String IMAGE_CACHE_KEY = "IMAGE_CACHE"; //$NON-NLS-1$ + + public static ImageDescriptor createOverlay(Image baseImage, ImageDescriptor overlayImage, int quadrant) { + return new DecoratorOverlayIcon(baseImage, createArrayFrom(overlayImage, quadrant), new Point(Math.max(baseImage.getBounds().width, 16), Math.max(baseImage.getBounds().height, 16))); + } + + private static ImageDescriptor[] createArrayFrom( + ImageDescriptor overlayImage, int quadrant) { + ImageDescriptor[] descs = new ImageDescriptor[] { null, null, null, null, null }; + descs[quadrant] = overlayImage; + return descs; + } + + class PatcherCompareEditorLabelProvider extends LabelProvider { + private ILabelProvider wrappedProvider; + + public PatcherCompareEditorLabelProvider(ILabelProvider labelProvider) { + wrappedProvider = labelProvider; + } + + public String getText(Object element) { + String text = wrappedProvider.getText(element); + if (element instanceof PatchDiffNode){ + PatchDiffNode node = (PatchDiffNode) element; + if (node instanceof PatchProjectDiffNode) { + PatchProjectDiffNode projectNode = (PatchProjectDiffNode) node; + if (!Utilities.getProject(projectNode.getDiffProject()).exists()) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[]{text, PatchMessages.PreviewPatchLabelDecorator_ProjectDoesNotExist}); + } + } + if (!node.isEnabled()) { + return NLS.bind(PatchMessages.Diff_2Args, + new String[]{text, PatchMessages.PatcherCompareEditorInput_NotIncluded}); + } + if (node instanceof PatchFileDiffNode) { + PatchFileDiffNode fileNode = (PatchFileDiffNode) node; + if (getPatcher().hasCachedContents(fileNode.getDiffResult().getDiff())) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[] {text, PatchMessages.HunkMergePage_Merged}); + } + if (!fileNode.fileExists()) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[] {text, PatchMessages.PatchCompareEditorInput_0}); + } + } + if (node instanceof HunkDiffNode) { + HunkDiffNode hunkNode = (HunkDiffNode) node; + if (hunkNode.isManuallyMerged()) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[] {text, PatchMessages.HunkMergePage_Merged}); + } + if (hunkNode.isFuzzUsed()) { + text = NLS.bind(PatchMessages.Diff_2Args, + new String[] { text, + NLS.bind(hunkNode.isAllContextIgnored() ? PatchMessages.PreviewPatchPage_AllContextIgnored : PatchMessages.PreviewPatchPage_FuzzUsed, + new String[] { hunkNode.getHunkResult().getFuzz() + ""}) }); //$NON-NLS-1$ + } + } + if (getPatcher().isRetargeted(node.getPatchElement())) + return NLS.bind(PatchMessages.Diff_2Args, + new String[]{getPatcher().getOriginalPath(node.getPatchElement()).toString(), + NLS.bind(PatchMessages.PreviewPatchPage_Target, new String[]{node.getName()})}); + } + return text; + } + + public Image getImage(Object element) { + Image image = wrappedProvider.getImage(element); + if (element instanceof PatchDiffNode){ + PatchDiffNode node = (PatchDiffNode) element; + if (!node.isEnabled() && image != null) { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(getPatcher().getConfiguration()); + return imageCache.createImage(createOverlay(image, CompareUIPlugin.getImageDescriptor(ICompareUIConstants.REMOVED_OVERLAY), IDecoration.TOP_LEFT)); + } + } + if (element instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) element; + if (node.isManuallyMerged()) { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(getPatcher().getConfiguration()); + return imageCache.createImage(PatchCompareEditorInput.createOverlay(image, CompareUIPlugin.getImageDescriptor(ICompareUIConstants.IS_MERGED_OVERLAY), IDecoration.TOP_LEFT)); + } + } + return image; + } + } + + private final DiffNode root; + private final WorkspacePatcher patcher; + private TreeViewer viewer; + private boolean fShowAll; + private boolean showMatched = false; + + /** + * Creates a new PatchCompareEditorInput and makes use of the passed in CompareConfiguration + * to configure the UI elements. + * @param patcher + * @param configuration + */ + public PatchCompareEditorInput(WorkspacePatcher patcher, CompareConfiguration configuration) { + super(configuration); + Assert.isNotNull(patcher); + this.patcher = patcher; + root = new DiffNode(Differencer.NO_CHANGE) { + public boolean hasChildren() { + return true; + } + }; + initializeImageCache(); + } + + private void initializeImageCache() { + initializeImageCache(patcher.getConfiguration()); + } + + private static LocalResourceManager initializeImageCache(PatchConfiguration patchConfiguration) { + LocalResourceManager imageCache = new LocalResourceManager(JFaceResources.getResources()); + patchConfiguration.setProperty(IMAGE_CACHE_KEY, imageCache); + return imageCache; + } + + protected void handleDispose() { + super.handleDispose(); + getImageCache(getPatcher().getConfiguration()).dispose(); + } + + public static LocalResourceManager getImageCache(PatchConfiguration patchConfiguration) { + LocalResourceManager cache = (LocalResourceManager)patchConfiguration.getProperty(IMAGE_CACHE_KEY); + if (cache == null) { + return initializeImageCache(patchConfiguration); + } + return cache; + } + + protected Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + initLabels(); + return root; + } + + private void initLabels() { + CompareConfiguration cc = getCompareConfiguration(); + // set left editable so that unmatched hunks can be edited + cc.setLeftEditable(true); + cc.setRightEditable(false); + //cc.setProperty(CompareEditor.CONFIRM_SAVE_PROPERTY, new Boolean(false)); + cc.setLeftLabel(getCompareConfiguration().getLeftLabel(root)); + cc.setLeftImage(getCompareConfiguration().getLeftImage(root)); + cc.setRightLabel(getCompareConfiguration().getRightLabel(root)); + cc.setRightImage(getCompareConfiguration().getRightImage(root)); + } + + /** + * Update the presentation of the diff tree. + */ + protected void updateTree() { + if (getViewer() != null && !getViewer().getControl().isDisposed()) + getViewer().refresh(true); + } + + /** + * Build the diff tree. + */ + protected void buildTree(){ + + // Reset the input node so it is empty + if (getRoot().hasChildren()) { + resetRoot(); + } + // Reset the input of the viewer so the old state is no longer used + getViewer().setInput(getRoot()); + + // Refresh the patcher state + getPatcher().refresh(); + + // Build the diff tree + if (getPatcher().isWorkspacePatch()){ + processProjects(getPatcher().getDiffProjects()); + } else { + processDiffs(getPatcher().getDiffs()); + } + + // Refresh the viewer + getViewer().refresh(); + } + + private void processDiffs(FilePatch2[] diffs) { + for (int i = 0; i < diffs.length; i++) { + processDiff(diffs[i], getRoot()); + } + } + + private void processProjects(DiffProject[] diffProjects) { + //create diffProject nodes + for (int i = 0; i < diffProjects.length; i++) { + PatchProjectDiffNode projectNode = new PatchProjectDiffNode(getRoot(), diffProjects[i], getPatcher().getConfiguration()); + FilePatch2[] diffs = diffProjects[i].getFileDiffs(); + for (int j = 0; j < diffs.length; j++) { + FilePatch2 fileDiff = diffs[j]; + processDiff(fileDiff, projectNode); + } + } + } + + private void processDiff(FilePatch2 diff, DiffNode parent) { + FileDiffResult diffResult = getPatcher().getDiffResult(diff); + PatchFileDiffNode node = PatchFileDiffNode.createDiffNode(parent, diffResult); + HunkResult[] hunkResults = diffResult.getHunkResults(); + for (int i = 0; i < hunkResults.length; i++) { + HunkResult hunkResult = hunkResults[i]; + if (!hunkResult.isOK()) { + HunkDiffNode hunkNode = HunkDiffNode.createDiffNode(node, hunkResult, true); + Object left = hunkNode.getLeft(); + if (left instanceof UnmatchedHunkTypedElement) { + UnmatchedHunkTypedElement element = (UnmatchedHunkTypedElement) left; + element.addContentChangeListener(new IContentChangeListener() { + public void contentChanged(IContentChangeNotifier source) { + if (getViewer() == null || getViewer().getControl().isDisposed()) + return; + getViewer().refresh(true); + } + }); + } + } else if (showMatched) { + HunkDiffNode.createDiffNode(node, hunkResult, false, true, false); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.CompareEditorInput#createDiffViewer(org.eclipse.swt.widgets.Composite) + */ + public Viewer createDiffViewer(Composite parent) { + viewer = new DiffTreeViewer(parent, getCompareConfiguration()){ + protected void fillContextMenu(IMenuManager manager) { + PatchCompareEditorInput.this.fillContextMenu(manager); + } + }; + + viewer.setLabelProvider(new PatcherCompareEditorLabelProvider((ILabelProvider)viewer.getLabelProvider())); + viewer.getTree().setData(CompareUI.COMPARE_VIEWER_TITLE, PatchMessages.PatcherCompareEditorInput_PatchContents); + viewer.addOpenListener(new IOpenListener() { + public void open(OpenEvent event) { + IStructuredSelection sel= (IStructuredSelection) event.getSelection(); + Object obj= sel.getFirstElement(); + if (obj instanceof HunkDiffNode) { + if (((HunkDiffNode) obj).getHunkResult().isOK()) { + getCompareConfiguration().setLeftLabel(PatchMessages.PatcherCompareEditorInput_LocalCopy); + getCompareConfiguration().setRightLabel(PatchMessages.PreviewPatchPage2_MatchedHunk); + } else { + getCompareConfiguration().setLeftLabel(PatchMessages.PreviewPatchPage2_PatchedLocalFile); + getCompareConfiguration().setRightLabel(PatchMessages.PreviewPatchPage2_OrphanedHunk); + } + } else { + getCompareConfiguration().setLeftLabel(PatchMessages.PatcherCompareEditorInput_LocalCopy); + getCompareConfiguration().setRightLabel(PatchMessages.PatcherCompareEditorInput_AfterPatch); + } + } + + }); + viewer.setFilters(getFilters()); + viewer.setInput(root); + return viewer; + } + + private ViewerFilter[] getFilters() { + return new ViewerFilter[] { new ViewerFilter() { + public boolean select(Viewer v, Object parentElement, Object element) { + if (element instanceof PatchDiffNode) { + PatchDiffNode node = (PatchDiffNode) element; + return node.isEnabled() || isShowAll(); + } + return false; + } + } }; + } + + protected boolean isShowAll() { + return fShowAll; + } + + protected void setShowAll(boolean show) { + fShowAll = show; + } + + public boolean isShowMatched() { + return showMatched; + } + + protected void setShowMatched(boolean show) { + showMatched = show; + } + + public void contributeDiffViewerToolbarItems(Action[] actions, boolean workspacePatch){ + ToolBarManager tbm= CompareViewerPane.getToolBarManager(viewer.getControl().getParent()); + if (tbm != null) { + tbm.removeAll(); + + tbm.add(new Separator("contributed")); //$NON-NLS-1$ + + for (int i = 0; i < actions.length; i++) { + tbm.appendToGroup("contributed", actions[i]); //$NON-NLS-1$ + } + + tbm.update(true); + } + } + + public TreeViewer getViewer() { + return viewer; + } + + public DiffNode getRoot() { + return root; + } + + public void resetRoot() { + IDiffElement[] children = root.getChildren(); + for (int i = 0; i < children.length; i++) { + IDiffElement child = children[i]; + root.remove(child); + } + } + + public WorkspacePatcher getPatcher() { + return patcher; + } + + public boolean confirmRebuild(String message) { + if (getPatcher().hasCachedContents()) { + if (promptToDiscardCachedChanges(message)) { + getPatcher().clearCachedContents(); + return true; + } + return false; + } + return true; + } + + private boolean promptToDiscardCachedChanges(String message) { + return MessageDialog.openConfirm(viewer.getControl().getShell(), PatchMessages.PatcherCompareEditorInput_0, message); + } + + /** + * Return whether this input has a result to apply. The input + * has a result to apply if at least one hunk is selected for inclusion. + * @return whether this input has a result to apply + */ + public boolean hasResultToApply() { + boolean atLeastOneIsEnabled = false; + if (getViewer() != null) { + IDiffElement[] elements = getRoot().getChildren(); + for (int i = 0; i < elements.length; i++) { + IDiffElement element = elements[i]; + if (isEnabled(element)) { + atLeastOneIsEnabled = true; + break; + } + } + } + return atLeastOneIsEnabled; + } + + private boolean isEnabled(IDiffElement element) { + if (element instanceof PatchDiffNode) { + PatchDiffNode node = (PatchDiffNode) element; + return node.isEnabled(); + } + return false; + } + + protected abstract void fillContextMenu(IMenuManager manager); + + public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, + Composite parent) { + if (org.eclipse.compare.internal.Utilities.isHunk(input)) + return null; + return super.findStructureViewer(oldViewer, input, parent); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java new file mode 100644 index 000000000..351af89d3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.internal.patch; + +import org.eclipse.compare.IResourceProvider; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.core.resources.IResource; + +public abstract class PatchDiffNode extends DiffNode implements IResourceProvider { + + private Object fElement; + + public PatchDiffNode(Object patchElement, IDiffContainer parent, int kind, + ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(parent, kind, ancestor, left, right); + fElement = patchElement; + } + + public PatchDiffNode(Object patchElement, IDiffContainer parent, int kind) { + super(parent, kind); + fElement = patchElement; + } + + public boolean isEnabled() { + return getPatcher().isEnabled(getPatchElement()); + } + + public void setEnabled(boolean enabled) { + getPatcher().setEnabled(getPatchElement(), enabled); + } + + protected final Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + public Object getPatchElement() { + return fElement; + } + + protected abstract PatchConfiguration getConfiguration(); + + public boolean equals(Object other) { + if (other instanceof PatchDiffNode) { + PatchDiffNode node = (PatchDiffNode) other; + return (node.getPatchElement().equals(getPatchElement())); + } + return super.equals(other); + } + + public int hashCode() { + return getPatchElement().hashCode(); + } + + public IResource getResource() { + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java new file mode 100644 index 000000000..e0a60b348 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.internal.patch; + +public class PatchErrorDialog { + + private PatchErrorDialog() { + // no instance. + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java new file mode 100644 index 000000000..949b1cd02 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.internal.patch; + +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.resources.IResource; + +public class PatchFileDiffNode extends PatchDiffNode implements IContentChangeListener { + + private final FileDiffResult result; + + public static PatchFileDiffNode createDiffNode(DiffNode parent, FileDiffResult result) { + return new PatchFileDiffNode(result, parent, getKind(result), getAncestorElement(result), getLeftElement(result), getRightElement(result)); + } + + public static int getKind(FileDiffResult result) { + if (!result.hasMatches()) + return Differencer.NO_CHANGE; + int fileDiffKind = result.getDiff().getDiffType(result.getConfiguration().isReversed()); + int kind = convertFileDiffTypeToDifferencerType(fileDiffKind); + return kind | Differencer.RIGHT; + } + + private static int convertFileDiffTypeToDifferencerType(int fileDiffKind) { + int kind; + switch (fileDiffKind) { + case FilePatch2.ADDITION: + kind = Differencer.ADDITION; + break; + case FilePatch2.DELETION: + kind = Differencer.DELETION; + break; + case FilePatch2.CHANGE: + kind = Differencer.CHANGE; + break; + default: + kind = Differencer.CHANGE; + break; + } + return kind; + } + + public static ITypedElement getRightElement(FileDiffResult result) { + return new PatchFileTypedElement(result, true); + } + + private static ITypedElement getLeftElement(FileDiffResult result) { + return new PatchFileTypedElement(result, false); + } + + public static ITypedElement getAncestorElement(FileDiffResult result) { + return new PatchFileTypedElement(result, false); + } + + public PatchFileDiffNode(FileDiffResult result, IDiffContainer parent, int kind, + ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(result.getDiff(), parent, kind, ancestor, left, right); + this.result = result; + } + + public FileDiffResult getDiffResult() { + return result; + } + + protected PatchConfiguration getConfiguration() { + return result.getConfiguration(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffContainer#add(org.eclipse.compare.structuremergeviewer.IDiffElement) + */ + public void add(IDiffElement diff) { + super.add(diff); + // Listen for content changes in unmatched children so we can fire an input change + if (diff instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) diff; + Object left = node.getLeft(); + if (left instanceof IContentChangeNotifier) { + IContentChangeNotifier notifier = (IContentChangeNotifier) left; + notifier.addContentChangeListener(this); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IContentChangeListener#contentChanged(org.eclipse.compare.IContentChangeNotifier) + */ + public void contentChanged(IContentChangeNotifier source) { + fireChange(); + } + + public int getKind() { + int kind = super.getKind(); + if (kind == Differencer.NO_CHANGE && getPatcher().hasCachedContents(getDiffResult().getDiff())) { + return Differencer.CHANGE | Differencer.RIGHT; + } + return kind; + } + + public boolean fileExists() { + IResource file = getResource(); + return file != null && file.isAccessible(); + } + + public IResource getResource() { + return ((WorkspaceFileDiffResult)getDiffResult()).getTargetFile(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java new file mode 100644 index 000000000..86c5fff2d --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.internal.patch; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.graphics.Image; + +public class PatchFileTypedElement implements ITypedElement, + IEncodedStreamContentAccessor { + + private final FileDiffResult result; + private final boolean isAfterState; + + public PatchFileTypedElement(FileDiffResult result, boolean isAfterState) { + this.result = result; + this.isAfterState = isAfterState; + } + + public Image getImage() { + IFile file = getPatcher().getTargetFile(result.getDiff()); + if (file == null) { + // We don't get a target file if the file doesn't exist + DiffProject project = result.getDiff().getProject(); + if (project != null) { + file = Utilities.getProject(project).getFile( + result.getDiff().getPath( + result.getConfiguration().isReversed())); + } else { + IResource target = getPatcher().getTarget(); + if (target instanceof IFile) { + file = (IFile) target; + } else if (target instanceof IContainer) { + IContainer container = (IContainer) target; + file = container.getFile(result.getTargetPath()); + } + } + } + Image image = null; + if (file != null) { + image = CompareUI.getImage(file); + } + if (result.containsProblems()) { + LocalResourceManager imageCache = PatchCompareEditorInput + .getImageCache(result.getConfiguration()); + image = HunkTypedElement.getHunkErrorImage(image, imageCache, true); + } + return image; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.ITypedElement#getName() + */ + public String getName() { + return result.getTargetPath().toString(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.ITypedElement#getType() + */ + public String getType() { + return result.getTargetPath().getFileExtension(); + } + + public String getCharset() throws CoreException { + return result.getCharset(); + } + + public InputStream getContents() throws CoreException { + // If there are cached contents, use them + if (isAfterState && getPatcher().hasCachedContents(result.getDiff())) + return new ByteArrayInputStream(getPatcher().getCachedContents( + result.getDiff())); + // Otherwise, get the lines from the diff result + List lines; + if (isAfterState) { + lines = result.getAfterLines(); + } else { + lines = result.getBeforeLines(); + } + String contents = LineReader.createString(getPatcher() + .isPreserveLineDelimeters(), lines); + String charSet = getCharset(); + byte[] bytes = null; + if (charSet != null) { + try { + bytes = contents.getBytes(charSet); + } catch (UnsupportedEncodingException e) { + CompareUIPlugin.log(e); + } + } + if (bytes == null) { + bytes = contents.getBytes(); + } + return new ByteArrayInputStream(bytes); + } + + private Patcher getPatcher() { + return Patcher.getPatcher(result.getConfiguration()); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java new file mode 100644 index 000000000..6fde7b42a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal.patch; + +import org.eclipse.osgi.util.NLS; + +public final class PatchMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.patch.PatchMessages";//$NON-NLS-1$ + + private PatchMessages() { + // Do not instantiate + } + + public static String HunkMergePage_GenerateRejectFile; + public static String HunkMergePage_Merged; + public static String InputPatchPage_MalformedURL; + public static String InputPatchPage_NoURL; + public static String InputPatchPage_URLButton_text; + public static String InputPatchPage_URL_title; + public static String PatchCompareEditorInput_0; + public static String PatcherCompareEditorInput_0; + public static String PatcherCompareEditorInput_AfterPatch; + public static String PatcherCompareEditorInput_LocalCopy; + public static String PatcherCompareEditorInput_NotIncluded; + public static String PatcherCompareEditorInput_PatchContents; + public static String PatchTargetPage_0; + public static String PatchWizard_0; + public static String PatchWizard_1; + public static String PatchWizard_title; + public static String PatchWizard_unexpectedException_message; + public static String InputPatchPage_title; + public static String InputPatchPage_message; + public static String InputPatchPage_Clipboard; + public static String InputPatchPage_SelectInput; + public static String InputPatchPage_PatchErrorDialog_title; + public static String InputPatchPage_FileButton_text; + public static String InputPatchPage_ChooseFileButton_text; + public static String InputPatchPage_UseClipboardButton_text; + public static String InputPatchPage_UseWorkspaceButton_text; + public static String InputPatchPage_WorkspaceSelectPatch_text; + public static String InputPatchPage_NothingSelected_message; + public static String InputPatchPage_ClipboardIsEmpty_message; + public static String InputPatchPage_NoTextInClipboard_message; + public static String InputPatchPage_CouldNotReadClipboard_message; + public static String InputPatchPage_CannotLocatePatch_message; + public static String InputPatchPage_NoFileName_message; + public static String InputPatchPage_FileSelectedNotPatch_message; + public static String InputPatchPage_SelectPatchFileDialog_title; + public static String InputPatchPage_PatchFileNotFound_message; + public static String InputPatchPage_ParseError_message; + public static String InputPatchPage_Clipboard_title; + public static String InputPatchPage_PatchFile_title; + public static String InputPatchPage_WorkspacePatch_title; + public static String InputPatchPage_NoDiffsFound_format; + public static String InputPatchPage_SingleFileError_format; + public static String InputPatchPage_URLConnecting; + public static String InputPatchPage_URLFetchingContent; + public static String PatchTargetPage_title; + public static String PatchTargetPage_message; + public static String PreviewPatchPage_title; + public static String PreviewPatchPage_PatchOptions_title; + public static String PreviewPatchPage_IgnoreSegments_text; + public static String PreviewPatchPage_ReversePatch_text; + public static String PreviewPatchPage_FuzzFactor_text; + public static String PreviewPatchPage_FuzzFactor_tooltip; + public static String PreviewPatchPage_GuessFuzz_text; + public static String PreviewPatchPage_FuzzUsed; + public static String PreviewPatchPage_AllContextIgnored; + + static { + NLS.initializeMessages(BUNDLE_NAME, PatchMessages.class); + } + + public static String Diff_2Args; + public static String PreviewPatchPage_RetargetPatch; + public static String PreviewPatchPage_SelectProject; + public static String PreviewPatchPage_Target; + public static String PreviewPatchLabelDecorator_ProjectDoesNotExist; + public static String PreviewPatchPage2_0; + public static String PreviewPatchPage2_1; + public static String PreviewPatchPage2_2; + public static String PreviewPatchPage2_3; + public static String PreviewPatchPage2_4; + public static String PreviewPatchPage2_5; + public static String PreviewPatchPage2_6; + public static String PreviewPatchPage2_7; + public static String PreviewPatchPage2_8; + public static String PreviewPatchPage2_9; + public static String PreviewPatchPage2_CalculateReverse; + public static String PreviewPatchPage2_IgnoreWhitespace; + public static String PreviewPatchPage2_IgnoreWSAction; + public static String PreviewPatchPage2_IgnoreWSTooltip; + public static String PreviewPatchPage2_OrphanedHunk; + public static String PreviewPatchPage2_MatchedHunk; + public static String PreviewPatchPage2_PatchedLocalFile; + public static String PreviewPatchPage2_RetargetAction; + public static String PreviewPatchPage2_RetargetTooltip; + public static String PreviewPatchPage2_ShowMatched; + public static String PreviewPatchPage2_AddedRemovedLines; + public static String RetargetPatchElementDialog_0; + public static String RetargetPatchElementDialog_1; +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties new file mode 100644 index 000000000..95ac4b8f2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties @@ -0,0 +1,117 @@ +############################################################################### +# Copyright (c) 2000, 2009 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 +# Sebastian Davids <sdavids@gmx.de> - layout tweaks +############################################################################### + +# +# 'Compare with Patch' Action +# +PatcherCompareEditorInput_LocalCopy=Local Copy +PatcherCompareEditorInput_0=Discard Changes? +PatcherCompareEditorInput_AfterPatch=After Patch + +# +# PatchWizard +# +PatchWizard_title=Apply Patch +PatchWizard_0=Patch has rejects +PatchWizard_1=The patch you are applying has segments that did not match. Are you sure you want to apply it? +PatchWizard_unexpectedException_message= Unexpected exception while applying the patch. See log for a detailed error description. +PatcherCompareEditorInput_PatchContents=Patch Contents +PatcherCompareEditorInput_NotIncluded=(Not included) + +# +# InputPatchPage +# +InputPatchPage_title= Patch Input Specification +InputPatchPage_NoURL=Please enter an URL +InputPatchPage_message= Select the patch location. +InputPatchPage_Clipboard=Clipboard +InputPatchPage_SelectInput=Apply the patch to the &selected file, folder or project: +InputPatchPage_MalformedURL=Malformed URL +InputPatchPage_PatchErrorDialog_title=Patch Error +InputPatchPage_FileButton_text=Fil&e +InputPatchPage_URLButton_text=&URL +InputPatchPage_ChooseFileButton_text=&Browse... +InputPatchPage_UseClipboardButton_text=&Clipboard +InputPatchPage_UseWorkspaceButton_text=&Workspace +InputPatchPage_WorkspaceSelectPatch_text=&Select the location of the patch: +InputPatchPage_NothingSelected_message=Select a file or folder to be patched +InputPatchPage_ClipboardIsEmpty_message=Clipboard is empty +InputPatchPage_NoTextInClipboard_message=Clipboard does not contain text +InputPatchPage_CouldNotReadClipboard_message=Cannot retrieve clipboard contents +InputPatchPage_CannotLocatePatch_message=Cannot locate patch file: +InputPatchPage_NoFileName_message=No file name +InputPatchPage_FileSelectedNotPatch_message=Selected resource is not a valid patch +#SI - Select file name ? +InputPatchPage_SelectPatchFileDialog_title=Select Patch File +InputPatchPage_PatchFileNotFound_message=Patch file not found. +InputPatchPage_ParseError_message=Error while parsing patch +InputPatchPage_Clipboard_title=Clipboard +InputPatchPage_PatchFile_title=Patch file +InputPatchPage_URL_title=URL +InputPatchPage_WorkspacePatch_title=Workspace file +InputPatchPage_NoDiffsFound_format={0} does not contain valid patch. +InputPatchPage_SingleFileError_format={0} contains multiple patches. You cannot apply them to a single file. +InputPatchPage_URLConnecting=Opening connection to the URL +InputPatchPage_URLFetchingContent=Fetching content from the URL +# +# PatchTargetPage +# +PatchTargetPage_title=Target Resource +PatchTargetPage_0=Apply the patch to the &workspace root +PatchTargetPage_message=Select the target workspace resource for the patch. +PatchCompareEditorInput_0=(file does not exist) +# +# PreviewPatchPage +# +PreviewPatchPage_title=Review Patch +PreviewPatchPage2_0=&Exclude +PreviewPatchPage2_1=&Include +PreviewPatchPage2_2=Performing this operation will require that your manual changes be discarded. +PreviewPatchPage2_3=Reversing the patch will require that your manual changes be discarded. +PreviewPatchPage2_4=Performing this operation will require that your manual changes be discarded. +PreviewPatchPage2_5=Changing the fuzz factor will require that your manual changes be discarded. +PreviewPatchPage2_6=Changing the fuzz factor will require that your manual changes be discarded. +PreviewPatchPage2_7=&Show Excluded +PreviewPatchPage2_8=Review the patch with respect to the local file system and manually merge any unmatched portions. +PreviewPatchPage2_9=Double-click on file or patch segment entries to view their content: +PreviewPatchPage_Target=(target: {0}) +PreviewPatchPage_PatchOptions_title=Patch options +PreviewPatchPage_IgnoreSegments_text=&Ignore leading path name segments: +PreviewPatchPage_ReversePatch_text=&Reverse patch +PreviewPatchPage_FuzzFactor_text=Fu&zz factor: +PreviewPatchPage2_RetargetAction=&Move +PreviewPatchPage2_RetargetTooltip=Move the selected patch element to another resource +PreviewPatchPage2_OrphanedHunk=Unmatched Patch Segment +PreviewPatchPage2_MatchedHunk=Matched Hunk +PreviewPatchPage2_IgnoreWSAction=Ignore whitespace +PreviewPatchPage_FuzzFactor_tooltip=Allow this number of context lines to be ignored +PreviewPatchPage2_IgnoreWSTooltip=Ignore whitespace +PreviewPatchPage2_IgnoreWhitespace=Ignore whitespace +PreviewPatchPage2_PatchedLocalFile=Patched Local File +PreviewPatchPage2_CalculateReverse=Calculating reverse +PreviewPatchPage_RetargetPatch=Retarget Patch +PreviewPatchPage_SelectProject=Select the new target project for the portion of the patch targeted to project ''{0}'': +PreviewPatchPage_GuessFuzz_text= &Guess +PreviewPatchPage_FuzzUsed=(fuzz factor used: {0}) +PreviewPatchPage_AllContextIgnored=(fuzz factor used: {0}, all context lines ignored) +PreviewPatchPage2_ShowMatched=Show &matched hunks +PreviewPatchPage2_AddedRemovedLines=Patch contains {0} added and {1} removed lines. +PreviewPatchLabelDecorator_ProjectDoesNotExist=(Project does not exist in workspace) + +# +# Patcher +# +Diff_2Args={0} {1} +HunkMergePage_Merged=(merged) +HunkMergePage_GenerateRejectFile=G&enerate a .rej file for unmerged hunks +RetargetPatchElementDialog_0=Select the new target file for the portion of the patch targeted to file ''{0}'' +RetargetPatchElementDialog_1=Select the new target file for this portion of the patch targeted to file ''{0}'' diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java new file mode 100644 index 000000000..1aa09b3d5 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.internal.patch; + +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.graphics.Image; + +public class PatchProjectDiffNode extends PatchDiffNode { + + private final DiffProject project; + private final PatchConfiguration configuration; + + public PatchProjectDiffNode(IDiffContainer parent, DiffProject project, PatchConfiguration configuration) { + super(project, parent, Differencer.NO_CHANGE); + this.project = project; + this.configuration = configuration; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffNode#getName() + */ + public String getName() { + return project.getName(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffNode#getImage() + */ + public Image getImage() { + Image image = CompareUI.getImage(Utilities.getProject(project)); + if (containsProblems()) { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(getConfiguration()); + image = HunkTypedElement.getHunkErrorImage(image, imageCache, true); + } + return image; + } + + private boolean containsProblems() { + IDiffElement[] elements = getChildren(); + for (int i = 0; i < elements.length; i++) { + IDiffElement diffElement = elements[i]; + if (diffElement instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) diffElement; + if (node.getDiffResult().containsProblems()) + return true; + } + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffNode#getType() + */ + public String getType() { + return ITypedElement.FOLDER_TYPE; + } + + protected PatchConfiguration getConfiguration() { + return configuration; + } + + public DiffProject getDiffProject() { + return project; + } + + public IResource getResource() { + return ResourcesPlugin.getWorkspace().getRoot().getProject(getDiffProject().getName()); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java new file mode 100644 index 000000000..0f7317461 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.internal.patch; + +import com.ibm.icu.text.MessageFormat; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardPage; + +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; + +import org.eclipse.compare.internal.ICompareContextIds; +import org.eclipse.compare.internal.Utilities; + +/*** + * This page only shows up if the user is trying to apply + * a non-workspace rooted patch. + */ +public class PatchTargetPage extends WizardPage { + + private boolean fShowError = false; + + // SWT widgets + private TreeViewer fPatchTargets; + private Button useWorkspaceAsTarget; + private Button selectTarget; + + protected WorkspacePatcher fPatcher; + + protected final static String PATCHTARGETPAGE_NAME = "PatchTargetPage"; //$NON-NLS-1$ + + public PatchTargetPage(WorkspacePatcher patcher) { + super(PATCHTARGETPAGE_NAME, PatchMessages.PatchTargetPage_title, null); + setMessage(PatchMessages.PatchTargetPage_message); + fPatcher = patcher; + } + + /* + * Get a path from the supplied text widget. + * @return org.eclipse.core.runtime.IPath + */ + protected IPath getPathFromText(Text textField) { + return (new Path(textField.getText())).makeAbsolute(); + } + + public void createControl(Composite parent) { + + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); + setControl(composite); + + useWorkspaceAsTarget = createRadioButton(composite, PatchMessages.PatchTargetPage_0, 1); + selectTarget = createRadioButton(composite, PatchMessages.InputPatchPage_SelectInput, 1); + + buildInputGroup(composite); + + updateWidgetEnablements(); + + Dialog.applyDialogFont(composite); + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.PATCH_INPUT_WIZARD_PAGE); + + useWorkspaceAsTarget.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + fShowError = true; + if (useWorkspaceAsTarget.getSelection()) { + fPatchTargets.getTree().setEnabled(false); + fPatcher.setTarget(ResourcesPlugin.getWorkspace().getRoot()); + } else { + fPatchTargets.getTree().setEnabled(true); + fPatcher.setTarget(Utilities.getFirstResource(fPatchTargets.getSelection())); + } + updateWidgetEnablements(); + } + }); + } + + private Button createRadioButton(Composite parent, String label, int span) { + Button button = new Button(parent, SWT.RADIO); + button.setText(label); + GridData data = new GridData(); + data.horizontalSpan = span; + button.setLayoutData(data); + return button; + } + + /* (non-JavaDoc) + * Method declared in IWizardPage. + */ + public IWizardPage getNextPage() { + + // if selected target is file ensure that patch file + // contains only a patch for a single file + if (!fPatcher.isWorkspacePatch() && fPatcher.getTarget() instanceof IFile && fPatcher.getDiffs().length > 1) { + InputPatchPage inputPage = (InputPatchPage) getWizard().getPage(InputPatchPage.INPUTPATCHPAGE_NAME); + String source = ""; //$NON-NLS-1$ + switch (inputPage.getInputMethod()) { + case InputPatchPage.CLIPBOARD : + source = PatchMessages.InputPatchPage_Clipboard_title; + break; + + case InputPatchPage.FILE : + source = PatchMessages.InputPatchPage_PatchFile_title; + break; + + case InputPatchPage.WORKSPACE : + source = PatchMessages.InputPatchPage_WorkspacePatch_title; + break; + } + String format = PatchMessages.InputPatchPage_SingleFileError_format; + String message = MessageFormat.format(format, new String[] {source}); + MessageDialog.openInformation(null, PatchMessages.InputPatchPage_PatchErrorDialog_title, message); + return this; + } + + return super.getNextPage(); + } + + /* (non-JavaDoc) + * Method declared in IWizardPage. + */ + public boolean canFlipToNextPage() { + // we can't call getNextPage to determine if flipping is allowed since computing + // the next page is quite expensive. So we say yes if the page is complete. + return isPageComplete(); + } + + private void buildInputGroup(Composite parent) { + Tree tree = new Tree(parent, SWT.BORDER); + GridData gd = new GridData(GridData.FILL_BOTH); + gd.heightHint = 200; + tree.setLayoutData(gd); + + fPatchTargets = new TreeViewer(tree); + fPatchTargets.setLabelProvider(new WorkbenchLabelProvider()); + fPatchTargets.setContentProvider(new WorkbenchContentProvider()); + fPatchTargets.setComparator(new ResourceComparator(ResourceComparator.NAME)); + fPatchTargets.setInput(ResourcesPlugin.getWorkspace().getRoot()); + + IResource target = fPatcher.getTarget(); + if (target != null && !(target instanceof IWorkspaceRoot)) { + fPatchTargets.expandToLevel(target, 0); + fPatchTargets.setSelection(new StructuredSelection(target)); + } + + // register listeners + fPatchTargets.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + fShowError = true; + fPatcher.setTarget(Utilities.getFirstResource(event.getSelection())); + updateWidgetEnablements(); + } + }); + + fPatchTargets.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ((PatchWizard)getWizard()).showPage(getNextPage()); + } + }); + } + + /** + * Updates the enable state of this page's controls. + */ + private void updateWidgetEnablements() { + String error = null; + + if (fPatcher.getTarget() == null) { + useWorkspaceAsTarget.setSelection(false); + selectTarget.setSelection(true); + error = PatchMessages.InputPatchPage_NothingSelected_message; + setPageComplete(false); + if (fShowError) + setErrorMessage(error); + return; + } + setErrorMessage(null); + useWorkspaceAsTarget.setSelection(fPatcher.getTarget() instanceof IWorkspaceRoot); + selectTarget.setSelection(!useWorkspaceAsTarget.getSelection()); + setPageComplete(true); + } + + /** + * The Finish button was pressed. Try to do the required work now and answer + * a boolean indicating success. If false is returned then the wizard will + * not close. + * + * @return boolean + */ + public boolean finish() { + return true; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java new file mode 100644 index 000000000..ede6e0649 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java @@ -0,0 +1,236 @@ +/******************************************************************************* + * 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.internal.patch; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ExceptionHandler; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.actions.WorkspaceModifyOperation; + +public class PatchWizard extends Wizard { + + // dialog store id constants + private final static String DIALOG_SETTINGS_KEY= "PatchWizard"; //$NON-NLS-1$ + + private boolean fHasNewDialogSettings; + + protected InputPatchPage fPatchWizardPage; + protected PatchTargetPage fPatchTargetPage; + protected PreviewPatchPage2 fPreviewPage2; + + private final WorkspacePatcher fPatcher; + + private CompareConfiguration fConfiguration; + private IStorage patch; + + private boolean patchReadIn = false; + + public PatchWizard(IStorage patch, IResource target, CompareConfiguration configuration) { + Assert.isNotNull(configuration); + this.fConfiguration = configuration; + setDefaultPageImageDescriptor(CompareUIPlugin.getImageDescriptor("wizban/applypatch_wizban.png")); //$NON-NLS-1$ + setWindowTitle(PatchMessages.PatchWizard_title); + initializeDialogSettings(); + fPatcher= new WorkspacePatcher(target); + if (patch != null) { + try { + fPatcher.parse(patch); + this.patch = patch; + patchReadIn = true; + } catch (IOException e) { + MessageDialog.openError(null, + PatchMessages.InputPatchPage_PatchErrorDialog_title, + PatchMessages.InputPatchPage_ParseError_message); + } catch (CoreException e) { + ErrorDialog.openError(getShell(), + PatchMessages.InputPatchPage_PatchErrorDialog_title, + PatchMessages.InputPatchPage_PatchFileNotFound_message, e.getStatus()); + } + } + } + + private void initializeDialogSettings() { + IDialogSettings workbenchSettings= CompareUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section= workbenchSettings.getSection(DIALOG_SETTINGS_KEY); + if (section == null) { + fHasNewDialogSettings= true; + } else { + fHasNewDialogSettings= false; + setDialogSettings(section); + } + } + + protected WorkspacePatcher getPatcher() { + return fPatcher; + } + + protected IStorage getPatch() { + return patch; + } + + IResource getTarget() { + return fPatcher.getTarget(); + } + + /* (non-Javadoc) + * Method declared on IWizard. + */ + public void addPages() { + if (patch == null) + addPage(fPatchWizardPage = new InputPatchPage(this)); + if (patch == null || !fPatcher.isWorkspacePatch()) + addPage(fPatchTargetPage = new PatchTargetPage(fPatcher)); + fPreviewPage2 = new PreviewPatchPage2(fPatcher, fConfiguration); + addPage(fPreviewPage2); + } + + /* (non-Javadoc) + * Method declared on IWizard. + */ + public boolean performFinish() { + + IWizardPage currentPage = getContainer().getCurrentPage(); + if (currentPage.getName().equals(PreviewPatchPage2.PREVIEWPATCHPAGE_NAME)){ + PreviewPatchPage2 previewPage = (PreviewPatchPage2) currentPage; + previewPage.ensureContentsSaved(); + } + + if (fPatchWizardPage != null){ + // make sure that the patch has been read + if (!fPatchWizardPage.isPatchRead()) + fPatchWizardPage.readInPatch(); + fPatcher.refresh(); + } else { + //either we have a patch from the patch input page or one has + //been specified; double check this + Assert.isNotNull(patch); + //make sure that the patch has been read in + Assert.isTrue(patchReadIn); + } + + if (!currentPage.getName().equals(PreviewPatchPage2.PREVIEWPATCHPAGE_NAME) && fPatcher.hasRejects()){ + if (!MessageDialog.openConfirm(getShell(), PatchMessages.PatchWizard_0, PatchMessages.PatchWizard_1)) { + return false; + } + } + + try { + // create scheduling rule based on the type of patch - single or workspace + ISchedulingRule scheduleRule = null; + if (fPatcher.isWorkspacePatch()) { + // workspace patch + ISchedulingRule[] projectRules = fPatcher.getTargetProjects(); + scheduleRule = new MultiRule(projectRules); + } else { + // single patch + IResource resource = getTarget(); + if (resource.getType() == IResource.FILE) { + // For a file, use the modify rule for the parent since we may need to include a reject file + resource = resource.getParent(); + } + scheduleRule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(resource); + } + + WorkspaceModifyOperation op = new WorkspaceModifyOperation(scheduleRule) { + protected void execute(IProgressMonitor monitor) throws InvocationTargetException { + try { + fPatcher.applyAll(monitor, new Patcher.IFileValidator() { + public boolean validateResources(IFile[] resoures) { + return Utilities.validateResources(resoures, getShell(), PatchMessages.PatchWizard_title); + } + }); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + }; + getContainer().run(true, false, op); + + } catch (InvocationTargetException e) { + ExceptionHandler.handle(e, PatchMessages.PatchWizard_title, PatchMessages.PatchWizard_unexpectedException_message); + } catch (InterruptedException e) { + // cannot happen + // NeedWork: use assert! + } + + // Save the dialog settings + if (fHasNewDialogSettings) { + IDialogSettings workbenchSettings = CompareUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section = workbenchSettings.getSection(DIALOG_SETTINGS_KEY); + section = workbenchSettings.addNewSection(DIALOG_SETTINGS_KEY); + setDialogSettings(section); + } + + if (fPatchWizardPage != null) + fPatchWizardPage.saveWidgetValues(); + fPreviewPage2.saveWidgetValues(); + return true; + } + + public void showPage(IWizardPage page) { + getContainer().showPage(page); + } + + public IWizardPage getNextPage(IWizardPage page) { + //no patch has been read in yet, input patch page + if (!patchReadIn) + return fPatchWizardPage; + + //Check to see if we're already on the patch target page and if + //a target has been set - if it has return the next page in sequence (the preview patch page) + if (page instanceof PatchTargetPage && getTarget() != null) { + return super.getNextPage(page); + } else if (page instanceof InputPatchPage && !fPatcher.isWorkspacePatch()) { + //Check to see if we need a target + return fPatchTargetPage; + } + return super.getNextPage(page); + } + + /** + * Used to report that the patch has + * + */ + protected void patchReadIn() { + patchReadIn = true; + } + + public CompareConfiguration getCompareConfiguration() { + return fConfiguration; + } + + public boolean canFinish() { + IWizardPage currentPage = getContainer().getCurrentPage(); + if (currentPage.getName().equals(PreviewPatchPage2.PREVIEWPATCHPAGE_NAME)){ + return currentPage.isPageComplete(); + } + return super.canFinish(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java new file mode 100644 index 000000000..bd61ac389 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2005, 2006 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.internal.patch; + +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Shell; + +public class PatchWizardDialog extends WizardDialog { + private static final String PATCH_WIZARD_SETTINGS_SECTION = "PatchWizard"; //$NON-NLS-1$ + + public PatchWizardDialog(Shell parent, IWizard wizard) { + super(parent, wizard); + + setShellStyle(getShellStyle() | SWT.RESIZE); + setMinimumPageSize(700, 500); + } + + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings settings = CompareUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section = settings.getSection(PATCH_WIZARD_SETTINGS_SECTION); + if (section == null) { + section = settings.addNewSection(PATCH_WIZARD_SETTINGS_SECTION); + } + return section; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java new file mode 100644 index 000000000..a059819db --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java @@ -0,0 +1,763 @@ +/******************************************************************************* + * 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 + * Martin Burger <m@rtin-burger.de> patch for #93810 and #93901 + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.compare.internal.core.Messages; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.compare.internal.core.patch.PatchReader; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.patch.IHunkFilter; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; + +/** + * A Patcher + * - knows how to parse various patch file formats into some in-memory structure, + * - holds onto the parsed data and the options to use when applying the patches, + * - knows how to apply the patches to files and folders. + */ +public class Patcher implements IHunkFilter { + + static protected final String REJECT_FILE_EXTENSION= ".rej"; //$NON-NLS-1$ + + static protected final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker"; //$NON-NLS-1$ + + /** + * Property used to associate a patcher with a {@link PatchConfiguration} + */ + public static final String PROP_PATCHER = "org.eclipse.compare.patcher"; //$NON-NLS-1$ + + public interface IFileValidator { + boolean validateResources(IFile[] array); + } + + // diff formats + // private static final int CONTEXT= 0; + // private static final int ED= 1; + // private static final int NORMAL= 2; + // private static final int UNIFIED= 3; + + private FilePatch2[] fDiffs; + private IResource fTarget; + // patch options + private Set disabledElements = new HashSet(); + private Map diffResults = new HashMap(); + private final Map contentCache = new HashMap(); + private Set mergedHunks = new HashSet(); + + private final PatchConfiguration configuration; + private boolean fGenerateRejectFile = false; + + public Patcher() { + configuration = new PatchConfiguration(); + configuration.setProperty(PROP_PATCHER, this); + configuration.addHunkFilter(this); + } + + /* + * Returns an array of Diffs after a sucessfull call to <code>parse</code>. + * If <code>parse</code> hasn't been called returns <code>null</code>. + */ + public FilePatch2[] getDiffs() { + if (fDiffs == null) + return new FilePatch2[0]; + return fDiffs; + } + + public IPath getPath(FilePatch2 diff) { + return diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setStripPrefixSegments(int strip) { + if (strip != getConfiguration().getPrefixSegmentStripCount()) { + getConfiguration().setPrefixSegmentStripCount(strip); + return true; + } + return false; + } + + int getStripPrefixSegments() { + return getConfiguration().getPrefixSegmentStripCount(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setFuzz(int fuzz) { + if (fuzz != getConfiguration().getFuzz()) { + getConfiguration().setFuzz(fuzz); + return true; + } + return false; + } + + public int getFuzz(){ + return getConfiguration().getFuzz(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setIgnoreWhitespace(boolean ignoreWhitespace) { + if (ignoreWhitespace != getConfiguration().isIgnoreWhitespace()) { + getConfiguration().setIgnoreWhitespace(ignoreWhitespace); + return true; + } + return false; + } + + public boolean isIgnoreWhitespace() { + return getConfiguration().isIgnoreWhitespace(); + } + + public boolean isGenerateRejectFile() { + return fGenerateRejectFile; + } + + public void setGenerateRejectFile(boolean generateRejectFile) { + fGenerateRejectFile = generateRejectFile; + } + + //---- parsing patch files + + public void parse(IStorage storage) throws IOException, CoreException { + BufferedReader reader = Utilities.createReader(storage); + try { + parse(reader); + } finally { + try { + reader.close(); + } catch (IOException e) { //ignored + } + } + } + + public void parse(BufferedReader reader) throws IOException { + PatchReader patchReader = new PatchReader() { + protected FilePatch2 createFileDiff(IPath oldPath, long oldDate, + IPath newPath, long newDate) { + return new FilePatch(oldPath, oldDate, newPath, newDate); + } + }; + patchReader.parse(reader); + patchParsed(patchReader); + } + + protected void patchParsed(PatchReader patchReader) { + fDiffs = patchReader.getDiffs(); + } + + public void countLines() { + FilePatch2[] fileDiffs = getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + int addedLines = 0; + int removedLines = 0; + FilePatch2 fileDiff = fileDiffs[i]; + for (int j = 0; j < fileDiff.getHunkCount(); j++) { + IHunk hunk = fileDiff.getHunks()[j]; + String[] lines = ((Hunk) hunk).getLines(); + for (int k = 0; k < lines.length; k++) { + char c = lines[k].charAt(0); + switch (c) { + case '+': + addedLines++; + continue; + case '-': + removedLines++; + continue; + } + } + } + fileDiff.setAddedLines(addedLines); + fileDiff.setRemovedLines(removedLines); + } + } + + //---- applying a patch file + + public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException { + + int i; + + IFile singleFile= null; // file to be patched + IContainer container= null; + if (fTarget instanceof IContainer) + container= (IContainer) fTarget; + else if (fTarget instanceof IFile) { + singleFile= (IFile) fTarget; + container= singleFile.getParent(); + } else { + Assert.isTrue(false); + } + + // get all files to be modified in order to call validateEdit + List list= new ArrayList(); + if (singleFile != null) + list.add(singleFile); + else { + for (i= 0; i < fDiffs.length; i++) { + FilePatch2 diff= fDiffs[i]; + if (isEnabled(diff)) { + switch (diff.getDiffType(isReversed())) { + case FilePatch2.CHANGE: + list.add(createPath(container, getPath(diff))); + break; + } + } + } + } + if (! validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { + return; + } + + final int WORK_UNIT= 10; + if (pm != null) { + String message= Messages.Patcher_0; + pm.beginTask(message, fDiffs.length*WORK_UNIT); + } + + for (i= 0; i < fDiffs.length; i++) { + + int workTicks= WORK_UNIT; + + FilePatch2 diff= fDiffs[i]; + if (isEnabled(diff)) { + + IPath path= getPath(diff); + if (pm != null) + pm.subTask(path.toString()); + + IFile file= singleFile != null + ? singleFile + : createPath(container, path); + + List failed= new ArrayList(); + + int type= diff.getDiffType(isReversed()); + switch (type) { + case FilePatch2.ADDITION: + // patch it and collect rejected hunks + List result= apply(diff, file, true, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + case FilePatch2.DELETION: + file.delete(true, true, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + case FilePatch2.CHANGE: + // patch it and collect rejected hunks + result= apply(diff, file, false, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + } + + if (isGenerateRejectFile() && failed.size() > 0) { + IPath pp = getRejectFilePath(path); + file= createPath(container, pp); + if (file != null) { + store(getRejected(failed), file, pm); + try { + IMarker marker= file.createMarker(MARKER_TYPE); + marker.setAttribute(IMarker.MESSAGE, Messages.Patcher_1); + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + } catch (CoreException ex) { + // NeedWork + } + } + } + } + + if (pm != null) { + if (pm.isCanceled()) + break; + if (workTicks > 0) + pm.worked(workTicks); + } + } + } + + private IPath getRejectFilePath(IPath path) { + IPath pp= null; + if (path.segmentCount() > 1) { + pp= path.removeLastSegments(1); + pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION); + } else + pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION); + return pp; + } + + List apply(FilePatch2 diff, IFile file, boolean create, List failedHunks) { + FileDiffResult result = getDiffResult(diff); + List lines = LineReader.load(file, create); + result.patch(lines, null); + failedHunks.addAll(result.getFailedHunks()); + if (hasCachedContents(diff)) { + // Used the cached contents since they would have been provided by the user + return getCachedLines(diff); + } else if (!result.hasMatches()) { + // Return null if there were no matches + return null; + } + return result.getLines(); + } + + /* + * Converts the string into bytes and stores them in the given file. + */ + protected void store(String contents, IFile file, IProgressMonitor pm) throws CoreException { + + byte[] bytes; + try { + bytes= contents.getBytes(Utilities.getCharset(file)); + } catch (UnsupportedEncodingException x) { + // uses default encoding + bytes= contents.getBytes(); + } + + store(bytes,file, pm); + } + + protected void store(byte[] bytes, IFile file, IProgressMonitor pm) throws CoreException { + InputStream is= new ByteArrayInputStream(bytes); + try { + if (file.exists()) { + file.setContents(is, false, true, pm); + } else { + file.create(is, false, pm); + } + } finally { + if (is != null) + try { + is.close(); + } catch(IOException ex) { + // silently ignored + } + } + } + + public boolean isPreserveLineDelimeters() { + return true; + } + + public static String getRejected(List failedHunks) { + if (failedHunks.size() <= 0) + return null; + + String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuffer sb= new StringBuffer(); + Iterator iter= failedHunks.iterator(); + while (iter.hasNext()) { + Hunk hunk= (Hunk) iter.next(); + sb.append(hunk.getRejectedDescription()); + sb.append(lineSeparator); + sb.append(hunk.getContent()); + } + return sb.toString(); + } + + /* + * Ensures that a file with the given path exists in + * the given container. Folder are created as necessary. + */ + protected IFile createPath(IContainer container, IPath path) throws CoreException { + if (path.segmentCount() > 1) { + IContainer childContainer; + if (container instanceof IWorkspaceRoot) { + IProject project = ((IWorkspaceRoot)container).getProject(path.segment(0)); + if (!project.exists()) + project.create(null); + if (!project.isOpen()) + project.open(null); + childContainer = project; + } else { + IFolder f= container.getFolder(path.uptoSegment(1)); + if (!f.exists()) + f.create(false, true, null); + childContainer = f; + } + return createPath(childContainer, path.removeFirstSegments(1)); + } + // a leaf + return container.getFile(path); + } + + public IResource getTarget() { + return fTarget; + } + + public void setTarget(IResource target) { + fTarget= target; + } + + + public IFile getTargetFile(FilePatch2 diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + return existsInTarget(path); + } + + /** + * Iterates through all of the resources contained in the Patch Wizard target + * and looks to for a match to the passed in file + * @param path + * @return IFile which matches the passed in path or null if none found + */ + public IFile existsInTarget(IPath path) { + if (fTarget instanceof IFile) { // special case + IFile file= (IFile) fTarget; + if (matches(file.getFullPath(), path)) + return file; + } else if (fTarget instanceof IContainer) { + IContainer c= (IContainer) fTarget; + if (c.exists(path)) + return c.getFile(path); + } + return null; + } + + /** + * Returns true if path completely matches the end of fullpath + * @param fullpath + * @param path + * @return true if path matches, false otherwise + */ + private boolean matches(IPath fullpath, IPath path) { + for (IPath p= fullpath; path.segmentCount()<=p.segmentCount(); p= p.removeFirstSegments(1)) { + if (p.equals(path)) + return true; + } + return false; + } + + public int calculatePrefixSegmentCount() { + //Update prefix count - go through all of the diffs and find the smallest + //path segment contained in all diffs. + int length= 99; + if (fDiffs!=null) + for (int i= 0; i<fDiffs.length; i++) { + FilePatch2 diff= fDiffs[i]; + length= Math.min(length, diff.segmentCount()); + } + return length; + } + + public void addDiff(FilePatch2 newDiff){ + FilePatch2[] temp = new FilePatch2[fDiffs.length + 1]; + System.arraycopy(fDiffs,0, temp, 0, fDiffs.length); + temp[fDiffs.length] = newDiff; + fDiffs = temp; + } + + public void removeDiff(FilePatch2 diffToRemove){ + FilePatch2[] temp = new FilePatch2[fDiffs.length - 1]; + int counter = 0; + for (int i = 0; i < fDiffs.length; i++) { + if (fDiffs[i] != diffToRemove){ + temp[counter++] = fDiffs[i]; + } + } + fDiffs = temp; + } + + public void setEnabled(Object element, boolean enabled) { + if (element instanceof DiffProject) + setEnabledProject((DiffProject) element, enabled); + if (element instanceof FilePatch2) + setEnabledFile((FilePatch2)element, enabled); + if (element instanceof Hunk) + setEnabledHunk((Hunk) element, enabled); + } + + private void setEnabledProject(DiffProject projectDiff, boolean enabled) { + FilePatch2[] diffFiles = projectDiff.getFileDiffs(); + for (int i = 0; i < diffFiles.length; i++) { + setEnabledFile(diffFiles[i], enabled); + } + } + + private void setEnabledFile(FilePatch2 fileDiff, boolean enabled) { + IHunk[] hunks = fileDiff.getHunks(); + for (int i = 0; i < hunks.length; i++) { + setEnabledHunk((Hunk) hunks[i], enabled); + } + } + + private void setEnabledHunk(Hunk hunk, boolean enabled) { + if (enabled) { + disabledElements.remove(hunk); + FilePatch2 file = hunk.getParent(); + disabledElements.remove(file); + DiffProject project = file.getProject(); + if (project != null) + disabledElements.remove(project); + } else { + disabledElements.add(hunk); + FilePatch2 file = hunk.getParent(); + if (disabledElements.containsAll(Arrays.asList(file.getHunks()))) { + disabledElements.add(file); + DiffProject project = file.getProject(); + if (project != null + && disabledElements.containsAll(Arrays.asList(project + .getFileDiffs()))) + disabledElements.add(project); + } + } + } + + public boolean isEnabled(Object element) { + if (disabledElements.contains(element)) + return false; + Object parent = getElementParent(element); + if (parent == null) + return true; + return isEnabled(parent); + } + + protected Object getElementParent(Object element) { + if (element instanceof Hunk) { + Hunk hunk = (Hunk) element; + return hunk.getParent(); + } + return null; + } + + /** + * Calculate the fuzz factor that will allow the most hunks to be matched. + * @param monitor a progress monitor + * @return the fuzz factor or <code>-1</code> if no hunks could be matched + */ + public int guessFuzzFactor(IProgressMonitor monitor) { + try { + monitor.beginTask(Messages.Patcher_2, IProgressMonitor.UNKNOWN); + FilePatch2[] diffs= getDiffs(); + if (diffs==null||diffs.length<=0) + return -1; + int fuzz= -1; + for (int i= 0; i<diffs.length; i++) { + FilePatch2 d= diffs[i]; + IFile file= getTargetFile(d); + if (file != null && file.exists()) { + List lines= LineReader.load(file, false); + FileDiffResult result = getDiffResult(d); + int f = result.calculateFuzz(lines, monitor); + if (f > fuzz) + fuzz = f; + } + } + return fuzz; + } finally { + monitor.done(); + } + } + + public void refresh() { + diffResults.clear(); + refresh(getDiffs()); + } + + public void refresh(FilePatch2[] diffs) { + for (int i = 0; i < diffs.length; i++) { + FilePatch2 diff = diffs[i]; + FileDiffResult result = getDiffResult(diff); + ((WorkspaceFileDiffResult)result).refresh(); + } + } + + public FileDiffResult getDiffResult(FilePatch2 diff) { + FileDiffResult result = (FileDiffResult)diffResults.get(diff); + if (result == null) { + result = new WorkspaceFileDiffResult(diff, getConfiguration()); + diffResults.put(diff, result); + } + return result; + } + + public PatchConfiguration getConfiguration() { + return configuration; + } + + /** + * Return the project that contains this diff or <code>null</code> + * if the patch is not a workspace patch. + * @param diff the diff + * @return the project that contains the diff + */ + public DiffProject getProject(FilePatch2 diff) { + return diff.getProject(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setReversed(boolean reverse) { + if (getConfiguration().isReversed() != reverse) { + getConfiguration().setReversed(reverse); + refresh(); + return true; + } + return false; + } + + public boolean isReversed() { + return getConfiguration().isReversed(); + } + + /** + * Cache the contents for the given file diff. These contents + * will be used for the diff when the patch is applied. When the + * patch is applied, it is assumed that the provided contents + * already have all relevant hunks applied. + * @param diff the file diff + * @param contents the contents for the file diff + */ + public void cacheContents(FilePatch2 diff, byte[] contents) { + contentCache.put(diff, contents); + } + + /** + * Return whether contents have been cached for the + * given file diff. + * @param diff the file diff + * @return whether contents have been cached for the file diff + * @see #cacheContents(FilePatch2, byte[]) + */ + public boolean hasCachedContents(FilePatch2 diff) { + return contentCache.containsKey(diff); + } + + /** + * Return the content lines that are cached for the given + * file diff. + * @param diff the file diff + * @return the content lines that are cached for the file diff + */ + public List getCachedLines(FilePatch2 diff) { + byte[] contents = (byte[])contentCache.get(diff); + if (contents != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(contents))); + return LineReader.readLines(reader); + } + return null; + } + + /** + * Return the contents that are cached for the given diff or + * <code>null</code> if there is no contents cached. + * @param diff the diff + * @return the contents that are cached for the given diff or + * <code>null</code> + */ + public byte[] getCachedContents(FilePatch2 diff) { + return (byte[])contentCache.get(diff); + } + + /** + * Return whether the patcher has any cached contents. + * @return whether the patcher has any cached contents + */ + public boolean hasCachedContents() { + return !contentCache.isEmpty(); + } + + /** + * Clear any cached contents. + */ + public void clearCachedContents() { + contentCache.clear(); + mergedHunks.clear(); + } + + public void setProperty(String key, Object value) { + getConfiguration().setProperty(key, value); + } + + public Object getProperty(String key) { + return getConfiguration().getProperty(key); + } + + public boolean isManuallyMerged(Hunk hunk) { + return mergedHunks.contains(hunk); + } + + public void setManuallyMerged(Hunk hunk, boolean merged) { + if (merged) + mergedHunks.add(hunk); + else + mergedHunks.remove(hunk); + } + + public IProject getTargetProject(FilePatch2 diff) { + DiffProject dp = getProject(diff); + if (dp != null) + return Utilities.getProject(dp); + IResource tr = getTarget(); + if (tr instanceof IWorkspaceRoot) { + IWorkspaceRoot root = (IWorkspaceRoot) tr; + return root.getProject(diff.getPath(isReversed()).segment(0)); + } + return tr.getProject(); + } + + public static Patcher getPatcher(PatchConfiguration configuration) { + return (Patcher)configuration.getProperty(PROP_PATCHER); + } + + public boolean hasRejects() { + for (Iterator iterator = diffResults.values().iterator(); iterator.hasNext();) { + FileDiffResult result = (FileDiffResult) iterator.next(); + if (result.hasRejects()) + return true; + } + return false; + } + + public boolean select(IHunk hunk) { + return isEnabled(hunk); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java new file mode 100644 index 000000000..7414cbef3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java @@ -0,0 +1,747 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.internal.patch; + +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.regex.Pattern; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.events.ExpansionAdapter; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.widgets.ExpandableComposite; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; + + +public class PreviewPatchPage2 extends WizardPage { + + protected final static String PREVIEWPATCHPAGE_NAME= "PreviewPatchPage"; //$NON-NLS-1$ + + private static final String EXPAND_PATCH_OPTIONS = "expandPatchOptions"; //$NON-NLS-1$ + private static final String GENERATE_REJECTS = "generateRejects"; //$NON-NLS-1$ + + final WorkspacePatcher fPatcher; + private final CompareConfiguration fConfiguration; + private PatchCompareEditorInput fInput; + + private Combo fStripPrefixSegments; + private Text fFuzzField; + private Label addedRemovedLines; + + private Action fExcludeAction; + private Action fIncludeAction; + private Action fIgnoreWhiteSpace; + private Action fReversePatch; + private Action fMoveAction; + + protected boolean pageRecalculate= true; + + private IDialogSettings settings; + private ExpandableComposite patchOptions; + private Button generateRejects; + private FormToolkit fToolkit; + + public PreviewPatchPage2(WorkspacePatcher patcher, CompareConfiguration configuration) { + super(PREVIEWPATCHPAGE_NAME, PatchMessages.PreviewPatchPage_title, null); + setDescription(PatchMessages.PreviewPatchPage2_8); + Assert.isNotNull(patcher); + Assert.isNotNull(configuration); + this.fPatcher = patcher; + this.fConfiguration = configuration; + this.fConfiguration.addPropertyChangeListener(new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(CompareConfiguration.IGNORE_WHITESPACE)){ + rebuildTree(); + } + } + }); + } + + public void createControl(Composite parent) { + fToolkit = new FormToolkit(parent.getDisplay()); + fToolkit.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); + + final Form form = fToolkit.createForm(parent); + Composite composite = form.getBody(); + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + initializeDialogUnits(parent); + + fInput = new PatchCompareEditorInput(getPatcher(), getCompareConfiguration()) { + protected void fillContextMenu(IMenuManager manager) { + if (isShowAll()) { + manager.add(fIncludeAction); + } + manager.add(fExcludeAction); + manager.add(new Separator()); + manager.add(fMoveAction); + } + }; + + buildPatchOptionsGroup(form); + + // Initialize the input + try { + fInput.run(null); + } catch (InterruptedException e) {//ignore + } catch (InvocationTargetException e) {//ignore + } + + Label label = new Label(composite, SWT.NONE); + label.setText(PatchMessages.PreviewPatchPage2_9); + Control c = fInput.createContents(composite); + initializeActions(); + fInput.contributeDiffViewerToolbarItems(getContributedActions(), getPatcher().isWorkspacePatch()); + fInput.getViewer().addSelectionChangedListener(new ISelectionChangedListener(){ + public void selectionChanged(SelectionChangedEvent event) { + ISelection s = event.getSelection(); + if (s != null && !s.isEmpty()) { + if (s instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) s; + updateActions(ss); + } + } + }}); + + c.setLayoutData(new GridData(GridData.FILL_BOTH)); + + addedRemovedLines = new Label(composite, SWT.NONE); + addedRemovedLines.setLayoutData(new GridData(GridData.FILL_HORIZONTAL + | GridData.VERTICAL_ALIGN_BEGINNING)); + + setControl(composite); + + restoreWidgetValues(); + + Dialog.applyDialogFont(composite); + } + + private void updateActions(IStructuredSelection ss) { + fExcludeAction.setEnabled(false); + fIncludeAction.setEnabled(false); + for (Iterator it = ss.iterator(); it.hasNext();) { + Object element = it.next(); + if (element instanceof PatchDiffNode) { + if (((PatchDiffNode) element).isEnabled()) { + fExcludeAction.setEnabled(true); + } else { + fIncludeAction.setEnabled(true); + } + } + } + } + + /** + * Makes sure that at least one hunk is checked off in the tree before + * allowing the patch to be applied. + */ + private void updateEnablements() { + boolean atLeastOneIsEnabled = false; + if (fInput != null) + atLeastOneIsEnabled = fInput.hasResultToApply(); + setPageComplete(atLeastOneIsEnabled); + } + + private Action[] getContributedActions() { + return new Action[]{ fIgnoreWhiteSpace }; + } + + private void initializeActions() { + + fMoveAction = new Action(PatchMessages.PreviewPatchPage2_RetargetAction, null) { + public void run() { + Shell shell = getShell(); + ISelection selection = fInput.getViewer().getSelection(); + PatchDiffNode node = null; + if (selection instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) selection; + if (ss.getFirstElement() instanceof PatchDiffNode) { + node = (PatchDiffNode) ss.getFirstElement(); + } + } + if (node == null) + return; + final RetargetPatchElementDialog dialog = new RetargetPatchElementDialog(shell, fPatcher, node); + int returnCode = dialog.open(); + if (returnCode == Window.OK) { + // TODO: This could be a problem. We should only rebuild the affected nodes + rebuildTree(); + } + } + }; + fMoveAction .setToolTipText(PatchMessages.PreviewPatchPage2_RetargetTooltip); + fMoveAction.setEnabled(true); + fInput.getViewer().addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection sel= (IStructuredSelection) event.getSelection(); + Object obj= sel.getFirstElement(); + boolean enable = false; + if (obj instanceof PatchProjectDiffNode) { + enable = true; + } else if (obj instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) obj; + enable = node.getDiffResult().getDiffProblem(); + } else if (obj instanceof HunkDiffNode) { + enable = true; + } + fMoveAction.setEnabled(enable); + } + }); + + fExcludeAction = new Action(PatchMessages.PreviewPatchPage2_0) { + public void run() { + ISelection selection = fInput.getViewer().getSelection(); + if (selection instanceof TreeSelection){ + TreeSelection treeSelection = (TreeSelection) selection; + Iterator iter = treeSelection.iterator(); + while (iter.hasNext()){ + Object obj = iter.next(); + if (obj instanceof PatchDiffNode){ + PatchDiffNode node = ((PatchDiffNode) obj); + node.setEnabled(false); + // TODO: This may require a rebuild if matched hunks are shown + } + } + updateActions(treeSelection); + } + fInput.getViewer().refresh(); + } + }; + fExcludeAction.setEnabled(true); + + fIncludeAction = new Action(PatchMessages.PreviewPatchPage2_1) { + public void run() { + ISelection selection = fInput.getViewer().getSelection(); + if (selection instanceof TreeSelection){ + TreeSelection treeSelection = (TreeSelection) selection; + Iterator iter = treeSelection.iterator(); + while (iter.hasNext()){ + Object obj = iter.next(); + if (obj instanceof PatchDiffNode){ + PatchDiffNode node = ((PatchDiffNode) obj); + node.setEnabled(true); + // TODO: This may require a rebuild if matched hunks are shown + } + } + updateActions(treeSelection); + } + fInput.getViewer().refresh(); + } + }; + fIncludeAction.setEnabled(true); + + fIgnoreWhiteSpace = new Action(PatchMessages.PreviewPatchPage2_IgnoreWSAction, CompareUIPlugin.getImageDescriptor(ICompareUIConstants.IGNORE_WHITESPACE_ENABLED)){ + public void run(){ + try { + getContainer().run(false, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(PatchMessages.PreviewPatchPage2_IgnoreWhitespace, IProgressMonitor.UNKNOWN); + if (isChecked() != getPatcher().isIgnoreWhitespace()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_2)) { + if (getPatcher().setIgnoreWhitespace(isChecked())){ + getCompareConfiguration().setProperty(CompareConfiguration.IGNORE_WHITESPACE, new Boolean(isChecked())); + } + } else { + fIgnoreWhiteSpace.setChecked(!isChecked()); + } + } + monitor.done(); + } + }); + } catch (InvocationTargetException e) { //ignore + } catch (InterruptedException e) { //ignore + } + } + }; + fIgnoreWhiteSpace.setChecked(false); + fIgnoreWhiteSpace.setToolTipText(PatchMessages.PreviewPatchPage2_IgnoreWSTooltip); + fIgnoreWhiteSpace.setDisabledImageDescriptor(CompareUIPlugin.getImageDescriptor(ICompareUIConstants.IGNORE_WHITESPACE_DISABLED)); + + fReversePatch = new Action(PatchMessages.PreviewPatchPage_ReversePatch_text){ + public void run(){ + try { + getContainer().run(true, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(PatchMessages.PreviewPatchPage2_CalculateReverse, IProgressMonitor.UNKNOWN); + if (isChecked() != getPatcher().isReversed()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_3)) { + if (getPatcher().setReversed(isChecked())){ + rebuildTree(); + } + } else { + fReversePatch.setChecked(!isChecked()); + } + } + monitor.done(); + } + }); + } catch (InvocationTargetException e) { //ignore + } catch (InterruptedException e) { //ignore + } + + } + + }; + fReversePatch.setChecked(false); + fReversePatch.setToolTipText(PatchMessages.PreviewPatchPage_ReversePatch_text); + } + + public void setVisible(boolean visible) { + super.setVisible(visible); + //Need to handle input and rebuild tree only when becoming visible + if (visible){ + fillSegmentCombo(); + // TODO: We should only do this if the tree needs to be rebuilt + rebuildTree(); + updateEnablements(); + addedRemovedLines.setText(countLines()); + // expand the first tree item i.e. change + getCompareConfiguration().getContainer().getNavigator().selectChange(true); + getContainer().updateButtons(); + getShell().getDefaultButton().setFocus(); + } + } + + private boolean promptToRebuild(final String promptToConfirm){ + final Control ctrl = getControl(); + final boolean[] result = new boolean[] { false }; + if (ctrl != null && !ctrl.isDisposed()){ + Runnable runnable = new Runnable() { + public void run() { + if (!ctrl.isDisposed()) { + // flush any viewers before prompting + try { + fInput.saveChanges(null); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + result[0] = fInput.confirmRebuild(promptToConfirm); + } + } + }; + if (Display.getCurrent() == null) + ctrl.getDisplay().syncExec(runnable); + else + runnable.run(); + } + return result[0]; + } + + private void rebuildTree(){ + final Control ctrl = getControl(); + if (ctrl != null && !ctrl.isDisposed()){ + Runnable runnable = new Runnable() { + public void run() { + if (!ctrl.isDisposed()) { + fInput.buildTree(); + updateEnablements(); + } + } + }; + if (Display.getCurrent() == null) + ctrl.getDisplay().syncExec(runnable); + else + runnable.run(); + } + } + + private void fillSegmentCombo() { + if (getPatcher().isWorkspacePatch()) { + fStripPrefixSegments.setEnabled(false); + } else { + fStripPrefixSegments.setEnabled(true); + int length= 99; + if (fStripPrefixSegments!=null && pageRecalculate) { + length= getPatcher().calculatePrefixSegmentCount(); + if (length!=99) { + for (int k= 1; k<length; k++) + fStripPrefixSegments.add(Integer.toString(k)); + pageRecalculate= false; + } + } + } + } + /* + * Create the group for setting various patch options + */ + private void buildPatchOptionsGroup(final Form form) { + Composite parent = form.getBody(); + + patchOptions = fToolkit.createExpandableComposite(parent, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT); + patchOptions.setText(PatchMessages.PreviewPatchPage_PatchOptions_title); + patchOptions.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT)); + patchOptions.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 3, 1)); + patchOptions.addExpansionListener(new ExpansionAdapter() { + public void expansionStateChanged(ExpansionEvent e) { + form.layout(); + } + }); + + Composite c = new Composite(patchOptions, SWT.NONE); + patchOptions.setClient(c); + patchOptions.setExpanded(true); + GridLayout gl= new GridLayout(); gl.numColumns= 3; + c.setLayout(gl); + c.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL|GridData.GRAB_HORIZONTAL)); + + // 1st row + createStripSegmentCombo(c); + createShowMatchedToggle(c); + createFuzzFactorChooser(c); + + // 2nd row + createReversePatchToggle(c); + createShowRemovedToggle(c); + createGenerateRejectsToggle(c); + + // register listeners + final WorkspacePatcher patcher= getPatcher(); + if (fStripPrefixSegments!=null) + fStripPrefixSegments.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (patcher.getStripPrefixSegments() != getStripPrefixSegments()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_4)) { + if (patcher.setStripPrefixSegments(getStripPrefixSegments())) + rebuildTree(); + } + } + } + } + ); + + + fFuzzField.addModifyListener( + new ModifyListener() { + public void modifyText(ModifyEvent e) { + if (patcher.getFuzz() != getFuzzFactor()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_5)) { + if (patcher.setFuzz(getFuzzFactor())) + rebuildTree(); + } else { + fFuzzField.setText(Integer.toString(patcher.getFuzz())); + } + } + } + }); + } + + private void createFuzzFactorChooser(Composite parent) { + final WorkspacePatcher patcher= getPatcher(); + Composite pair= new Composite(parent, SWT.NONE); + GridLayout gl= new GridLayout(); gl.numColumns= 3; gl.marginHeight= gl.marginWidth= 0; + pair.setLayout(gl); + GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL); + pair.setLayoutData(gd); + + Label l= new Label(pair, SWT.NONE); + l.setText(PatchMessages.PreviewPatchPage_FuzzFactor_text); + l.setToolTipText(PatchMessages.PreviewPatchPage_FuzzFactor_tooltip); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER|GridData.HORIZONTAL_ALIGN_BEGINNING|GridData.GRAB_HORIZONTAL); + l.setLayoutData(gd); + + fFuzzField= new Text(pair, SWT.BORDER); + fFuzzField.setText("0"); //$NON-NLS-1$ + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER | GridData.HORIZONTAL_ALIGN_END); + gd.widthHint= 30; + fFuzzField.setLayoutData(gd); + + Button b= new Button(pair, SWT.PUSH); + b.setText(PatchMessages.PreviewPatchPage_GuessFuzz_text); + b.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_6)) { + // Reset the fuzz. We don't use HunkResult.MAXIMUM_FUZZ_FACTOR on purpose here, + // in order to refresh the tree the result of the calculation needs to be different + // than the fuzz set in the configuration (see fFuzzField modify listener). + patcher.setFuzz(-1); + int fuzz= guessFuzzFactor(patcher); + if (fuzz>=0) + fFuzzField.setText(Integer.toString(fuzz)); + } + } + } + ); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER); + int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); + Point minSize = b.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + gd.widthHint = Math.max(widthHint, minSize.x); + b.setLayoutData(gd); + } + + private void createGenerateRejectsToggle(Composite pair) { + generateRejects = new Button(pair, SWT.CHECK); + generateRejects.setText(PatchMessages.HunkMergePage_GenerateRejectFile); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + generateRejects.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getPatcher().setGenerateRejectFile( + generateRejects.getSelection()); + } + }); + generateRejects.setSelection(false); + generateRejects.setLayoutData(gd); + } + + private void createShowRemovedToggle(Composite pair) { + final Button showRemoved = new Button(pair, SWT.CHECK); + showRemoved.setText(PatchMessages.PreviewPatchPage2_7); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + showRemoved.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + fInput.setShowAll(showRemoved.getSelection()); + fInput.updateTree(); + } + }); + showRemoved.setSelection(fInput.isShowAll()); + showRemoved.setLayoutData(gd); + } + + private void createReversePatchToggle(Composite pair) { + final Button reversePatch = new Button(pair, SWT.CHECK); + reversePatch.setText(PatchMessages.PreviewPatchPage_ReversePatch_text); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + reversePatch.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (fReversePatch != null) { + fReversePatch.setChecked(reversePatch.getSelection()); + fReversePatch.run(); + if (fReversePatch.isChecked() != reversePatch.getSelection()) { + reversePatch.setSelection(fReversePatch.isChecked()); + } + } + } + }); + reversePatch.setSelection(getPatcher().isReversed()); + reversePatch.setLayoutData(gd); + } + + private void createStripSegmentCombo(Composite parent) { + final WorkspacePatcher patcher= getPatcher(); + + Composite pair= new Composite(parent, SWT.NONE); + GridLayout gl= new GridLayout(); gl.numColumns= 2; gl.marginHeight= gl.marginWidth= 0; + pair.setLayout(gl); + GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL); + pair.setLayoutData(gd); + + Label l= new Label(pair, SWT.NONE); + l.setText(PatchMessages.PreviewPatchPage_IgnoreSegments_text); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER|GridData.HORIZONTAL_ALIGN_BEGINNING); + l.setLayoutData(gd); + + fStripPrefixSegments= new Combo(pair, SWT.DROP_DOWN|SWT.READ_ONLY|SWT.SIMPLE); + int prefixCnt= patcher.getStripPrefixSegments(); + String prefix= Integer.toString(prefixCnt); + fStripPrefixSegments.add(prefix); + fStripPrefixSegments.setText(prefix); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER|GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.GRAB_HORIZONTAL); + fStripPrefixSegments.setLayoutData(gd); + } + + private void createShowMatchedToggle(Composite parent) { + final Button showMatched = new Button(parent, SWT.CHECK); + showMatched.setText(PatchMessages.PreviewPatchPage2_ShowMatched); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + showMatched.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + fInput.setShowMatched(showMatched.getSelection()); + rebuildTree(); + } + }); + showMatched.setSelection(fInput.isShowMatched()); + showMatched.setLayoutData(gd); + } + + public int getFuzzFactor() { + int fuzzFactor= 0; + if (fFuzzField!=null) { + String s= fFuzzField.getText(); + try { + fuzzFactor= Integer.parseInt(s); + } catch (NumberFormatException ex) { + // silently ignored + } + } + return fuzzFactor; + } + + public int getStripPrefixSegments() { + int stripPrefixSegments= 0; + if (fStripPrefixSegments!=null) { + String s= fStripPrefixSegments.getText(); + try { + stripPrefixSegments= Integer.parseInt(s); + } catch (NumberFormatException ex) { + // silently ignored + } + } + return stripPrefixSegments; + } + + private int guessFuzzFactor(final WorkspacePatcher patcher) { + final int[] result= new int[] { -1 }; + try { + PlatformUI.getWorkbench().getProgressService().run(true, true, + new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) { + result[0]= patcher.guessFuzzFactor(monitor); + } + } + ); + } catch (InvocationTargetException ex) { + // NeedWork + } catch (InterruptedException ex) { + // NeedWork + } + return result[0]; + } + + public void ensureContentsSaved() { + try { + fInput.saveChanges(new NullProgressMonitor()); + } catch (CoreException e) { + //ignore + } + } + + public WorkspacePatcher getPatcher() { + return fPatcher; + } + + public CompareConfiguration getCompareConfiguration() { + return fConfiguration; + } + + private void restoreWidgetValues() { + IDialogSettings dialogSettings = CompareUI.getPlugin().getDialogSettings(); + settings = dialogSettings.getSection(PREVIEWPATCHPAGE_NAME); + if (settings == null) { + settings = dialogSettings.addNewSection(PREVIEWPATCHPAGE_NAME); + } + if (settings != null) { + if (settings.get(EXPAND_PATCH_OPTIONS) != null) + patchOptions.setExpanded(settings.getBoolean(EXPAND_PATCH_OPTIONS)); + if (settings.get(GENERATE_REJECTS) != null) { + generateRejects.setSelection(settings.getBoolean(GENERATE_REJECTS)); + getPatcher().setGenerateRejectFile(generateRejects.getSelection()); + } + } + } + + void saveWidgetValues() { + settings.put(EXPAND_PATCH_OPTIONS, patchOptions.isExpanded()); + settings.put(GENERATE_REJECTS, generateRejects.getSelection()); + } + + private String countLines() { + int added = 0, removed = 0; + + IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore(); + String addedLinesRegex = store.getString(ComparePreferencePage.ADDED_LINES_REGEX); + String removedLinesRegex = store.getString(ComparePreferencePage.REMOVED_LINES_REGEX); + + if ((addedLinesRegex == null || "".equals(addedLinesRegex)) //$NON-NLS-1$ + && (removedLinesRegex == null || "".equals(removedLinesRegex))) { //$NON-NLS-1$ + + fPatcher.countLines(); + FilePatch2[] fileDiffs = fPatcher.getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + added += fileDiffs[i].getAddedLines(); + removed += fileDiffs[i].getRemovedLines(); + } + + } else { + + Pattern addedPattern = Pattern.compile(addedLinesRegex); + Pattern removedPattern = Pattern.compile(removedLinesRegex); + + FilePatch2[] fileDiffs = fPatcher.getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + IHunk[] hunks = fileDiffs[i].getHunks(); + for (int j = 0; j < hunks.length; j++) { + String[] lines = ((Hunk) hunks[j]).getLines(); + for (int k = 0; k < lines.length; k++) { + String line = lines[k]; + if (addedPattern.matcher(line).find()) + added++; + if (removedPattern.matcher(line).find()) + removed++; + } + } + } + } + + return NLS.bind(PatchMessages.PreviewPatchPage2_AddedRemovedLines, + new String[] { added + "", removed + "" }); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void dispose() { + fToolkit.dispose(); + super.dispose(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java new file mode 100644 index 000000000..3efc37027 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.internal.patch; + +import java.util.ArrayList; + +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.*; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; +import org.eclipse.ui.model.BaseWorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; + +class RetargetPatchElementDialog extends Dialog { + + private static class RetargetPatchContentProvider extends BaseWorkbenchContentProvider { + private final PatchDiffNode node; + public RetargetPatchContentProvider(PatchDiffNode node) { + this.node = node; + } + public Object[] getChildren(Object element) { + if (element instanceof IWorkspaceRoot) { + // Don't show closed projects + IProject[] allProjects= ((IWorkspaceRoot) element).getProjects(); + ArrayList accessibleProjects= new ArrayList(); + for (int i= 0; i<allProjects.length; i++) { + if (allProjects[i].isOpen()) { + accessibleProjects.add(allProjects[i]); + } + } + return accessibleProjects.toArray(); + } + // When retargeting a diff project, don't support expansion + if (element instanceof IProject && node instanceof PatchProjectDiffNode) { + return new Object[0]; + } + return super.getChildren(element); + } + } + + private final PatchDiffNode fSelectedNode; + private final WorkspacePatcher fPatcher; + private TreeViewer fViewer; + private IResource fSelectedResource; + + public RetargetPatchElementDialog(Shell shell, WorkspacePatcher patcher, PatchDiffNode node) { + super(shell); + Assert.isNotNull(patcher); + Assert.isNotNull(node); + setShellStyle(getShellStyle() | SWT.RESIZE); + this.fPatcher = patcher; + fSelectedNode= node; + } + + protected Control createButtonBar(Composite parent) { + Control control = super.createButtonBar(parent); + Button okButton = this.getButton(IDialogConstants.OK_ID); + okButton.setEnabled(false); + return control; + } + + protected Control createDialogArea(Composite parent) { + Composite composite= (Composite) super.createDialogArea(parent); + + initializeDialogUnits(parent); + + getShell().setText(PatchMessages.PreviewPatchPage_RetargetPatch); + + GridLayout layout= new GridLayout(); + layout.numColumns= 1; + layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); + layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); + composite.setLayout(layout); + final GridData data= new GridData(SWT.FILL, SWT.FILL, true, true); + composite.setLayoutData(data); + + //add controls to composite as necessary + Label label= new Label(composite, SWT.LEFT|SWT.WRAP); + label.setText(getTreeLabel()); + final GridData data2= new GridData(SWT.FILL, SWT.BEGINNING, true, false); + label.setLayoutData(data2); + + fViewer= new TreeViewer(composite, SWT.BORDER); + GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true); + gd.widthHint= 0; + gd.heightHint= 0; + fViewer.getTree().setLayoutData(gd); + fViewer.setContentProvider(new RetargetPatchContentProvider(fSelectedNode)); + fViewer.setLabelProvider(new WorkbenchLabelProvider()); + fViewer.setComparator(new ResourceComparator(ResourceComparator.NAME)); + fViewer.setInput(getViewerInput()); + IResource resource = getInitialSelection(); + if (resource != null) { + fViewer.setSelection(new StructuredSelection(resource)); + fViewer.expandToLevel(resource, 0); + } + setupListeners(); + + Dialog.applyDialogFont(composite); + return parent; + } + + private IResource getViewerInput() { + if (fPatcher.isWorkspacePatch()) + return ResourcesPlugin.getWorkspace().getRoot(); + return fPatcher.getTarget(); + } + + private IResource getInitialSelection() { + if (fSelectedNode instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) fSelectedNode; + return fPatcher.getTargetFile(node.getDiffResult().getDiff()); + } else if (fSelectedNode instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) fSelectedNode; + return fPatcher.getTargetFile(node.getHunkResult().getDiffResult().getDiff()); + } else if (fSelectedNode instanceof PatchProjectDiffNode) { + PatchProjectDiffNode node = (PatchProjectDiffNode) fSelectedNode; + DiffProject diffProject = node.getDiffProject(); + return Utilities.getProject(diffProject); + } + return null; + } + + private String getTreeLabel() { + if (fSelectedNode instanceof PatchProjectDiffNode) { + PatchProjectDiffNode node = (PatchProjectDiffNode) fSelectedNode; + DiffProject project = node.getDiffProject(); + return NLS.bind(PatchMessages.PreviewPatchPage_SelectProject, project.getName()); + } else if (fSelectedNode instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) fSelectedNode; + //copy over all hunks to new target resource + FilePatch2 diff = node.getDiffResult().getDiff(); + return NLS.bind(PatchMessages.RetargetPatchElementDialog_0, fPatcher.getPath(diff)); + } else if (fSelectedNode instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) fSelectedNode; + Hunk hunk = node.getHunkResult().getHunk(); + return NLS.bind(PatchMessages.RetargetPatchElementDialog_1, fPatcher.getPath(hunk.getParent())); + } + return ""; //$NON-NLS-1$ + } + + protected void okPressed() { + if (fSelectedResource != null){ + if (fSelectedNode instanceof PatchProjectDiffNode && fSelectedResource instanceof IProject) { + PatchProjectDiffNode node = (PatchProjectDiffNode) fSelectedNode; + DiffProject project = node.getDiffProject(); + fPatcher.retargetProject(project, (IProject)fSelectedResource); + } else if (fSelectedNode instanceof PatchFileDiffNode && fSelectedResource instanceof IFile) { + PatchFileDiffNode node = (PatchFileDiffNode) fSelectedNode; + //copy over all hunks to new target resource + FilePatch2 diff = node.getDiffResult().getDiff(); + fPatcher.retargetDiff(diff, (IFile)fSelectedResource); + } else if (fSelectedNode instanceof HunkDiffNode && fSelectedResource instanceof IFile) { + HunkDiffNode node = (HunkDiffNode) fSelectedNode; + fPatcher.retargetHunk(node.getHunkResult().getHunk(), (IFile)fSelectedResource); + } + } + super.okPressed(); + } + + void setupListeners() { + fViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection s= (IStructuredSelection) event.getSelection(); + Object obj= s.getFirstElement(); + if (obj instanceof IResource){ + fSelectedResource = (IResource) obj; + if (fSelectedNode instanceof PatchProjectDiffNode) { + if (fSelectedResource instanceof IProject){ + Button okButton = getButton(IDialogConstants.OK_ID); + okButton.setEnabled(true); + } + } else if (fSelectedNode instanceof PatchFileDiffNode + || fSelectedNode instanceof HunkDiffNode) { + if (fSelectedResource instanceof IFile){ + Button okButton = getButton(IDialogConstants.OK_ID); + okButton.setEnabled(true); + } + } + } + } + }); + + fViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ISelection s= event.getSelection(); + if (s instanceof IStructuredSelection) { + Object item= ((IStructuredSelection) s).getFirstElement(); + if (fViewer.getExpandedState(item)) + fViewer.collapseToLevel(item, 1); + else + fViewer.expandToLevel(item, 1); + } + } + }); + + } + + protected Point getInitialSize() { + final Point size= super.getInitialSize(); + size.x= convertWidthInCharsToPixels(75); + size.y+= convertHeightInCharsToPixels(20); + return size; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java new file mode 100644 index 000000000..09eb69c83 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.internal.patch; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.compare.IEditableContent; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ContentChangeNotifier; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; + +public class UnmatchedHunkTypedElement extends HunkTypedElement implements IContentChangeNotifier, IEditableContent { + + private ContentChangeNotifier changeNotifier; + + public UnmatchedHunkTypedElement(HunkResult result) { + // An unmatched hunk element is always used for the before state and is full context + super(result, false, true); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IContentChangeNotifier#addContentChangeListener(org.eclipse.compare.IContentChangeListener) + */ + public synchronized void addContentChangeListener(IContentChangeListener listener) { + if (changeNotifier == null) + changeNotifier = new ContentChangeNotifier(this); + changeNotifier.addContentChangeListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IContentChangeNotifier#removeContentChangeListener(org.eclipse.compare.IContentChangeListener) + */ + public synchronized void removeContentChangeListener(IContentChangeListener listener) { + if (changeNotifier != null) + changeNotifier.removeContentChangeListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContent#isEditable() + */ + public boolean isEditable() { + IFile file = ((WorkspaceFileDiffResult)getHunkResult().getDiffResult()).getTargetFile(); + return file != null && file.isAccessible(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContent#replace(org.eclipse.compare.ITypedElement, org.eclipse.compare.ITypedElement) + */ + public ITypedElement replace(ITypedElement dest, ITypedElement src) { + // Not supported + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContent#setContent(byte[]) + */ + public void setContent(byte[] newContent) { + getPatcher().setManuallyMerged(getHunkResult().getHunk(), true); + getPatcher().cacheContents(getDiff(), newContent); + if (changeNotifier != null) + changeNotifier.fireContentChanged(); + } + + private FilePatch2 getDiff() { + return getHunkResult().getDiffResult().getDiff(); + } + + private Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.patch.HunkTypedElement#getContents() + */ + public InputStream getContents() throws CoreException { + // If there are cached contents, use them + if (getPatcher().hasCachedContents(getDiff())) + return new ByteArrayInputStream(getPatcher().getCachedContents(getDiff())); + // Otherwise return the after state of the diff result + List lines = getHunkResult().getDiffResult().getAfterLines(); + String content = LineReader.createString(getHunkResult().getDiffResult().isPreserveLineDelimeters(), lines); + byte[] bytes = null; + if (getCharset() != null) + try { + bytes = content.getBytes(getCharset()); + } catch (UnsupportedEncodingException e) { + CompareUIPlugin.log(e); + } + if (bytes == null) + bytes = content.getBytes(); + return new ByteArrayInputStream(bytes); + } + + private PatchConfiguration getConfiguration() { + return getHunkResult().getDiffResult().getConfiguration(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java new file mode 100644 index 000000000..17c2d88a7 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 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.internal.patch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; + +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.patch.ReaderCreator; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +public class Utilities { + + public static String getCharset(Object resource) { + if (resource instanceof IEncodedStorage) { + try { + return ((IEncodedStorage) resource).getCharset(); + } catch (CoreException ex) { + CompareUIPlugin.log(ex); + } + } + return ResourcesPlugin.getEncoding(); + } + + public static IProject getProject(DiffProject diffProject) { + return ResourcesPlugin.getWorkspace().getRoot().getProject( + diffProject.getName()); + } + + public static ReaderCreator getReaderCreator(final IStorage storage) { + return new ReaderCreator() { + public Reader createReader() throws CoreException { + return Utilities.createReader(storage); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.patch.ReaderCreator#canCreateReader() + */ + public boolean canCreateReader() { + if (storage == null + || (storage != null && storage instanceof IFile && !((IFile) storage) + .isAccessible())) { + return false; + } + return true; + } + }; + } + + public static BufferedReader createReader(IStorage storage) + throws CoreException { + if (storage == null + || (storage != null && storage instanceof IFile && !((IFile) storage) + .isAccessible())) { + throw new CoreException(new Status(IStatus.WARNING, + CompareUIPlugin.PLUGIN_ID, + CompareMessages.ReaderCreator_fileIsNotAccessible)); + } + String charset = null; + if (storage instanceof IEncodedStorage) { + IEncodedStorage es = (IEncodedStorage) storage; + charset = es.getCharset(); + } + InputStreamReader in = null; + if (charset != null) { + InputStream contents = storage.getContents(); + try { + in = new InputStreamReader(contents, charset); + } catch (UnsupportedEncodingException e) { + CompareUIPlugin.log(e); + try { + contents.close(); + } catch (IOException e1) { + // Ignore + } + } + } + if (in == null) { + in = new InputStreamReader(storage.getContents()); + } + return new BufferedReader(in); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java new file mode 100644 index 000000000..494dda42c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.internal.patch; + +import java.util.List; + +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; + +public class WorkspaceFileDiffResult extends FileDiffResult { + + public WorkspaceFileDiffResult(FilePatch2 diff, + PatchConfiguration configuration) { + super(diff, configuration); + } + + protected boolean canCreateTarget(IStorage storage) { + IProject project = getPatcher().getTargetProject(getDiff()); + return project != null && project.isAccessible(); + } + + protected boolean targetExists(IStorage storage) { + IFile file= (IFile)storage; + return file != null && file.isAccessible(); + } + + protected List getLines(IStorage storage, boolean create) { + IFile file= getTargetFile(); + List lines = LineReader.load(file, create); + return lines; + } + + protected Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + public IFile getTargetFile() { + return getPatcher().getTargetFile(getDiff()); + } + + public void refresh() { + refresh(Utilities.getReaderCreator(getTargetFile()), null); + } + + public String getCharset() { + IFile file = getTargetFile(); + try { + if (file != null) + return file.getCharset(); + } catch (CoreException e) { + } + return ResourcesPlugin.getEncoding(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java new file mode 100644 index 000000000..0699d116c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java @@ -0,0 +1,382 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.internal.patch; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.compare.internal.core.Messages; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.compare.internal.core.patch.PatchReader; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceRuleFactory; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * A Patcher + * - knows how to parse various patch file formats into some in-memory structure, + * - holds onto the parsed data and the options to use when applying the patches, + * - knows how to apply the patches to files and folders. + */ +public class WorkspacePatcher extends Patcher { + + private DiffProject[] fDiffProjects; + private boolean fIsWorkspacePatch= false; + private final Map retargetedDiffs = new HashMap(); + + public WorkspacePatcher() { + // nothing to do + } + + public WorkspacePatcher(IResource target) { + setTarget(target); + } + + protected void patchParsed(PatchReader patchReader) { + super.patchParsed(patchReader); + fDiffProjects = patchReader.getDiffProjects(); + fIsWorkspacePatch = patchReader.isWorkspacePatch(); + } + + public DiffProject[] getDiffProjects() { + return fDiffProjects; + } + + public boolean isWorkspacePatch() { + return fIsWorkspacePatch; + } + + //---- parsing patch files + + public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException { + if (!fIsWorkspacePatch) { + super.applyAll(pm, validator); + } else { + final int WORK_UNIT= 10; + + // get all files to be modified in order to call validateEdit + List list= new ArrayList(); + for (int j= 0; j < fDiffProjects.length; j++) { + DiffProject diffProject= fDiffProjects[j]; + if (Utilities.getProject(diffProject).isAccessible()) + list.addAll(Arrays.asList(getTargetFiles(diffProject))); + } + // validate the files for editing + if (!validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { + return; + } + + FilePatch2[] diffs = getDiffs(); + if (pm != null) { + String message= Messages.WorkspacePatcher_0; + pm.beginTask(message, diffs.length * WORK_UNIT); + } + + for (int i= 0; i < diffs.length; i++) { + + int workTicks= WORK_UNIT; + + FilePatch2 diff= diffs[i]; + if (isAccessible(diff)) { + IFile file= getTargetFile(diff); + IPath path= file.getProjectRelativePath(); + if (pm != null) + pm.subTask(path.toString()); + createPath(file.getProject(), path); + + List failed= new ArrayList(); + + int type= diff.getDiffType(isReversed()); + switch (type) { + case FilePatch2.ADDITION : + // patch it and collect rejected hunks + List result= apply(diff, file, true, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + case FilePatch2.DELETION : + file.delete(true, true, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + case FilePatch2.CHANGE : + // patch it and collect rejected hunks + result= apply(diff, file, false, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + } + + if (isGenerateRejectFile() && failed.size() > 0) { + IPath pp= null; + if (path.segmentCount() > 1) { + pp= path.removeLastSegments(1); + pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION); + } else + pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION); + file= createPath(file.getProject(), pp); + if (file != null) { + store(getRejected(failed), file, pm); + try { + IMarker marker= file.createMarker(MARKER_TYPE); + marker.setAttribute(IMarker.MESSAGE, Messages.WorkspacePatcher_1); + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + } catch (CoreException ex) { + // NeedWork + } + } + } + } + + if (pm != null) { + if (pm.isCanceled()) + break; + if (workTicks > 0) + pm.worked(workTicks); + } + } + } + } + + private boolean isAccessible(FilePatch2 diff) { + return isEnabled(diff) && Utilities.getProject(diff.getProject()).isAccessible(); + } + + /** + * Returns the target files of all the Diffs contained by this + * DiffProject. + * @param project + * @return An array of IFiles that are targeted by the Diffs + */ + public IFile[] getTargetFiles(DiffProject project) { + List files= new ArrayList(); + FilePatch2[] diffs = project.getFileDiffs(); + for (int i = 0; i < diffs.length; i++) { + FilePatch2 diff = diffs[i]; + if (isEnabled(diff)) { + files.add(getTargetFile(diff)); + } + } + return (IFile[]) files.toArray(new IFile[files.size()]); + } + + public IFile getTargetFile(FilePatch2 diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + DiffProject project = getProject(diff); + if (project != null) + return Utilities.getProject(project).getFile(path); + return super.getTargetFile(diff); + } + + private IPath getFullPath(FilePatch2 diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + DiffProject project = getProject(diff); + if (project != null) + return Utilities.getProject(project).getFile(path).getFullPath(); + return getTarget().getFullPath().append(path); + } + + public ISchedulingRule[] getTargetProjects() { + List projects= new ArrayList(); + IResourceRuleFactory ruleFactory= ResourcesPlugin.getWorkspace().getRuleFactory(); + // Determine the appropriate scheduling rules + for (int i= 0; i < fDiffProjects.length; i++) { + IProject tempProject= Utilities.getProject(fDiffProjects[i]); + // The goal here is to lock as little of the workspace as necessary + // but still allow the patcher to obtain the locks it needs. + // As such, we need to get the modify rules from the rule factory for the .project file. A pessimistic + // rule factory will return the root, while others might return just the project. Combining + // this rule with the project will result in the smallest possible locking set. + ISchedulingRule scheduleRule= ruleFactory.modifyRule(tempProject.getFile(IProjectDescription.DESCRIPTION_FILE_NAME)); + MultiRule multiRule= new MultiRule(new ISchedulingRule[] { scheduleRule, tempProject } ); + projects.add(multiRule); + } + + return (ISchedulingRule[]) projects.toArray(new ISchedulingRule[projects.size()]); + } + + public void setDiffProjects(DiffProject[] newProjectArray) { + fDiffProjects = new DiffProject[newProjectArray.length]; + System.arraycopy(newProjectArray,0, fDiffProjects, 0, newProjectArray.length); + } + + public void removeProject(DiffProject project) { + DiffProject[] temp = new DiffProject[fDiffProjects.length - 1]; + int counter = 0; + for (int i = 0; i < fDiffProjects.length; i++) { + if (fDiffProjects[i] != project){ + temp[counter++] = fDiffProjects[i]; + } + } + fDiffProjects = temp; + } + + protected Object getElementParent(Object element) { + if (element instanceof FilePatch2 && fDiffProjects != null) { + FilePatch2 diff = (FilePatch2) element; + for (int i = 0; i < fDiffProjects.length; i++) { + DiffProject project = fDiffProjects[i]; + if (project.contains(diff)) + return project; + } + } + return null; + } + + public boolean isRetargeted(Object object) { + return retargetedDiffs.containsKey(object); + } + + public IPath getOriginalPath(Object object) { + return (IPath)retargetedDiffs.get(object); + } + + public void retargetDiff(FilePatch2 diff, IFile file) { + retargetedDiffs.put(diff, diff.getPath(false)); + IHunk[] hunks = diff.getHunks(); + + if (isWorkspacePatch()){ + //since the diff has no more hunks to apply, remove it from the parent and the patcher + diff.getProject().remove(diff); + } + removeDiff(diff); + FilePatch2 newDiff = getDiffForFile(file); + for (int i = 0; i < hunks.length; i++) { + Hunk hunk = (Hunk) hunks[i]; + newDiff.add(hunk); + } + } + + private FilePatch2 getDiffForFile(IFile file) { + DiffProject diffProject = null; + FilePatch2[] diffsToCheck; + if (isWorkspacePatch()){ + // Check if the diff project already exists for the file + IProject project = file.getProject(); + DiffProject[] diffProjects = getDiffProjects(); + for (int i = 0; i < diffProjects.length; i++) { + if (Utilities.getProject(diffProjects[i]).equals(project)){ + diffProject = diffProjects[i]; + break; + } + } + // If the project doesn't exist yet, create it and add it to the project list + if (diffProject == null){ + diffProject = addDiffProjectForProject(project); + } + diffsToCheck = diffProject.getFileDiffs(); + } else { + diffsToCheck = getDiffs(); + } + // Check to see if a diff already exists for the file + for (int i = 0; i < diffsToCheck.length; i++) { + FilePatch2 fileDiff = diffsToCheck[i]; + if (isDiffForFile(fileDiff, file)) { + return fileDiff; + } + } + + // Create a new diff for the file + IPath path = getDiffPath(file); + FilePatch2 newDiff = new FilePatch2(path, 0, path, 0); + if (diffProject != null){ + diffProject.add(newDiff); + } + addDiff(newDiff); + return newDiff; + } + + private IPath getDiffPath(IFile file) { + DiffProject project = getDiffProject(file.getProject()); + if (project != null) { + return file.getProjectRelativePath(); + } + return file.getFullPath().removeFirstSegments(getTarget().getFullPath().segmentCount()); + } + + private boolean isDiffForFile(FilePatch2 fileDiff, IFile file) { + return getFullPath(fileDiff).equals(file.getFullPath()); + } + + private DiffProject addDiffProjectForProject(IProject project) { + DiffProject[] diffProjects = getDiffProjects(); + DiffProject diffProject = new DiffProject(project.getName()); + DiffProject[] newProjectArray = new DiffProject[diffProjects.length + 1]; + System.arraycopy(diffProjects, 0, newProjectArray, 0, diffProjects.length); + newProjectArray[diffProjects.length] = diffProject; + setDiffProjects(newProjectArray); + return diffProject; + } + + public void retargetHunk(Hunk hunk, IFile file) { + FilePatch2 newDiff = getDiffForFile(file); + newDiff.add(hunk); + } + + public void retargetProject(DiffProject project, IProject targetProject) { + retargetedDiffs.put(project, Utilities.getProject(project).getFullPath()); + FilePatch2[] diffs = project.getFileDiffs(); + DiffProject selectedProject = getDiffProject(targetProject); + if (selectedProject == null) + selectedProject = addDiffProjectForProject(targetProject); + // Copy over the diffs to the new project + for (int i = 0; i < diffs.length; i++) { + selectedProject.add(diffs[i]); + } + // Since the project has been retargeted, remove it from the patcher + removeProject(project); + } + + /** + * Return the diff project for the given project + * or <code>null</code> if the diff project doesn't exist + * or if the patch is not a workspace patch. + * @param project the project + * @return the diff project for the given project + * or <code>null</code> + */ + private DiffProject getDiffProject(IProject project) { + if (!isWorkspacePatch()) + return null; + DiffProject[] projects = getDiffProjects(); + for (int i = 0; i < projects.length; i++) { + if (Utilities.getProject(projects[i]).equals(project)) + return projects[i]; + } + return null; + } + + public int getStripPrefixSegments() { + // Segments are never stripped from a workspace patch + if (isWorkspacePatch()) + return 0; + return super.getStripPrefixSegments(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/package.html b/bundles/org.eclipse.compare/compare/org/eclipse/compare/package.html new file mode 100644 index 000000000..96ffb6342 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/package.html @@ -0,0 +1,113 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.75 [en] (WinNT; U) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides support for performing structural and textual +compare operations on arbitrary data and displaying the results. +<h2> +Package Specification</h2> + +The class <b>CompareUI</b> defines the entry point to initiate a configurable +compare operation on arbitrary resources. The result of the compare is +opened into a compare editor where the details can be browsed and edited +in dynamically selected structure and content viewers. +<p> + +A compare operation must be implemented as a subclass of <b>CompareEditorInput</b>. +A <b>CompareEditorInput</b> runs a (potentially lengthy) compare operation +under progress monitor control, creates a UI for drilling-down into the +compare results, tracks the dirty state of the result in case of merge, +and saves any changes that occured during a merge. +<p> + +The <b>NavigationAction</b> is used to navigate (step) through the individual +differences of a <b>CompareEditorInput</b>. +<p> + +An instance of <b>CompareConfiguration</b> configures various UI aspects +of compare/merge viewers like title labels and images, or whether a side +of a merge viewer is editable. It is passed to the <b>CompareEditorInput</b> +on creation. +<p> + +When implementing a hierarchical compare operation as a subclass of +<b>CompareEditorInput</b> clients have to provide a tree of objects where each +node implements the interface +<b>org.eclipse.compare.structuremergeviewer.IStructureComparator</b>. +This interface is used by the hierarchical differencing engine +(<b>org.eclipse.compare.structuremergeviewer.Differencer</b>) to walk the tree. +<br> +In addition every leaf of the tree must implement the <b>IStreamContentAccessor</b> +or <b>IEncodedStreamContentAccessor</b> +interfaces in order to give the differencing engine access to its stream content +and to its encoding (with IEncodedStreamContentAccessor). +<p> + +The abstract class <b>BufferedContent</b> provides a default implementation +for the <b>IStreamContentAccessor</b> and <b>IContentChangeNotifier</b> interfaces. +Its subclass <b>ResourceNode</b> adds an implementation for the +<b>IStructureComparator</b> and <b>ITypedElement</b> interfaces +based on Eclipse workbench resources (org.eclipse.core.resources.IResource). +It can be used without modification as the input to the differencing engine. +<p> + +The <b>ZipFileStructureCreator</b> is an implementation of the +<b>org.eclipse.compare.structuremergeviewer.IStructureCreator</b> interface +and makes the contents of a zip archive available as a +hierarchical structure of <b>IStructureComparator</b>s which can be easily compared +by the differencing engine (<b>org.eclipse.compare.structuremergeviewer.Differencer</b>). +It is a good example for how to make structured files available to the hierarchical +compare functionality of the Compare plugin. +<p> + +The <b>EditionSelectionDialog</b> is a simple selection dialog where +one input element can be compared against a list of historic variants (<i>editions</i>) +of the same input element. The dialog can be used to implement functions +like <i>"Replace with Version"</i> or <i>"Replace with Edition"</i> on workbench resources. +<p> + +In addition it is possible to specify a subsection of the input element +(e.g. a method in a Java source file) by means of a <i>path</i>. In this +case the dialog compares only the subsection (as specified by the path) +with the corresponding subsection in the list of editions. This functionality +can be used to implement <i>"Replace with Method Edition"</i> for the Java +language. +<p> + +The <b>EditionSelectionDialog</b> requires that the editions implement +the <b>IStreamContentAccessor</b> and <b>IModificationDate</b> interfaces. +The <b>HistoryItem</b> is a convenience class that implements these interfaces +for <b>IFileState</b> objects. +<p> + + +The <b>CompareViewerPane</b> is a convenience class which provides +a label and local toolbar for a compare viewer (or any other subclass of a +JFace <b>Viewer</b>). +<br> +Its abstract subclass <b>CompareViewerSwitchingPane</b> supports <i>dynamic +viewer switching</i>, that is the viewer installed in the pane is dynamically +determined by the pane's input object. +Both classes are useful if you want to use compare viewers outside the context of +a compare editor, for example in a dialog or wizard. +<p> + +A <b>Splitter</b> is an extension of a SashForm that supports nesting, maximizing of panes, +and propagating the visibility state of panes. +<p> + +The interface <b>IStreamMerger</b> defines a single operation for performing a three-way merge on three +input streams. The merged result is written to an output stream. +<br> +Clients must implement this interface when contributing new mergers to the +<code>org.eclipse.compare.streamMergers</code> extension point. +New <b>IStreamMerger</b>s can be created for registered types with the createStreamMerger methods of CompareUI. + + +</body> +</html> diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/ApplyPatchOperation.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/ApplyPatchOperation.java new file mode 100644 index 000000000..3cc73973c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/ApplyPatchOperation.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.patch; + +import java.io.BufferedReader; +import java.io.IOException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.PatchReader; +import org.eclipse.compare.internal.patch.FilePatch; +import org.eclipse.compare.internal.patch.PatchWizard; +import org.eclipse.compare.internal.patch.PatchWizardDialog; +import org.eclipse.compare.internal.patch.Utilities; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.ide.IDE; + +/** + * An operation that provides an interface to the Apply Patch Wizard. Users specify + * the input in terms of an <code>IStorage</code> (note: input must be in unified diff + * format), an <code>IResource</code> target to apply the patch to and can provide <code>CompareConfiguration</code> + * elements to supply the label and images used on the preview page and hunk merge page. Finally, the + * user can also supply a title and image to override the default ones provided by the Apply Patch Wizard. + * Note that the Apply Patch Wizard does not require any particular set of inputs, and in the absence of + * any user supplied values, it will work in default mode. + * + * @since 3.3 + * + */ +public class ApplyPatchOperation implements Runnable { + + private IWorkbenchPart part; + + /** + * Used for the Preview Patch page. + */ + private CompareConfiguration configuration; + + /** + * The patch to use as an input into the Apply Patch wizard + */ + private IStorage patch; + + /** + * Specific <code>IResource</code> target to patch. + */ + private IResource target; + + /** + * An optional image for the patch wizard + */ + private ImageDescriptor patchWizardImage; + + + /** + * An optional title for the patchWizard + */ + private String patchWizardTitle; + + private boolean saveAllEditors = true; + + /** + * Return whether the given storage contains a patch. + * @param storage the storage + * @return whether the given storage contains a patch + * @throws CoreException if an error occurs reading the contents from the storage + */ + public static boolean isPatch(IStorage storage) throws CoreException { + return internalParsePatch(storage).length > 0; + } + + /** + * Parse the given patch and return the set of file patches that it contains. + * @param storage the storage that contains the patch + * @return the set of file patches that the storage contains + * @throws CoreException if an error occurs reading the contents from the storage + */ + public static IFilePatch[] parsePatch(IStorage storage) throws CoreException { + return internalParsePatch(storage); + } + + /** + * Creates a new ApplyPatchOperation with the supplied compare configuration, patch and target. + * The behaviour of the Apply Patch wizard is controlled by the number of parameters supplied: + * <ul> + * <li>If a patch is supplied, the initial input page is skipped. If a patch is not supplied the wizard + * will open on the input page.</li> + * <li>If the patch is a workspace patch, the target selection page is skipped and the preview page is + * displayed.</li> + * <li>If the patch is not a workspace patch and the target is specified, the target page is still + * shown with the target selected.</li> + * </ul> + * + * @param part an IWorkbenchPart or <code>null</code> + * @param patch an IStorage containing a patch in unified diff format or <code>null</code> + * @param target an IResource which the patch is to be applied to or <code>null</code> + * @param configuration a CompareConfiguration supplying the labels and images for the preview patch page + */ + public ApplyPatchOperation(IWorkbenchPart part, IStorage patch, IResource target, CompareConfiguration configuration) { + Assert.isNotNull(configuration); + this.part = part; + this.patch = patch; + this.target = target; + this.configuration = configuration; + } + + /** + * Create an operation for the given part and resource. This method is a convenience + * method that calls {@link #ApplyPatchOperation(IWorkbenchPart, IStorage, IResource, CompareConfiguration)} + * with appropriate defaults for the other parameters. + * @param targetPart an IResource which the patch is to be applied to or <code>null</code> + * @param resource an IResource which the patch is to be applied to or <code>null</code> + * @see #ApplyPatchOperation(IWorkbenchPart, IStorage, IResource, CompareConfiguration) + */ + public ApplyPatchOperation(IWorkbenchPart targetPart, IResource resource) { + this(targetPart, null, resource, new CompareConfiguration()); + } + + /** + * Open the Apply Patch wizard using the values associated with this operation. + * This method must be called from the UI thread. + */ + public void openWizard() { + saveAllEditors(); + + if (saveAllEditors) { + PatchWizard wizard = new PatchWizard(patch, target, configuration); + if (patchWizardImage != null) + wizard.setDefaultPageImageDescriptor(patchWizardImage); + if (patchWizardTitle != null) + wizard.setWindowTitle(patchWizardTitle); + wizard.setNeedsProgressMonitor(true); + + new PatchWizardDialog(getShell(), wizard).open(); + } + } + + /** + * Return the parent shell to be used when the wizard is opened. + * By default, the site of the part is used to get the shell. + * Subclasses may override. + * @return the parent shell to be used when the wizard is opened + */ + protected Shell getShell() { + if (part == null) + return CompareUIPlugin.getShell(); + return part.getSite().getShell(); + } + + /** + * This method will save all dirty editors. It will prompt the user if the Compare preference to save + * dirty editors before viewing a patch is <code>false</code>. Clients can use this or provide their own + * implementation. + */ + protected void saveAllEditors(){ + saveAllEditors = IDE.saveAllEditors(new IResource[]{ResourcesPlugin.getWorkspace().getRoot()}, !ComparePreferencePage.getSaveAllEditors()); + } + + /** + * Sets the title of the patch wizard. Needs to be set before {@link #openWizard()} is called. + * @param title a string to display in the title bar + */ + public void setPatchWizardTitle(String title){ + this.patchWizardTitle = title; + } + + /** + * Sets the image descriptor to use in the patch wizard. Needs to be set before {@link #openWizard()} is called. + * @param descriptor an image descriptor + */ + public void setPatchWizardImageDescriptor(ImageDescriptor descriptor){ + this.patchWizardImage = descriptor; + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() + */ + public void run() { + openWizard(); + } + + private static IFilePatch[] internalParsePatch(IStorage storage) + throws CoreException { + BufferedReader reader = Utilities.createReader(storage); + try { + PatchReader patchReader = new PatchReader() { + protected FilePatch2 createFileDiff(IPath oldPath, long oldDate, + IPath newPath, long newDate) { + return new FilePatch(oldPath, oldDate, newPath, + newDate); + } + }; + patchReader.parse(reader); + FilePatch2[] fileDiffs = patchReader.getAdjustedDiffs(); + + IFilePatch[] filePatch = new IFilePatch[fileDiffs.length]; + for (int i = 0; i < fileDiffs.length; i++) { + filePatch[i] = (FilePatch) fileDiffs[i]; + } + + return filePatch; + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, + CompareUIPlugin.PLUGIN_ID, 0, e.getMessage(), e)); + } finally { + try { + reader.close(); + } catch (IOException e) { // ignored + } + } + } + + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/IFilePatch.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/IFilePatch.java new file mode 100644 index 000000000..f1377392e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/IFilePatch.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 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.patch; + +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A representation of a file patch that can be applied to an input stream. + * + * @see ApplyPatchOperation#parsePatch(org.eclipse.core.resources.IStorage) + * @since 3.3 + * @noimplement This interface is not intended to be implemented by clients. + * Clients can obtain file patches by calling + * {@link ApplyPatchOperation#parsePatch(org.eclipse.core.resources.IStorage)}. + */ +public interface IFilePatch extends IFilePatch2 { + + /** + * Special constant that will be returned from get getBeforeDate() or + * getAfterDate() if the date is unknown. Equal to Midnight, Jan 1, 1970 + * GMT. + * + * @since 3.4 + */ + public static long DATE_UNKNOWN = 0; + + /** + * Return the target path for this patch. The target path may differ + * depending on whether the patch is being reversed or not. + * + * @param configuration the patch configuration + * @return the target path for this patch + * @see PatchConfiguration#isReversed() + */ + public IPath getTargetPath(PatchConfiguration configuration); + + /** + * Apply this patch to the given file contents. The result provides the + * original and patch contents and also indicates whether some portions of + * the patch (called hunks) failed to apply. + * + * @param contents the file contents + * @param configuration the patch configuration + * @param monitor a progress monitor + * @return the result of the patch application + */ + public IFilePatchResult apply(IStorage contents, + PatchConfiguration configuration, IProgressMonitor monitor); + + /** + * Return the header information of the patch or + * <code>null</code> if there was no header text. + * The header may be multi-line. + * @return the header information of the patch or + * <code>null</code> + */ + public String getHeader(); + + /** + * Returns the milliseconds time value of the before date from the patch, or + * DATE_UNKNOWN if the date is unknown. + * + * @return milliseconds time value of the before date from the patch + * @since 3.4 + */ + public long getBeforeDate(); + + /** + * Returns the milliseconds time value of the after date from the patch, or + * DATE_UNKNOWN if the date is unknown. + * + * @return milliseconds time value of the after date from the patch + * @since 3.4 + */ + public long getAfterDate(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/WorkspacePatcherUI.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/WorkspacePatcherUI.java new file mode 100644 index 000000000..d19688b08 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/WorkspacePatcherUI.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.patch; + +import org.eclipse.compare.internal.core.patch.PatchReader; +import org.eclipse.core.resources.IProject; + +/** + * Provides the headers required to create a workspace patch. + * @since 3.2 + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + */ +public class WorkspacePatcherUI { + /** + * Returns a string that must be the first line of a workspace patch (a multi-project patch + * that is understood by the Apply Patch wizard). Each project to be included in the patch + * must be prefixed by the line obtained from the <code>getWorkspacePatchProjectHeader()</code>. + * This snippet outlines how the a workspace patch is to be created: + * <pre> + * //Write out workspace patch header + * stream.println(CompareUI.getWorkspacePatchHeader()); + * for (int i=0; i<projects.length; i++){ + * //Write out project header + * stream.println(CompareUI.getWorkspacePatchProjectHeader(projects[i]); + * //Write out patches in Unified Diff format + * } + * </pre> + * @return String + * @see WorkspacePatcherUI#getWorkspacePatchProjectHeader(IProject) + * @since 3.2 + */ + public static String getWorkspacePatchHeader() { + return PatchReader.MULTIPROJECTPATCH_HEADER+" "+PatchReader.MULTIPROJECTPATCH_VERSION; //$NON-NLS-1$ + } + + /** + * Returns the project header that must appear before any patches that apply to that + * project. All patches that are encountered after this header and before the next header + * are understood to belong the the project. + * @param project project to be patched + * @return String + * @see WorkspacePatcherUI#getWorkspacePatchHeader() + * @since 3.2 + */ + public static String getWorkspacePatchProjectHeader(IProject project) { + return PatchReader.MULTIPROJECTPATCH_PROJECT+" "+ project.getName(); //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffContainer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffContainer.java new file mode 100644 index 000000000..78ca3588c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffContainer.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +import java.util.ArrayList; + +/** + * The standard implementation of a diff container element. + * <p> + * This class may be instantiated, or further subclassed. + * </p> + */ +public abstract class DiffContainer extends DiffElement implements IDiffContainer { + + private static IDiffElement[] fgEmptyArray= new IDiffElement[0]; + private ArrayList fChildren; + + /** + * Creates a new container with the specified kind under the given parent. + * + * @param parent under which the new container is added as a child or <code>null</code>. + * @param kind of difference (defined in <code>Differencer</code>). + */ + public DiffContainer(IDiffContainer parent, int kind) { + super(parent, kind); + } + + /** + * Tries to find the child with the given name. + * Returns <code>null</code> if no such child exists. + * + * @param name of the child to find + * @return the first element with a matching name + */ + public IDiffElement findChild(String name) { + Object[] children= getChildren(); + for (int i= 0; i < children.length; i++) { + IDiffElement child= (IDiffElement) children[i]; + if (name.equals(child.getName())) + return child; + } + return null; + } + + /* (non Javadoc) + * see IDiffContainer.add + */ + public void add(IDiffElement diff) { + if (fChildren == null) + fChildren= new ArrayList(); + fChildren.add(diff); + diff.setParent(this); + } + + /* + * Removes the given child from this container. + * If the container becomes empty it is removed from its container. + */ + public void removeToRoot(IDiffElement child) { + if (fChildren != null) { + fChildren.remove(child); + child.setParent(null); + if (fChildren.size() == 0) { + IDiffContainer p= getParent(); + if (p != null) + p.removeToRoot(this); + } + } + } + + /** + * Removes the given child (non-recursively) from this container. + * + * @param child to remove + */ + public void remove(IDiffElement child) { + if (fChildren != null) { + fChildren.remove(child); + child.setParent(null); + } + } + + /* (non Javadoc) + * see IDiffContainer.hasChildren + */ + public boolean hasChildren() { + return fChildren != null && fChildren.size() > 0; + } + + /* (non Javadoc) + * see IDiffContainer.getChildren + */ + public IDiffElement[] getChildren() { + if (fChildren != null) + return (IDiffElement[]) fChildren.toArray(fgEmptyArray); + return fgEmptyArray; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffElement.java new file mode 100644 index 000000000..e4c309e06 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffElement.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.compare.ITypedElement; + +/** + * An abstract base implementation of the <code>IDiffElement</code> interface. + * <p> + * Subclasses may add behavior and state, and may override <code>getImage</code> + * and <code>getType</code> to suit. + * </p> + */ +public abstract class DiffElement implements IDiffElement { + + private int fKind; + private IDiffContainer fParent; + + /** + * Creates a new <code>DiffElement</code> as a child of the given parent. + * If parent is not <code>null</code> the new element is added to the parent. + * + * @param parent the parent of this child; if not <code>null</code> this element is automatically added as a child + * @param kind the kind of change + */ + public DiffElement(IDiffContainer parent, int kind) { + fParent= parent; + fKind= kind; + if (parent != null) + parent.add(this); + } + + /** + * The <code>DiffElement</code> implementation of this <code>ITypedInput</code> + * method returns <code>null</code>. Subclasses may re-implement to provide + * an image for this element. + * @return <code>null</code>. + */ + public Image getImage() { + return null; + } + + /** + * The <code>DiffElement</code> implementation of this <code>ITypedElement</code> + * method returns <code>ITypedElement.UNKNOWN_TYPE</code>. Subclasses may + * re-implement to provide a type for this element. + * @return <code>ITypedElement.UNKNOWN_TYPE</code>. + */ + public String getType() { + return ITypedElement.UNKNOWN_TYPE; + } + + /** + * Sets the kind of difference for this element. + * + * @param kind set the kind of difference this element represents + * @see Differencer + */ + public void setKind(int kind) { + fKind= kind; + } + + /* (non Javadoc) + * see IDiffElement.getKind + */ + public int getKind() { + return fKind; + } + + /* (non Javadoc) + * see IDiffElement.getParent + */ + public IDiffContainer getParent() { + return fParent; + } + + /* (non Javadoc) + * see IDiffElement.setParent + */ + public void setParent(IDiffContainer parent) { + fParent= parent; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffNode.java new file mode 100644 index 000000000..c9ee82d77 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffNode.java @@ -0,0 +1,364 @@ +/******************************************************************************* + * 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.structuremergeviewer; + +import org.eclipse.compare.IEditableContent; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.swt.graphics.Image; + +import com.ibm.icu.text.MessageFormat; + +/** + * Diff node are used as the compare result of the differencing engine. + * Since it implements the <code>ITypedElement</code> and <code>ICompareInput</code> + * interfaces it can be used directly to display the + * compare result in a <code>DiffTreeViewer</code> and as the input to any other + * compare/merge viewer. + * <p> + * <code>DiffNode</code>s are typically created as the result of performing + * a compare with the <code>Differencer</code>. + * <p> + * Clients typically use this class as is, but may subclass if required. + * + * @see DiffTreeViewer + * @see Differencer + */ +public class DiffNode extends DiffContainer implements ICompareInput { + + private ITypedElement fAncestor; + private ITypedElement fLeft; + private ITypedElement fRight; + private boolean fDontExpand; + private ListenerList fListener; + private boolean fSwapSides; + + + /** + * Creates a new <code>DiffNode</code> and initializes with the given values. + * + * @param parent under which the new container is added as a child or <code>null</code> + * @param kind of difference (defined in <code>Differencer</code>) + * @param ancestor the common ancestor input to a compare + * @param left the left input to a compare + * @param right the right input to a compare + */ + public DiffNode(IDiffContainer parent, int kind, ITypedElement ancestor, ITypedElement left, ITypedElement right) { + this(parent, kind); + fAncestor= ancestor; + fLeft= left; + fRight= right; + } + + /** + * Creates a new <code>DiffNode</code> with diff kind <code>Differencer.CHANGE</code> + * and initializes with the given values. + * + * @param left the left input to a compare + * @param right the right input to a compare + */ + public DiffNode(ITypedElement left, ITypedElement right) { + this(null, Differencer.CHANGE, null, left, right); + } + + /** + * Creates a new <code>DiffNode</code> and initializes with the given values. + * + * @param kind of difference (defined in <code>Differencer</code>) + * @param ancestor the common ancestor input to a compare + * @param left the left input to a compare + * @param right the right input to a compare + */ + public DiffNode(int kind, ITypedElement ancestor, ITypedElement left, ITypedElement right) { + this(null, kind, ancestor, left, right); + } + + /** + * Creates a new <code>DiffNode</code> with the given diff kind. + * + * @param kind of difference (defined in <code>Differencer</code>) + */ + public DiffNode(int kind) { + super(null, kind); + } + + /** + * Creates a new <code>DiffNode</code> and initializes with the given values. + * + * @param parent under which the new container is added as a child or <code>null</code> + * @param kind of difference (defined in <code>Differencer</code>) + */ + public DiffNode(IDiffContainer parent, int kind) { + super(parent, kind); + } + + /** + * Registers a listener for changes of this <code>ICompareInput</code>. + * Has no effect if an identical listener is already registered. + * + * @param listener the listener to add + */ + public void addCompareInputChangeListener(ICompareInputChangeListener listener) { + if (fListener == null) + fListener= new ListenerList(); + fListener.add(listener); + } + + /** + * Unregisters a <code>ICompareInput</code> listener. + * Has no effect if listener is not registered. + * + * @param listener the listener to remove + */ + public void removeCompareInputChangeListener(ICompareInputChangeListener listener) { + if (fListener != null) { + fListener.remove(listener); + if (fListener.isEmpty()) + fListener= null; + } + } + + /** + * Sends out notification that a change has occurred on the <code>ICompareInput</code>. + */ + protected void fireChange() { + if (fListener != null) { + Object[] listeners= fListener.getListeners(); + for (int i= 0; i < listeners.length; i++) + ((ICompareInputChangeListener) listeners[i]).compareInputChanged(this); + } + } + + //---- getters & setters + + /** + * Returns <code>true</code> if this node shouldn't automatically be expanded in + * a </code>DiffTreeViewer</code>. + * + * @return <code>true</code> if node shouldn't automatically be expanded + */ + public boolean dontExpand() { + return fDontExpand; + } + + /** + * Controls whether this node is not automatically expanded when displayed in + * a </code>DiffTreeViewer</code>. + * + * @param dontExpand if <code>true</code> this node is not automatically expanded in </code>DiffTreeViewer</code> + */ + public void setDontExpand(boolean dontExpand) { + fDontExpand= dontExpand; + } + + /** + * Returns the first not-<code>null</code> input of this node. + * Method checks the three inputs in the order: ancestor, right, left. + * + * @return the first not-<code>null</code> input of this node + */ + public ITypedElement getId() { + if (fAncestor != null) + return fAncestor; + if (fRight != null) + return fRight; + return fLeft; + } + + /** + * Returns the (non-<code>null</code>) name of the left or right side if they are identical. + * Otherwise both names are concatenated (separated with a slash ('/')). + * <p> + * Subclasses may re-implement to provide a different name for this node. + * @return the name of this node. + */ + public String getName() { + String right= null; + if (fRight != null) + right= fRight.getName(); + + String left= null; + if (fLeft != null) + left= fLeft.getName(); + + if (right == null && left == null) { + if (fAncestor != null) + return fAncestor.getName(); + return Utilities.getString("DiffNode.noName"); //$NON-NLS-1$ + } + + if (right == null) + return left; + if (left == null) + return right; + + if (right.equals(left)) + return right; + + String s1; + String s2; + + if (fSwapSides) { + s1= left; + s2= right; + } else { + s1= right; + s2= left; + } + + String fmt= Utilities.getString("DiffNode.nameFormat"); //$NON-NLS-1$ + return MessageFormat.format(fmt, new String[] { s1, s2 }); + } + + void swapSides(boolean swap) { + fSwapSides= swap; + } + + /* (non Javadoc) + * see ITypedElement.getImage + */ + public Image getImage() { + ITypedElement id= getId(); + if (id != null) + return id.getImage(); + return null; + } + + /* (non Javadoc) + * see ITypedElement.getType + */ + public String getType() { + ITypedElement id= getId(); + if (id != null) + return id.getType(); + return ITypedElement.UNKNOWN_TYPE; + } + + /** + * Sets the ancestor input to the given value. + * + * @param ancestor the new value for the ancestor input + * @since 3.0 + */ + public void setAncestor(ITypedElement ancestor) { + fAncestor= ancestor; + } + + /* (non Javadoc) + * see ICompareInput.getAncestor + */ + public ITypedElement getAncestor() { + return fAncestor; + } + + /** + * Sets the left input to the given value. + * + * @param left the new value for the left input + */ + public void setLeft(ITypedElement left) { + fLeft= left; + } + + /* (non Javadoc) + * see ICompareInput.getLeft + */ + public ITypedElement getLeft() { + return fLeft; + } + + /** + * Sets the right input to the given value. + * + * @param right the new value for the right input + */ + public void setRight(ITypedElement right) { + fRight= right; + } + + /* (non Javadoc) + * see ICompareInput.getRight + */ + public ITypedElement getRight() { + return fRight; + } + + /* (non Javadoc) + * see ICompareInput.copy + */ + public void copy(boolean leftToRight) { + //System.out.println("DiffNode.copy: " + leftToRight); + + IDiffContainer pa= getParent(); + if (pa instanceof ICompareInput) { + ICompareInput parent= (ICompareInput) pa; + Object dstParent= leftToRight ? parent.getRight() : parent.getLeft(); + + if (dstParent instanceof IEditableContent) { + ITypedElement dst= leftToRight ? getRight() : getLeft(); + ITypedElement src= leftToRight ? getLeft() : getRight(); + dst= ((IEditableContent)dstParent).replace(dst, src); + if (leftToRight) + setRight(dst); + else + setLeft(dst); + + //setKind(Differencer.NO_CHANGE); + + fireChange(); + } + } + } + + /* (non Javadoc) + * see Object.hashCode + */ + public int hashCode() { + String[] path= getPath(this, 0); + int hashCode= 1; + for (int i= 0; i < path.length; i++) { + String s= path[i]; + hashCode= (31*hashCode) + (s != null ? s.hashCode() : 0); + } + return hashCode; + } + + /* (non Javadoc) + * see Object.equals + */ + public boolean equals(Object other) { + if (other != null && getClass() == other.getClass()) { + String[] path1= getPath(this, 0); + String[] path2= getPath((DiffNode) other, 0); + if (path1.length != path2.length) + return false; + for (int i= 0; i < path1.length; i++) + if (! path1[i].equals(path2[i])) + return false; + return true; + } + return super.equals(other); + } + + private static String[] getPath(ITypedElement el, int level) { + String[] path= null; + if (el instanceof IDiffContainer) { + IDiffContainer parent= ((IDiffContainer)el).getParent(); + if (parent != null) + path= getPath(parent, level+1); + } + if (path == null) + path= new String[level+1]; + path[(path.length-1)-level]= el.getName(); + return path; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java new file mode 100644 index 000000000..44d9c907e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java @@ -0,0 +1,728 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.structuremergeviewer; + +import java.util.Iterator; +import java.util.ResourceBundle; + +import org.eclipse.compare.*; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.patch.DiffViewerComparator; +import org.eclipse.jface.action.*; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.*; + +/** + * A tree viewer that works on objects implementing + * the <code>IDiffContainer</code> and <code>IDiffElement</code> interfaces. + * <p> + * This class may be instantiated; it is not intended to be subclassed outside + * this package. + * </p> + * + * @see IDiffContainer + * @see IDiffElement + * @noextend This class is not intended to be subclassed by clients. + */ +public class DiffTreeViewer extends TreeViewer { + + class DiffViewerContentProvider implements ITreeContentProvider { + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // empty implementation + } + + public boolean isDeleted(Object element) { + return false; + } + + public void dispose() { + inputChanged(DiffTreeViewer.this, getInput(), null); + } + + public Object getParent(Object element) { + if (element instanceof IDiffElement) + return ((IDiffElement)element).getParent(); + return null; + } + + public final boolean hasChildren(Object element) { + if (element instanceof IDiffContainer) + return ((IDiffContainer)element).hasChildren(); + return false; + } + + public final Object[] getChildren(Object element) { + if (element instanceof IDiffContainer) + return ((IDiffContainer)element).getChildren(); + return new Object[0]; + } + + public Object[] getElements(Object element) { + return getChildren(element); + } + } + + /* + * Takes care of swapping left and right if fLeftIsLocal + * is true. + */ + class DiffViewerLabelProvider extends LabelProvider { + + public String getText(Object element) { + + if (element instanceof IDiffElement) + return ((IDiffElement)element).getName(); + + return Utilities.getString(fBundle, "defaultLabel"); //$NON-NLS-1$ + } + + public Image getImage(Object element) { + if (element instanceof IDiffElement) { + IDiffElement input= (IDiffElement) element; + + int kind= input.getKind(); + if (fLeftIsLocal) { + switch (kind & Differencer.DIRECTION_MASK) { + case Differencer.LEFT: + kind= (kind &~ Differencer.LEFT) | Differencer.RIGHT; + break; + case Differencer.RIGHT: + kind= (kind &~ Differencer.RIGHT) | Differencer.LEFT; + break; + } + } + + return fCompareConfiguration.getImage(input.getImage(), kind); + } + return null; + } + } + + static class FilterSame extends ViewerFilter { + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (element instanceof IDiffElement) + return (((IDiffElement)element).getKind() & Differencer.PSEUDO_CONFLICT) == 0; + return true; + } + public boolean isFilterProperty(Object element, Object property) { + return false; + } + } + + private ResourceBundle fBundle; + private CompareConfiguration fCompareConfiguration; + /* package */ boolean fLeftIsLocal; + private IPropertyChangeListener fPropertyChangeListener; + + private Action fCopyLeftToRightAction; + private Action fCopyRightToLeftAction; + private Action fEmptyMenuAction; + private Action fExpandAllAction; + + /** + * Creates a new viewer for the given SWT tree control with the specified configuration. + * + * @param tree the tree control + * @param configuration the configuration for this viewer + */ + public DiffTreeViewer(Tree tree, CompareConfiguration configuration) { + super(tree); + initialize(configuration == null ? new CompareConfiguration() : configuration); + } + + /** + * Creates a new viewer under the given SWT parent and with the specified configuration. + * + * @param parent the SWT control under which to create the viewer + * @param configuration the configuration for this viewer + */ + public DiffTreeViewer(Composite parent, CompareConfiguration configuration) { + super(new Tree(parent, SWT.MULTI)); + initialize(configuration == null ? new CompareConfiguration() : configuration); + } + + private void initialize(CompareConfiguration configuration) { + + Control tree= getControl(); + + INavigatable nav= new INavigatable() { + public boolean selectChange(int flag) { + if (flag == INavigatable.FIRST_CHANGE) { + setSelection(StructuredSelection.EMPTY); + flag = INavigatable.NEXT_CHANGE; + } else if (flag == INavigatable.LAST_CHANGE) { + setSelection(StructuredSelection.EMPTY); + flag = INavigatable.PREVIOUS_CHANGE; + } + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + return internalNavigate(flag == INavigatable.NEXT_CHANGE, true); + } + public Object getInput() { + return DiffTreeViewer.this.getInput(); + } + public boolean openSelectedChange() { + return internalOpen(); + } + public boolean hasChange(int changeFlag) { + return getNextItem(changeFlag == INavigatable.NEXT_CHANGE, false) != null; + } + }; + tree.setData(INavigatable.NAVIGATOR_PROPERTY, nav); + + fLeftIsLocal= Utilities.getBoolean(configuration, "LEFT_IS_LOCAL", false); //$NON-NLS-1$ + + tree.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle()); + + Composite parent= tree.getParent(); + + fBundle= ResourceBundle.getBundle("org.eclipse.compare.structuremergeviewer.DiffTreeViewerResources"); //$NON-NLS-1$ + + // register for notification with the CompareConfiguration + fCompareConfiguration= configuration; + if (fCompareConfiguration != null) { + fPropertyChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + DiffTreeViewer.this.propertyChange(event); + } + }; + fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener); + } + + setContentProvider(new DiffViewerContentProvider()); + setLabelProvider(new DiffViewerLabelProvider()); + + addSelectionChangedListener( + new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent se) { + updateActions(); + } + } + ); + + setComparator(new DiffViewerComparator()); + + ToolBarManager tbm= CompareViewerPane.getToolBarManager(parent); + if (tbm != null) { + tbm.removeAll(); + + tbm.add(new Separator("merge")); //$NON-NLS-1$ + tbm.add(new Separator("modes")); //$NON-NLS-1$ + tbm.add(new Separator("navigation")); //$NON-NLS-1$ + + createToolItems(tbm); + updateActions(); + + tbm.update(true); + } + + MenuManager mm= new MenuManager(); + mm.setRemoveAllWhenShown(true); + mm.addMenuListener( + new IMenuListener() { + public void menuAboutToShow(IMenuManager mm2) { + fillContextMenu(mm2); + if (mm2.isEmpty()) { + if (fEmptyMenuAction == null) { + fEmptyMenuAction= new Action(Utilities.getString(fBundle, "emptyMenuItem")) { //$NON-NLS-1$ + // left empty + }; + fEmptyMenuAction.setEnabled(false); + } + mm2.add(fEmptyMenuAction); + } + } + } + ); + tree.setMenu(mm.createContextMenu(tree)); + } + + /** + * Returns the viewer's name. + * + * @return the viewer's name + */ + public String getTitle() { + String title= Utilities.getString(fBundle, "title", null); //$NON-NLS-1$ + if (title == null) + title= Utilities.getString("DiffTreeViewer.title"); //$NON-NLS-1$ + return title; + } + + /** + * Returns the resource bundle. + * + * @return the viewer's resource bundle + */ + protected ResourceBundle getBundle() { + return fBundle; + } + + /** + * Returns the compare configuration of this viewer. + * + * @return the compare configuration of this viewer + */ + public CompareConfiguration getCompareConfiguration() { + return fCompareConfiguration; + } + + /** + * Called on the viewer disposal. + * Unregisters from the compare configuration. + * Clients may extend if they have to do additional cleanup. + * @param event dispose event that triggered call to this method + */ + protected void handleDispose(DisposeEvent event) { + + if (fCompareConfiguration != null) { + if (fPropertyChangeListener != null) + fCompareConfiguration.removePropertyChangeListener(fPropertyChangeListener); + fCompareConfiguration= null; + } + fPropertyChangeListener= null; + + super.handleDispose(event); + } + + /** + * Tracks property changes of the configuration object. + * Clients may extend to track their own property changes. + * @param event property change event that triggered call to this method + */ + protected void propertyChange(PropertyChangeEvent event) { + // empty default implementation + } + + protected void inputChanged(Object in, Object oldInput) { + super.inputChanged(in, oldInput); + + if (in != oldInput) { + initialSelection(); + updateActions(); + } + } + + /** + * This hook method is called from within <code>inputChanged</code> + * after a new input has been set but before any controls are updated. + * This default implementation calls <code>navigate(true)</code> + * to select and expand the first leaf node. + * Clients can override this method and are free to decide whether + * they want to call the inherited method. + * + * @since 2.0 + */ + protected void initialSelection() { + navigate(true); + } + + /** + * Overridden to avoid expanding <code>DiffNode</code>s that shouldn't expand. + * @param node the node to expand + * @param level non-negative level, or <code>ALL_LEVELS</code> to collapse all levels of the tree + */ + protected void internalExpandToLevel(Widget node, int level) { + + Object data= node.getData(); + + if (dontExpand(data)) + return; + + super.internalExpandToLevel(node, level); + } + + /** + * This hook method is called from within <code>internalExpandToLevel</code> + * to control whether a given model node should be expanded or not. + * This default implementation checks whether the object is a <code>DiffNode</code> and + * calls <code>dontExpand()</code> on it. + * Clients can override this method and are free to decide whether + * they want to call the inherited method. + * + * @param o the model object to be expanded + * @return <code>false</code> if a node should be expanded, <code>true</code> to prevent expanding + * @since 2.0 + */ + protected boolean dontExpand(Object o) { + return o instanceof DiffNode && ((DiffNode)o).dontExpand(); + } + + //---- merge action support + + /** + * This factory method is called after the viewer's controls have been created. + * It installs four actions in the given <code>ToolBarManager</code>. Two actions + * allow for copying one side of a <code>DiffNode</code> to the other side. + * Two other actions are for navigating from one node to the next (previous). + * <p> + * Clients can override this method and are free to decide whether they want to call + * the inherited method. + * + * @param toolbarManager the toolbar manager for which to add the actions + */ + protected void createToolItems(ToolBarManager toolbarManager) { + +// fCopyLeftToRightAction= new Action() { +// public void run() { +// copySelected(true); +// } +// }; +// Utilities.initAction(fCopyLeftToRightAction, fBundle, "action.TakeLeft."); +// toolbarManager.appendToGroup("merge", fCopyLeftToRightAction); + +// fCopyRightToLeftAction= new Action() { +// public void run() { +// copySelected(false); +// } +// }; +// Utilities.initAction(fCopyRightToLeftAction, fBundle, "action.TakeRight."); +// toolbarManager.appendToGroup("merge", fCopyRightToLeftAction); + +// fNextAction= new Action() { +// public void run() { +// navigate(true); +// } +// }; +// Utilities.initAction(fNextAction, fBundle, "action.NextDiff."); //$NON-NLS-1$ +// toolbarManager.appendToGroup("navigation", fNextAction); //$NON-NLS-1$ + +// fPreviousAction= new Action() { +// public void run() { +// navigate(false); +// } +// }; +// Utilities.initAction(fPreviousAction, fBundle, "action.PrevDiff."); //$NON-NLS-1$ +// toolbarManager.appendToGroup("navigation", fPreviousAction); //$NON-NLS-1$ + } + + /** + * This method is called to add actions to the viewer's context menu. + * It installs actions for expanding tree nodes, copying one side of a <code>DiffNode</code> to the other side. + * Clients can override this method and are free to decide whether they want to call + * the inherited method. + * + * @param manager the menu manager for which to add the actions + */ + protected void fillContextMenu(IMenuManager manager) { + if (fExpandAllAction == null) { + fExpandAllAction= new Action() { + public void run() { + expandSelection(); + } + }; + Utilities.initAction(fExpandAllAction, fBundle, "action.ExpandAll."); //$NON-NLS-1$ + } + + boolean enable= false; + ISelection selection= getSelection(); + if (selection instanceof IStructuredSelection) { + Iterator elements= ((IStructuredSelection)selection).iterator(); + while (elements.hasNext()) { + Object element= elements.next(); + if (element instanceof IDiffContainer) { + if (((IDiffContainer)element).hasChildren()) { + enable= true; + break; + } + } + } + } + fExpandAllAction.setEnabled(enable); + + manager.add(fExpandAllAction); + + if (fCopyLeftToRightAction != null) + manager.add(fCopyLeftToRightAction); + if (fCopyRightToLeftAction != null) + manager.add(fCopyRightToLeftAction); + } + + /** + * Expands to infinity all items in the selection. + * + * @since 2.0 + */ + protected void expandSelection() { + ISelection selection= getSelection(); + if (selection instanceof IStructuredSelection) { + Iterator elements= ((IStructuredSelection)selection).iterator(); + while (elements.hasNext()) { + Object next= elements.next(); + expandToLevel(next, ALL_LEVELS); + } + } + } + + /** + * Copies one side of all <code>DiffNode</code>s in the current selection to the other side. + * Called from the (internal) actions for copying the sides of a <code>DiffNode</code>. + * Clients may override. + * + * @param leftToRight if <code>true</code> the left side is copied to the right side. + * If <code>false</code> the right side is copied to the left side + */ + protected void copySelected(boolean leftToRight) { + ISelection selection= getSelection(); + if (selection instanceof IStructuredSelection) { + Iterator e= ((IStructuredSelection) selection).iterator(); + while (e.hasNext()) { + Object element= e.next(); + if (element instanceof ICompareInput) + copyOne((ICompareInput) element, leftToRight); + } + } + } + + /** + * Called to copy one side of the given node to the other. + * This default implementation delegates the call to <code>ICompareInput.copy(...)</code>. + * Clients may override. + * @param node the node to copy + * @param leftToRight if <code>true</code> the left side is copied to the right side. + * If <code>false</code> the right side is copied to the left side + */ + protected void copyOne(ICompareInput node, boolean leftToRight) { + + node.copy(leftToRight); + + // update node's image + update(new Object[] { node }, null); + } + + /** + * Selects the next (or previous) node of the current selection. + * If there is no current selection the first (last) node in the tree is selected. + * Wraps around at end or beginning. + * Clients may override. + * + * @param next if <code>true</code> the next node is selected, otherwise the previous node + */ + protected void navigate(boolean next) { + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + internalNavigate(next, false); + } + + //---- private + + /** + * Selects the next (or previous) node of the current selection. + * If there is no current selection the first (last) node in the tree is selected. + * Wraps around at end or beginning. + * Clients may override. + * + * @param next if <code>true</code> the next node is selected, otherwise the previous node + * @param fireOpen if <code>true</code> an open event is fired. + * @return <code>true</code> if at end (or beginning) + */ + private boolean internalNavigate(boolean next, boolean fireOpen) { + Control c= getControl(); + if (!(c instanceof Tree) || c.isDisposed()) + return false; + TreeItem item = getNextItem(next, true); + if (item != null) { + internalSetSelection(item, fireOpen); + } + return item == null; + } + + private TreeItem getNextItem(boolean next, boolean expand) { + Control c= getControl(); + if (!(c instanceof Tree) || c.isDisposed()) + return null; + + Tree tree= (Tree) c; + TreeItem item= null; + TreeItem children[]= tree.getSelection(); + if (children != null && children.length > 0) + item= children[0]; + if (item == null) { + children= tree.getItems(); + if (children != null && children.length > 0) { + item= children[0]; + if (item != null && item.getItemCount() <= 0) { + return item; + } + } + } + + while (true) { + item= findNextPrev(item, next, expand); + if (item == null) + break; + if (item.getItemCount() <= 0) + break; + } + return item; + } + + private TreeItem findNextPrev(TreeItem item, boolean next, boolean expand) { + + if (item == null) + return null; + + TreeItem children[]= null; + + if (!next) { + + TreeItem parent= item.getParentItem(); + if (parent != null) + children= parent.getItems(); + else + children= item.getParent().getItems(); + + if (children != null && children.length > 0) { + // goto previous child + int index= 0; + for (; index < children.length; index++) + if (children[index] == item) + break; + + if (index > 0) { + + item= children[index-1]; + + while (true) { + createChildren(item); + int n= item.getItemCount(); + if (n <= 0) + break; + + if (expand) + item.setExpanded(true); + item= item.getItems()[n-1]; + } + + // previous + return item; + } + } + + // go up + item= parent; + + } else { + if (expand) + item.setExpanded(true); + createChildren(item); + + if (item.getItemCount() > 0) { + // has children: go down + children= item.getItems(); + return children[0]; + } + + while (item != null) { + children= null; + TreeItem parent= item.getParentItem(); + if (parent != null) + children= parent.getItems(); + else + children= item.getParent().getItems(); + + if (children != null && children.length > 0) { + // goto next child + int index= 0; + for (; index < children.length; index++) + if (children[index] == item) + break; + + if (index < children.length-1) { + // next + return children[index+1]; + } + } + + // go up + item= parent; + } + } + + return item; + } + + private void internalSetSelection(TreeItem ti, boolean fireOpen) { + if (ti != null) { + Object data= ti.getData(); + if (data != null) { + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + ISelection selection= new StructuredSelection(data); + setSelection(selection, true); + ISelection currentSelection= getSelection(); + if (fireOpen && currentSelection != null && selection.equals(currentSelection)) { + fireOpen(new OpenEvent(this, selection)); + } + } + } + } + + private final boolean isEditable(Object element, boolean left) { + if (element instanceof ICompareInput) { + ICompareInput diff= (ICompareInput) element; + Object side= left ? diff.getLeft() : diff.getRight(); + if (side == null && diff instanceof IDiffElement) { + IDiffContainer container= ((IDiffElement)diff).getParent(); + if (container instanceof ICompareInput) { + ICompareInput parent= (ICompareInput) container; + side= left ? parent.getLeft() : parent.getRight(); + } + } + if (side instanceof IEditableContent) + return ((IEditableContent) side).isEditable(); + } + return false; + } + + private void updateActions() { + int leftToRight= 0; + int rightToLeft= 0; + ISelection selection= getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection ss= (IStructuredSelection) selection; + Iterator e= ss.iterator(); + while (e.hasNext()) { + Object element= e.next(); + if (element instanceof ICompareInput) { + if (isEditable(element, false)) + leftToRight++; + if (isEditable(element, true)) + rightToLeft++; + if (leftToRight > 0 && rightToLeft > 0) + break; + } + } + if (fExpandAllAction != null) + fExpandAllAction.setEnabled(selection.isEmpty()); + } + if (fCopyLeftToRightAction != null) + fCopyLeftToRightAction.setEnabled(leftToRight > 0); + if (fCopyRightToLeftAction != null) + fCopyRightToLeftAction.setEnabled(rightToLeft > 0); + } + + /* + * Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + */ + private boolean internalOpen() { + ISelection selection= getSelection(); + if (selection != null && !selection.isEmpty()) { + fireOpen(new OpenEvent(this, selection)); + return true; + } + return false; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewerResources.properties new file mode 100644 index 000000000..76ae2b89d --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewerResources.properties @@ -0,0 +1,53 @@ +############################################################################### +# Copyright (c) 2000, 2006 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 +############################################################################### + +# @(#)DiffTreeViewerResources.properties +# +# Resource strings for DiffTreeViewer.java + +title= Structure Compare +defaultLabel= <no name> + +##################################################### +# Dummy menu item for empty context menu +##################################################### + +emptyMenuItem= <Empty Menu> + +##################################################### +# Actions +##################################################### + +action.Smart.label=Smart +action.Smart.tooltip=Guess Similar Elements +action.Smart.image=smartmode_co.gif + +action.ExpandAll.label=Expand All +action.ExpandAll.tooltip=Expand All Nodes + +action.CompareContents.label= Show Content Comparison +action.CompareContents.tooltip= Show Content Comparison + +action.NextDiff.label=Next +action.NextDiff.tooltip=Select Next Change +action.NextDiff.image=next_nav.gif + +action.PrevDiff.label=Previous +action.PrevDiff.tooltip=Select Previous Change +action.PrevDiff.image=prev_nav.gif + +action.TakeLeft.label=Copy Left to Right +action.TakeLeft.tooltip=Copy Selected Nodes from Left to Right +action.TakeLeft.image=copycont_r_co.gif + +action.TakeRight.label=Copy Right to Left +action.TakeRight.tooltip=Copy Selected Nodes from Right to Left +action.TakeRight.image=copycont_l_co.gif diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java new file mode 100644 index 000000000..60fa61b97 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java @@ -0,0 +1,534 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.structuremergeviewer; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +import com.ibm.icu.text.MessageFormat; + +/** + * A generic two-way or three-way differencing engine. + * <p> + * The engine is used by calling one of the <code>findDifferences</code> methods and passing + * in the objects to compare. + * The engine calls the following methods on the input objects to perform the compare: + * <UL> + * <LI><code>getChildren</code>: for enumerating the children of an object (if any), + * <LI><code>contentsEqual</code>: for comparing the content of leaf objects, that is, objects without children, + * <LI><code>visit</code>: for every pair of compared object the compare result is passed in. + * </UL> + * Clients may use as is, or subclass to provide a custom implementation for the three hooks. + * However the default implementation already deals with the typical case: + * <UL> + * <LI><code>getChildren</code>: tries to apply the <code>IStructureComparator</code> + * interface to enumerate the children, + * <LI><code>contentsEqual</code>: tries to apply the <code>IStreamContentAccessor</code> interface + * to perform a byte-wise content comparison, + * <LI><code>visit</code>: creates a <code>DiffNode</code> for any detected difference between the compared objects and + * links it under a parent node effectively creating a tree of differences. + * </UL> + * The different kind of changes detected by the engine are decoded as follows: + * In the two-way case only NO_CHANGE, ADDITION, DELETION, and CHANGE are used. + * In the three-way case these constants are bitwise ORed with one of directional constants + * LEFT, RIGHT, and CONFLICTING. + */ +public class Differencer { + + // The kind of differences. + /** + * Difference constant (value 0) indicating no difference. + */ + public static final int NO_CHANGE= 0; + /** + * Difference constant (value 1) indicating one side was added. + */ + public static final int ADDITION= 1; + /** + * Difference constant (value 2) indicating one side was removed. + */ + public static final int DELETION= 2; + /** + * Difference constant (value 3) indicating side changed. + */ + public static final int CHANGE= 3; + + /** + * Bit mask (value 3) for extracting the kind of difference. + */ + public static final int CHANGE_TYPE_MASK= 3; + + // The direction of a three-way change. + /** + * Three-way change constant (value 4) indicating a change on left side. + */ + public static final int LEFT= 4; + + /** + * Three-way change constant (value 8) indicating a change on right side. + */ + public static final int RIGHT= 8; + + /** + * Three-way change constant (value 12) indicating a change on left and + * right sides. + */ + public static final int CONFLICTING= 12; + + /** + * Bit mask (value 12) for extracting the direction of a three-way change. + */ + public static final int DIRECTION_MASK= 12; + + /** + * Constant (value 16) indicating a change on left and + * right side (with respect to ancestor) but left and right are identical. + */ + public static final int PSEUDO_CONFLICT= 16; + + + static class Node { + List fChildren; + int fCode; + Object fAncestor; + Object fLeft; + Object fRight; + + Node() { + // nothing to do + } + Node(Node parent, Object ancestor, Object left, Object right) { + parent.add(this); + fAncestor= ancestor; + fLeft= left; + fRight= right; + } + void add(Node child) { + if (fChildren == null) + fChildren= new ArrayList(); + fChildren.add(child); + } + Object visit(Differencer d, Object parent, int level) { + if (fCode == NO_CHANGE) + return null; + //dump(level); + Object data= d.visit(parent, fCode, fAncestor, fLeft, fRight); + if (fChildren != null) { + Iterator i= fChildren.iterator(); + while (i.hasNext()) { + Node n= (Node) i.next(); + n.visit(d, data, level+1); + } + } + return data; + } +// private void dump(int level) { +// String name= null; +// if (fAncestor instanceof ITypedElement) +// name= ((ITypedElement)fAncestor).getName(); +// if (name == null && fLeft instanceof ITypedElement) +// name= ((ITypedElement)fLeft).getName(); +// if (name == null && fRight instanceof ITypedElement) +// name= ((ITypedElement)fRight).getName(); +// if (name == null) +// name= "???"; //$NON-NLS-1$ +// +// for (int i= 0; i < level; i++) +// System.out.print(" "); //$NON-NLS-1$ +// +// System.out.println(getDiffType(fCode) + name); +// } + +// private String getDiffType(int code) { +// String dir= " "; //$NON-NLS-1$ +// switch (code & DIRECTION_MASK) { +// case LEFT: +// dir= ">"; //$NON-NLS-1$ +// break; +// case RIGHT: +// dir= "<"; //$NON-NLS-1$ +// break; +// case CONFLICTING: +// dir= "!"; //$NON-NLS-1$ +// break; +// } +// String change= "="; //$NON-NLS-1$ +// switch (code & CHANGE_TYPE_MASK) { +// case ADDITION: +// change= "+"; //$NON-NLS-1$ +// break; +// case DELETION: +// change= "-"; //$NON-NLS-1$ +// break; +// case CHANGE: +// change= "#"; //$NON-NLS-1$ +// break; +// } +// return dir + change + " "; //$NON-NLS-1$ +// } + } + + /** + * Creates a new differencing engine. + */ + public Differencer() { + // nothing to do + } + + /** + * Starts the differencing engine on the three input objects. If threeWay is <code>true</code> a + * three-way comparison is performed, otherwise a two-way compare (in the latter case the ancestor argument is ignored). + * The progress monitor is passed to the method <code>updateProgress</code> which is called for every node or + * leaf compare. The method returns the object that was returned from the top-most call to method <code>visit</code>. + * At most two of the ancestor, left, and right parameters are allowed to be <code>null</code>. + * + * @param threeWay if <code>true</code> a three-way comparison is performed, otherwise a two-way compare + * @param pm a progress monitor which is passed to method <code>updateProgress</code> + * @param data a client data that is passed to the top-level call to <code>visit</code> + * @param ancestor the ancestor object of the compare (may be <code>null</code>) + * @param left the left object of the compare + * @param right the right object of the compare + * @return the object returned from the top most call to method <code>visit</code>, + * possibly <code>null</code> + */ + public Object findDifferences(boolean threeWay, IProgressMonitor pm, Object data, Object ancestor, Object left, Object right) { + + Node root= new Node(); + + int code= traverse(threeWay, root, pm, threeWay ? ancestor : null, left, right); + + if (code != NO_CHANGE) { + List l= root.fChildren; + if (l.size() > 0) { + Node first= (Node)l.get(0); + return first.visit(this, data, 0); + } + } + return null; + } + + /* + * Traverse tree in postorder. + */ + private int traverse(boolean threeWay, Node parent, IProgressMonitor pm, Object ancestor, Object left, Object right) { + + Object[] ancestorChildren= getChildren(ancestor); + Object[] rightChildren= getChildren(right); + Object[] leftChildren= getChildren(left); + + int code= NO_CHANGE; + + Node node= new Node(parent, ancestor, left, right); + + boolean content= true; // we reset this if we have at least one child + + if (((threeWay && ancestorChildren != null) || !threeWay) + && rightChildren != null && leftChildren != null) { + // we only recurse down if no leg is null + // a node + + Set allSet= new HashSet(20); + Map ancestorSet= null; + Map rightSet= null; + Map leftSet= null; + + if (ancestorChildren != null) { + ancestorSet= new HashMap(10); + for (int i= 0; i < ancestorChildren.length; i++) { + Object ancestorChild= ancestorChildren[i]; + ancestorSet.put(ancestorChild, ancestorChild); + allSet.add(ancestorChild); + } + } + + if (rightChildren != null) { + rightSet= new HashMap(10); + for (int i= 0; i < rightChildren.length; i++) { + Object rightChild= rightChildren[i]; + rightSet.put(rightChild, rightChild); + allSet.add(rightChild); + } + } + + if (leftChildren != null) { + leftSet= new HashMap(10); + for (int i= 0; i < leftChildren.length; i++) { + Object leftChild= leftChildren[i]; + leftSet.put(leftChild, leftChild); + allSet.add(leftChild); + } + } + + Iterator e= allSet.iterator(); + while (e.hasNext()) { + Object keyChild= e.next(); + + if (pm != null) { + + if (pm.isCanceled()) + throw new OperationCanceledException(); + + updateProgress(pm, keyChild); + } + + Object ancestorChild= ancestorSet != null ? ancestorSet.get(keyChild) : null; + Object leftChild= leftSet != null ? leftSet.get(keyChild) : null; + Object rightChild= rightSet != null ? rightSet.get(keyChild) : null; + + int c= traverse(threeWay, node, pm, ancestorChild, leftChild, rightChild); + + if ((c & CHANGE_TYPE_MASK) != NO_CHANGE) { + code|= CHANGE; // deletions and additions of child result in a change of the container + code|= (c & DIRECTION_MASK); // incoming & outgoing are just ored + content= false; + } + } + } + + if (content) // a leaf + code= compare(threeWay, ancestor, left, right); + + node.fCode= code; + + return code; + } + + /** + * Called for every node or leaf comparison. + * The differencing engine passes in the input objects of the compare and the result of the compare. + * The data object is the value returned from a call to the <code>visit</code> method on the parent input. + * It can be considered the "parent" reference and is useful when building a tree. + * <p> + * The <code>Differencer</code> implementation returns a new + * <code>DiffNode</code> which is initialized with the corresponding values. + * Subclasses may override. + * + * @param data object returned from parent call to <code>visit</code>, + * possibly <code>null</code> + * @param result the result of the compare operation performed on the three inputs + * @param ancestor the compare ancestor of the left and right inputs + * @param left the left input to the compare + * @param right the right input to the compare + * @return the result, possibly <code>null</code> + */ + protected Object visit(Object data, int result, Object ancestor, Object left, Object right) { + return new DiffNode((IDiffContainer) data, result, (ITypedElement)ancestor, (ITypedElement)left, (ITypedElement)right); + } + + /* + * Performs a 2-way or 3-way compare of the given leaf elements and returns an integer + * describing the kind of difference. + */ + private int compare(boolean threeway, Object ancestor, Object left, Object right) { + + int description= NO_CHANGE; + + if (threeway) { + if (ancestor == null) { + if (left == null) { + if (right == null) { + Assert.isTrue(false); + // shouldn't happen + } else { + description= RIGHT | ADDITION; + } + } else { + if (right == null) { + description= LEFT | ADDITION; + } else { + description= CONFLICTING | ADDITION; + if (contentsEqual(left, right)) + description|= PSEUDO_CONFLICT; + } + } + } else { + if (left == null) { + if (right == null) { + description= CONFLICTING | DELETION | PSEUDO_CONFLICT; + } else { + if (contentsEqual(ancestor, right)) + description= LEFT | DELETION; + else + description= CONFLICTING | CHANGE; + } + } else { + if (right == null) { + if (contentsEqual(ancestor, left)) + description= RIGHT | DELETION; + else + description= CONFLICTING | CHANGE; + } else { + boolean ay= contentsEqual(ancestor, left); + boolean am= contentsEqual(ancestor, right); + + if (ay && am) { + // empty + } else if (ay && !am) { + description= RIGHT | CHANGE; + } else if (!ay && am) { + description= LEFT | CHANGE; + } else { + description= CONFLICTING | CHANGE; + if (contentsEqual(left, right)) + description|= PSEUDO_CONFLICT; + } + } + } + } + } else { // two way compare ignores ancestor + if (left == null) { + if (right == null) { + Assert.isTrue(false); + // shouldn't happen + } else { + description= ADDITION; + } + } else { + if (right == null) { + description= DELETION; + } else { + if (! contentsEqual(left, right)) + description= CHANGE; + } + } + } + + return description; + } + + /** + * Performs a content compare on the two given inputs. + * <p> + * The <code>Differencer</code> implementation + * returns <code>true</code> if both inputs implement <code>IStreamContentAccessor</code> + * and their byte contents is identical. Subclasses may override to implement + * a different content compare on the given inputs. + * </p> + * + * @param input1 first input to contents compare + * @param input2 second input to contents compare + * @return <code>true</code> if content is equal + */ + protected boolean contentsEqual(Object input1, Object input2) { + + if (input1 == input2) + return true; + + InputStream is1= getStream(input1); + InputStream is2= getStream(input2); + + if (is1 == null && is2 == null) // no byte contents + return true; + + try { + if (is1 == null || is2 == null) // only one has contents + return false; + + while (true) { + int c1= is1.read(); + int c2= is2.read(); + if (c1 == -1 && c2 == -1) + return true; + if (c1 != c2) + break; + + } + } catch (IOException ex) { + // NeedWork + } finally { + if (is1 != null) { + try { + is1.close(); + } catch(IOException ex) { + // silently ignored + } + } + if (is2 != null) { + try { + is2.close(); + } catch(IOException ex) { + // silently ignored + } + } + } + return false; + } + + /* + * Tries to return an InputStream for the given object. + * Returns <code>null</code> if the object not an IStreamContentAccessor + * or an error occurred. + */ + private InputStream getStream(Object o) { + if (o instanceof IStreamContentAccessor) { + try { + return ((IStreamContentAccessor)o).getContents(); + } catch(CoreException ex) { + // NeedWork + } + } + return null; + } + + /** + * Returns the children of the given input or <code>null</code> if there are no children. + * <p> + * The <code>Differencer</code> implementation checks whether the input + * implements the <code>IStructureComparator</code> interface. If yes it is used + * to return an array containing all children. Otherwise <code>null</code> is returned. + * Subclasses may override to implement a different strategy to enumerate children. + * </p> + * + * @param input the object for which to return children + * @return the children of the given input or <code>null</code> if there are no children. + */ + protected Object[] getChildren(Object input) { + if (input instanceof IStructureComparator) + return ((IStructureComparator)input).getChildren(); + return null; + } + + /** + * Called for every leaf or node compare to update progress information. + * <p> + * The <code>Differencer</code> implementation shows the name of the input object + * as a subtask. Subclasses may override. + * </p> + * + * @param progressMonitor the progress monitor for reporting progress + * @param node the currently processed non-<code>null</code> node + */ + protected void updateProgress(IProgressMonitor progressMonitor, Object node) { + if (node instanceof ITypedElement) { + String name= ((ITypedElement)node).getName(); + String fmt= Utilities.getString("Differencer.progressFormat"); //$NON-NLS-1$ + String msg= MessageFormat.format(fmt, new String[] { name }); + progressMonitor.subTask(msg); + //progressMonitor.worked(1); + } + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DocumentRangeNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DocumentRangeNode.java new file mode 100644 index 000000000..4ffee0189 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DocumentRangeNode.java @@ -0,0 +1,468 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.structuremergeviewer; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.IDocumentRange; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.text.*; +import org.eclipse.swt.widgets.Shell; + + +/** + * A document range node represents a structural element + * when performing a structure compare of documents. + * <code>DocumentRangeNodes</code> are created while parsing the document and represent + * a semantic entity (e.g. a Java class or method). + * As a consequence of the parsing a <code>DocumentRangeNode</code> maps to a range + * of characters in the document. + * <p> + * Since a <code>DocumentRangeNode</code> implements the <code>IStructureComparator</code> + * and <code>IStreamContentAccessor</code> interfaces it can be used as input to the + * differencing engine. This makes it possible to perform + * a structural diff on a document and have the nodes and leaves of the compare easily map + * to character ranges within the document. + * <p> + * Clients need to be aware that this node registers position updaters with the document + * using {@link IDocument#addPosition(String, Position)} with the category set to + * {@link IDocumentRange#RANGE_CATEGORY}. The {@link StructureDiffViewer} will + * remove the category when the nodes are no longer being used. Other clients + * must do the same. + * <p> + * Subclasses may add additional state collected while parsing the document. + * </p> + * @see Differencer + */ +public class DocumentRangeNode + implements IDocumentRange, IStructureComparator, IEditableContent, IEncodedStreamContentAccessor, IAdaptable, IEditableContentExtension { + + private static final String UTF_16= "UTF-16"; //$NON-NLS-1$ + + private IDocument fBaseDocument; + private Position fRange; // the range in the base document + private int fTypeCode; + private String fID; + private Position fAppendPosition; // a position where to insert a child textually + private ArrayList fChildren; + private final DocumentRangeNode fParent; + + /** + * Creates a new <code>DocumentRangeNode</code> for the given range within the specified + * document. The <code>typeCode</code> is uninterpreted client data. The ID is used when comparing + * two nodes with each other: i.e. the differencing engine performs a content compare + * on two nodes if their IDs are equal. + * + * @param typeCode a type code for this node + * @param id an identifier for this node + * @param document document on which this node is based on + * @param start start position of range within document + * @param length length of range + */ + public DocumentRangeNode(int typeCode, String id, IDocument document, int start, int length) { + this(null, typeCode, id, document, start, length); + } + + /** + * Creates a new <code>DocumentRangeNode</code> for the given range within the specified + * document. The <code>typeCode</code> is uninterpreted client data. The ID is used when comparing + * two nodes with each other: i.e. the differencing engine performs a content compare + * on two nodes if their IDs are equal. + * + * @param parent the parent node + * @param typeCode a type code for this node + * @param id an identifier for this node + * @param document document on which this node is based on + * @param start start position of range within document + * @param length length of range + * @since 3.3 + */ + public DocumentRangeNode(DocumentRangeNode parent, int typeCode, String id, IDocument document, int start, int length) { + fParent = parent; + fTypeCode= typeCode; + fID= id; + fBaseDocument= document; + registerPositionUpdater(start, length); + } + + private void registerPositionUpdater(int start, int length) { + fBaseDocument.addPositionCategory(RANGE_CATEGORY); + fRange= new Position(start, length); + try { + fBaseDocument.addPosition(RANGE_CATEGORY, fRange); + } catch (BadPositionCategoryException ex) { + CompareUIPlugin.log(ex); + } catch (BadLocationException ex) { + CompareUIPlugin.log(ex); + } + } + + /* (non Javadoc) + * see IDocumentRange.getDocument + */ + public IDocument getDocument() { + return fBaseDocument; + } + + /* (non Javadoc) + * see IDocumentRange.getRange + */ + public Position getRange() { + return fRange; + } + + /** + * Returns the type code of this node. + * The type code is uninterpreted client data which can be set in the constructor. + * + * @return the type code of this node + */ + public int getTypeCode() { + return fTypeCode; + } + + /** + * Returns this node's id. + * It is used in <code>equals</code> and <code>hashcode</code>. + * + * @return the node's id + */ + public String getId() { + return fID; + } + + /** + * Sets this node's id. + * It is used in <code>equals</code> and <code>hashcode</code>. + * + * @param id the new id for this node + */ + public void setId(String id) { + fID= id; + } + + /** + * Adds the given node as a child. + * + * @param node the node to add as a child + */ + public void addChild(DocumentRangeNode node) { + if (fChildren == null) + fChildren= new ArrayList(); + fChildren.add(node); + } + + /* (non Javadoc) + * see IStructureComparator.getChildren + */ + public Object[] getChildren() { + if (fChildren != null) + return fChildren.toArray(); + return new Object[0]; + } + + /** + * Sets the length of the range of this node. + * + * @param length the length of the range + */ + public void setLength(int length) { + getRange().setLength(length); + } + + /** + * Sets a position within the document range that can be used to (legally) insert + * text without breaking the syntax of the document. + * <p> + * E.g. when parsing a Java document the "append position" of a <code>DocumentRangeNode</code> + * representing a Java class could be the character position just before the closing bracket. + * Inserting the text of a new method there would not disturb the syntax of the class. + * + * @param pos the character position within the underlying document where text can be legally inserted + */ + public void setAppendPosition(int pos) { + if (fAppendPosition != null) + try { + fBaseDocument.removePosition(RANGE_CATEGORY, fAppendPosition); + } catch (BadPositionCategoryException e) { + // Ignore + } + try { + // TODO: Avoid an exception for a position that is past the end of the document + if (pos <= getDocument().getLength()) { + Position p= new Position(pos); + fBaseDocument.addPosition(RANGE_CATEGORY, p); + fAppendPosition= p; + } + } catch (BadPositionCategoryException ex) { + // silently ignored + } catch (BadLocationException ex) { + // silently ignored + } + } + + /** + * Returns the position that has been set with <code>setAppendPosition</code>. + * If <code>setAppendPosition</code> hasn't been called, the position after the last character + * of this range is returned. This method will return <code>null</code> if the position + * could not be registered with the document. + * + * @return a position where text can be legally inserted + */ + public Position getAppendPosition() { + if (fAppendPosition == null) { + try { + Position p= new Position(fBaseDocument.getLength()); + fBaseDocument.addPosition(RANGE_CATEGORY, p); + fAppendPosition= p; + return fAppendPosition; + } catch (BadPositionCategoryException ex) { + // silently ignored + } catch (BadLocationException ex) { + // silently ignored + } + } + return new Position(fBaseDocument.getLength()); + } + + /** + * Implementation based on <code>getID</code>. + * @param other the object to compare this <code>DocumentRangeNode</code> against. + * @return <code>true</code> if the <code>DocumentRangeNodes</code>are equal; <code>false</code> otherwise. + */ + public boolean equals(Object other) { + if (other != null && other.getClass() == getClass()) { + DocumentRangeNode tn= (DocumentRangeNode) other; + return fTypeCode == tn.fTypeCode && fID.equals(tn.fID); + } + return super.equals(other); + } + + /** + * Implementation based on <code>getID</code>. + * @return a hash code for this object. + */ + public int hashCode() { + return fID.hashCode(); + } + + /* + * Find corresponding position + */ + private Position findCorrespondingPosition(DocumentRangeNode otherParent, DocumentRangeNode child) { + + // we try to find a predecessor of left Node which exists on the right side + + if (child != null && fChildren != null) { + int ix= otherParent.fChildren.indexOf(child); + if (ix >= 0) { + + for (int i= ix - 1; i >= 0; i--) { + DocumentRangeNode c1= (DocumentRangeNode) otherParent.fChildren.get(i); + int i2= fChildren.indexOf(c1); + if (i2 >= 0) { + DocumentRangeNode c= (DocumentRangeNode) fChildren.get(i2); + //System.out.println(" found corresponding: " + i2 + " " + c); + Position p= c.fRange; + + //try { + Position po= new Position(p.getOffset() + p.getLength() + 1, 0); + //c.fBaseDocument.addPosition(RANGE_CATEGORY, po); + return po; + //} catch (BadLocationException ex) { + //} + //break; + } + } + + for (int i= ix; i < otherParent.fChildren.size(); i++) { + DocumentRangeNode c1= (DocumentRangeNode) otherParent.fChildren.get(i); + int i2= fChildren.indexOf(c1); + if (i2 >= 0) { + DocumentRangeNode c= (DocumentRangeNode) fChildren.get(i2); + //System.out.println(" found corresponding: " + i2 + " " + c); + Position p= c.fRange; + //try { + Position po= new Position(p.getOffset(), 0); + //c.fBaseDocument.addPosition(RANGE_CATEGORY, po); + return po; + //} catch (BadLocationException ex) { + //} + //break; + } + } + + } + } + return getAppendPosition(); + } + + private void add(String s, DocumentRangeNode parent, DocumentRangeNode child) { + + Position p= findCorrespondingPosition(parent, child); + if (p != null) { + try { + fBaseDocument.replace(p.getOffset(), p.getLength(), s); + } catch (BadLocationException ex) { + CompareUIPlugin.log(ex); + } + } + } + + /* (non Javadoc) + * see IStreamContentAccessor.getContents + */ + public InputStream getContents() { + String s; + try { + s= fBaseDocument.get(fRange.getOffset(), fRange.getLength()); + } catch (BadLocationException ex) { + s= ""; //$NON-NLS-1$ + } + return new ByteArrayInputStream(Utilities.getBytes(s, UTF_16)); + } + + + /** + * If this node has a parent, return the editability of the parent. + * Otherwise return <code>true</code>. Subclasses may override. + * @see org.eclipse.compare.IEditableContent#isEditable() + */ + public boolean isEditable() { + if (fParent != null) + return fParent.isEditable(); + return true; + } + + /* (non Javadoc) + * see IEditableContent.replace + */ + public ITypedElement replace(ITypedElement child, ITypedElement other) { + + if (fParent == null) { + // TODO: I don't believe this code does anything useful but just in case + // I'm leaving it in but disabling it for the shared document case + // since all the subclasses that have been converted overrode the method anyway + DocumentRangeNode src= null; + String srcContents= ""; //$NON-NLS-1$ + + if (other != null) { + src= (DocumentRangeNode) child; + + if (other instanceof IStreamContentAccessor) { + try { + srcContents= Utilities.readString((IStreamContentAccessor)other); + } catch(CoreException ex) { + // NeedWork + CompareUIPlugin.log(ex); + } + } + } + + if (child == null) // no destination: we have to add the contents into the parent + add(srcContents, null, src); + } + nodeChanged(this); + return child; + } + + /** + * Default implementation that calls {@link #internalSetContents(byte[])} + * and then {@link #nodeChanged(DocumentRangeNode)}. Subclasses + * may override but should then call {@link #nodeChanged(DocumentRangeNode)} + * after the contents have been set. + * @see org.eclipse.compare.IEditableContent#setContent(byte[]) + */ + public void setContent(byte[] content) { + internalSetContents(content); + nodeChanged(this); + } + + /** + * Method that is invoked from {@link #setContent(byte[])}. By default, + * this method does nothing. Subclasses may override. + * @param content the new content + * @since 3.3 + */ + protected void internalSetContents(byte[] content) { + // By default, do nothing + + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IStreamContentAccessor#getEncoding() + */ + public String getCharset() { + return UTF_16; + } + + /** + * Method that should be invoked whenever the contents of this node are + * changed. the change is propagated to the parent if there is one. + * @param node the node that has changed. + * @since 3.3 + */ + protected void nodeChanged(DocumentRangeNode node) { + if (fParent != null) + fParent.nodeChanged(node); + } + + /** + * Implement {@link IAdaptable#getAdapter(Class)} in order to provide + * an {@link ISharedDocumentAdapter} that provides the proper look up key based + * on the input from which this structure node was created. The proper + * shared document adapter is obtained by calling {@link #getAdapter(Class)} + * on this node's parent if there is one. + * @param adapter the adapter class to look up + * @return the object adapted to the given class or <code>null</code> + * @see IAdaptable#getAdapter(Class) + * @since 3.3 + */ + public Object getAdapter(Class adapter) { + if (adapter == ISharedDocumentAdapter.class && fParent != null) + return fParent.getAdapter(adapter); + + return Platform.getAdapterManager().getAdapter(this, adapter); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContentExtension#isReadOnly() + */ + public boolean isReadOnly() { + if (fParent != null) + return fParent.isReadOnly(); + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContentExtension#validateEdit(org.eclipse.swt.widgets.Shell) + */ + public IStatus validateEdit(Shell shell) { + if (fParent != null) + return fParent.validateEdit(shell); + return Status.OK_STATUS; + } + + /** + * Return the parent of this node or <code>null</code> + * if the node doesn't have a parent or the parent is not known. + * @return the parent of this node or <code>null</code> + */ + public Object getParentNode() { + return fParent; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInput.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInput.java new file mode 100644 index 000000000..2c3a2f1e1 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInput.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +import org.eclipse.compare.ITypedElement; +import org.eclipse.swt.graphics.Image; + +/** + * Interface for objects used as input to a two-way or three-way compare viewer. + * It defines API for accessing the three sides for the compare, + * and a name and image which is used when displaying the three way input + * in the UI, for example, in a title bar. + * <p> + * Note: at most two sides of an <code>ICompareInput</code> can be <code>null</code>, + * (as it is normal for additions or deletions) but not all three. + * <p> + * <code>ICompareInput</code> provides methods for registering + * <code>ICompareInputChangeListener</code>s + * that get informed if one (or more) + * of the three sides of an <code>ICompareInput</code> object changes its value. + * <p> + * For example when accepting an incoming addition + * the (non-<code>null</code>) left side of an <code>ICompareInput</code> + * is copied to the right side by means of method <code>copy</code>. + * This should trigger a call to <code>compareInputChanged</code> of registered + * <code>ICompareInputChangeListener</code>s. + * <p> + * Clients can implement this interface, or use the convenience implementation + * <code>DiffNode</code>. + * </p> + * + * @see StructureDiffViewer + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer + * @see DiffNode + */ +public interface ICompareInput { + + /** + * Returns name of input. + * This name is displayed when this input is shown in a viewer. + * In many cases this name is the name of one of the non-<code>null</code> sides or a combination + * thereof. + * + * @return name of input + */ + String getName(); + + /** + * Returns an image representing this input. + * This image is typically displayed when this input is shown in a viewer. + * In many cases this image is the image of one of the non-<code>null</code> sides. + * + * @return image representing this input, or <code>null</code> if no icon should be shown + */ + Image getImage(); + + /** + * Returns the kind of difference between the + * three sides ancestor, left and right. + * This field is only meaningful if the <code>ICompareInput</code> + * is the result of another compare. In this case it is used + * together with <code>getImage</code> to compose a icon + * which reflects the kind of difference between the two or three elements. + * + * @return kind of difference (see <code>Differencer</code>) + */ + int getKind(); + + /** + * Returns the ancestor side of this input. + * Returns <code>null</code> if this input has no ancestor + * or in the two-way compare case. + * + * @return the ancestor of this input, or <code>null</code> + */ + ITypedElement getAncestor(); + + /** + * Returns the left side of this input. + * Returns <code>null</code> if there is no left side (deletion or addition). + * + * @return the left side of this input, or <code>null</code> + */ + ITypedElement getLeft(); + + /** + * Returns the right side of this input. + * Returns <code>null</code> if there is no right side (deletion or addition). + * + * @return the right side of this input, or <code>null</code> + */ + ITypedElement getRight(); + + /** + * Registers the given listener for notification. + * If the identical listener is already registered the method has no effect. + * + * @param listener the listener to register for changes of this input + */ + void addCompareInputChangeListener(ICompareInputChangeListener listener); + + /** + * Unregisters the given listener. + * If the identical listener is not registered the method has no effect. + * + * @param listener the listener to unregister + */ + void removeCompareInputChangeListener(ICompareInputChangeListener listener); + + /** + * Copy one side (source) to the other side (destination) depending on the + * value of <code>leftToRight</code>. This method is called from + * a merge viewer if a corresponding action ("take left" or "take right") + * has been pressed. + * <p> + * The implementation should handle the following cases: + * <UL> + * <LI> + * if the source side is <code>null</code> the destination must be deleted, + * <LI> + * if the destination is <code>null</code> the destination must be created + * and filled with the contents from the source, + * <LI> + * if both sides are non-<code>null</code> the contents of source must be copied to destination. + * </UL> + * In addition the implementation should send out notification to the registered + * <code>ICompareInputChangeListener</code>. + * + * @param leftToRight if <code>true</code> the left side is copied to the right side. + * If <code>false</code> the right side is copied to the left side + */ + void copy(boolean leftToRight); +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInputChangeListener.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInputChangeListener.java new file mode 100644 index 000000000..e18760f6c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInputChangeListener.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +/** + * Listener that gets informed if one (or more) + * of the three sides of an <code>ICompareInput</code> object changes its value. + * <p> + * For example when accepting an incoming addition + * the (non-null) left side of an <code>ICompareInput</code> + * is copied to the right side (which was <code>null</code>). + * This triggers a call to <code>compareInputChanged</code> of registered + * <code>ICompareInputChangeListener</code>. + * <p> + * Note however, that listener are not informed if the content of one of the sides changes. + * <p> + * Clients may implement this interface. It is also implemented by viewers that take + * an <code>ICompareInput</code> as input. + * </p> + */ +public interface ICompareInputChangeListener { + + /** + * Called whenever the value (not the content) of one or more of the three sides + * of a <code>ICompareInput</code> has changed. + * + * @param source the <code>ICompareInput</code> that has changed + */ + void compareInputChanged(ICompareInput source); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffContainer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffContainer.java new file mode 100644 index 000000000..baecfcb14 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffContainer.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +/** + * <code>IDiffContainer</code> is a <code>IDiffElement</code> with children. + * <p> + * <code>IDiffContainer</code> are the inner nodes displayed + * by the <code>DiffTreeViewer</code>. + * <code>IDiffContainer</code> are typically created as the result of performing + * a compare with the <code>Differencer</code>. + * <p> + * Clients may implement this interface, or use one of the standard implementations, + * <code>DiffContainer</code> or <code>DiffNode</code>. + * + * @see Differencer + * @see DiffTreeViewer + */ +public interface IDiffContainer extends IDiffElement { + + /** + * Returns whether this container has at least one child. + * In some cases this methods avoids having to call the + * potential more costly <code>getChildren</code> method. + * + * @return <code>true</code> if this container has at least one child + */ + boolean hasChildren(); + + /** + * Returns the children of this container. + * If this container has no children an empty array is returned (not <code>null</code>). + * + * @return the children of this container as an array + */ + IDiffElement[] getChildren(); + + /** + * Adds the given child to this container. + * If the child is already contained in this container, this method has no effect. + * + * @param child the child to be added to this container + */ + void add(IDiffElement child); + + /** + * Removes the given child from this container. + * If the container becomes empty it is removed from its container. + * If the child is not contained in this container, this method has no effect. + * + * @param child the child to be removed from this container + */ + void removeToRoot(IDiffElement child); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffElement.java new file mode 100644 index 000000000..a22f0e6e2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffElement.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +import org.eclipse.compare.ITypedElement; + +/** + * An <code>IDiffElement</code> is used in the <code>DiffTreeViewer</code> + * to display the kind of change detected as the result of a two-way or three-way compare. + * <p> + * The base interface <code>ITypedElement</code> provides a name, a type, and an image. + * <code>IDiffElement</code> adds API for maintaining a parent relationship. + * <p> + * <code>DiffTreeViewer</code> works on a tree of <code>IDiffElements</code>. + * Leaf elements must implement the + * <code>IDiffElement</code> interface, inner nodes the <code>IDiffContainer</code> interface. + * <p> + * <code>IDiffElement</code>s are typically created as the result of performing + * a compare with the <code>Differencer</code>. + * <p> + * Clients may implement this interface, or use one of the standard implementations, + * <code>DiffElement</code>, <code>DiffContainer</code>, or <code>DiffNode</code>. + * + * @see DiffTreeViewer + * @see DiffElement + * @see DiffContainer + * @see DiffNode + */ +public interface IDiffElement extends ITypedElement { + + /** + * Returns the kind of difference as defined in <code>Differencer</code>. + * + * @return the kind of difference as defined in <code>Differencer</code> + */ + int getKind(); + + /** + * Returns the parent of this element. + * If the object is the root of a hierarchy <code>null</code> is returned. + * + * @return the parent of this element, or <code>null</code> if the element has no parent + */ + IDiffContainer getParent(); + + /** + * Sets the parent of this element. + * + * @param parent the new parent of this element, or <code>null</code> if this + * element is to have no parent + */ + void setParent(IDiffContainer parent); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureComparator.java new file mode 100644 index 000000000..4bc67670b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureComparator.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.structuremergeviewer; + +/** + * Interface used to compare hierarchical structures. + * It is used by the differencing engine. + * <p> + * Clients typically implement this interface in an adaptor class which + * wrappers the objects to be compared. + * + * @see org.eclipse.compare.ResourceNode + * @see Differencer + */ +public interface IStructureComparator { + + /** + * Returns an iterator for all children of this object or <code>null</code> + * if there are no children. + * + * @return an array with all children of this object, or an empty array if there are no children + */ + Object[] getChildren(); + + /** + * Returns whether some other object is "equal to" this one + * with respect to a structural comparison. For example, when comparing + * Java class methods, <code>equals</code> would return <code>true</code> + * if two methods have the same signature (the argument names and the + * method body might differ). + * + * @param other the reference object with which to compare + * @return <code>true</code> if this object is the same as the other argument; <code>false</code> otherwise + * @see java.lang.Object#equals + */ + boolean equals(Object other); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator.java new file mode 100644 index 000000000..71e6b3268 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.structuremergeviewer; + +/** + * Interface used to create a hierarchical structure of + * <code>IStructureComparator</code>s for a given input object. + * In addition, it provides methods for locating a path in the hierarchical structure + * and to map a node of this structure back to the corresponding input object. + * <p> + * Structure creators are used in the following contexts: + * <ul> + * <li> + * the <code>StructureDiffViewer</code> uses an <code>IStructureCreator</code> to + * build two (or three) tree structures of its input elements (method <code>getStructure</code>). + * These trees are then compared with each other by means of the differencing engine and displayed + * with the <code>DiffTreeViewer</code>, + * </li> + * <li> + * the <code>ReplaceWithEditionDialog</code> uses an <code>IStructureCreator</code> + * to map a path back to a range of characters in the textual representation. + * </li> + * </ul> + * A <code>IStructureCreator</code> provides methods for rewriting the tree produced by the differencing + * engine to support "smart" structural differencing. E.g. certain patterns of pairs of "addition" + * and "deletion" nodes can be detected as renames and merged into a single node. + * </p> + * <p> + * Clients may implement this interface; there is no standard implementation. + * </p> + * + * @see StructureDiffViewer + * @see org.eclipse.compare.EditionSelectionDialog + * @see Differencer + */ +public interface IStructureCreator { + + /** + * Returns a descriptive name which can be used in the UI of the <code>StructureDiffViewer</code>. + * + * @return a descriptive name for this <code>IStructureCreator</code> + */ + String getName(); + + /** + * Creates a tree structure consisting of <code>IStructureComparator</code>s + * from the given object and returns its root object. + * Implementing this method typically involves parsing the input object. + * In case of an error (e.g. a parsing error) the value <code>null</code> is returned. + * + * @param input the object from which to create the tree of <code>IStructureComparator</code> + * @return the root node of the structure or <code>null</code> in case of error + */ + IStructureComparator getStructure(Object input); + + /** + * Creates the single node specified by path from the given input object. + * In case of an error (e.g. a parsing error) the value <code>null</code> is returned. + * This method is similar to <code>getStructure</code> but in + * contrast to <code>getStructure</code> only a single node without any children must be returned. + * This method is used in the <code>ReplaceWithEditionDialog</code> to locate a sub element + * (e.g. a method) within an input object (e.g. a file containing source code). + * <p> + * One (not optimized) approach to implement this method is calling <code>getStructure(input)</code> + * to build the full tree, and then finding that node within the tree that is specified + * by <code>path</code>. + * <p> + * The syntax of <code>path</code> is not specified, because it is treated by the compare subsystem + * as an opaque entity and is not further interpreted. Clients using this functionality + * will pass a value of <code>path</code> to the <code>selectEdition</code> + * method of <code>ReplaceWithEditionDialog</code> and will receive this value unchanged + * as an argument to <code>locate</code>. + * + * @param path specifies a sub object within the input object + * @param input the object from which to create the <code>IStructureComparator</code> + * @return the single node specified by <code>path</code> or <code>null</code> + * + */ + IStructureComparator locate(Object path, Object input); + + /** + * Returns the contents of the given node as a string for the purpose + * of performing a content comparison only (that is the string will not be visible in the UI). + * If <code>ignoreWhitespace</code> is <code>true</code> all character sequences considered + * whitespace should be removed from the returned string. + * + * @param node the node for which to return a string representation + * @param ignoreWhitespace if <code>true</code> the returned string should not contain whitespace + * @return the string contents of the given node + */ + String getContents(Object node, boolean ignoreWhitespace); + + /** + * Called whenever a copy operation has been performed on a tree node. + * + * @param node the node for which to save the new content + * @param input the object from which the structure tree was created in <code>getStructure</code> + */ + void save(IStructureComparator node, Object input); +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator2.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator2.java new file mode 100644 index 000000000..55bdf7914 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator2.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.structuremergeviewer; + +import org.eclipse.compare.ISharedDocumentAdapter; +import org.eclipse.compare.ITypedElement; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * An extension to the {@link IStructureCreator} interface that supports the + * use of shared documents. + * <p> + * This interface is not intended to be implemented by clients. Client should instead + * subclass {@link StructureCreator}. + * </p> + * @since 3.3 + */ +public interface IStructureCreator2 extends IStructureCreator { + + /** + * Creates a tree structure consisting of <code>IStructureComparator</code>s + * from the given object and returns its root object. Implementing this + * method typically involves parsing the input object. In case of an error + * (e.g. a parsing error) the value <code>null</code> is returned. + * <p> + * This method is equivalent to + * {@link IStructureCreator#getStructure(Object)} with the exception that + * the {@link #destroy(Object)} method must be called with the returned + * comparator as a parameter when the comparator is no longer + * needed. This is done to allow structure creators + * to make use of shared resources such a file buffer. + * <p> + * Also, the node returned from this method should adapt to an + * {@link ISharedDocumentAdapter} if the provided input has + * a shared document adapter and it is being used by the + * this creator. The convenience class {@link SharedDocumentAdapterWrapper} + * is provided to allow the creator to wrap the adapter of the input + * so that the proper key can be returned. + * + * @param input + * the object from which to create the tree of + * <code>IStructureComparator</code> + * @param monitor a progress monitor or <code>null</code> if progress and cancelation is not required + * @return the root node of the structure or <code>null</code> in case of + * error + * @throws CoreException + * @see IStructureCreator#getStructure(Object) + * @see #destroy(Object) + */ + IStructureComparator createStructure(Object input, IProgressMonitor monitor) throws CoreException; + + /** + * Creates the single node specified by path from the given input object. + * This method is equivalent to + * {@link IStructureCreator#locate(Object, Object)} with the exception that + * the {@link #destroy(Object)} method must be called with the returned + * element as a parameter when the element is no longer + * needed. This is done to allow structure creators + * to make use of shared resources such a file buffer. + * + * @param element specifies a sub object within the input object + * @param input the object from which to create the + * <code>ITypedElement</code> + * @param monitor a progress monitor or <code>null</code> if progress is not desired + * @return the single node specified by <code>path</code> or + * <code>null</code> + * @throws CoreException if an error occurs while parsing the input + * + * @see IStructureCreator#locate(Object, Object) + * @see #destroy(Object) + */ + ITypedElement createElement(Object element, Object input, IProgressMonitor monitor) throws CoreException; + + /** + * Release any resources associated with the given object. + * This method must be called for objects returned from either + * {@link #createStructure(Object, IProgressMonitor)} or + * {@link #createElement(Object, Object, IProgressMonitor)}. + * @param object the object to be destroyed + * @see #createElement(Object, Object, IProgressMonitor) + * @see #createStructure(Object, IProgressMonitor) + */ + void destroy(Object object); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/SharedDocumentAdapterWrapper.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/SharedDocumentAdapterWrapper.java new file mode 100644 index 000000000..b1fcd0ff0 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/SharedDocumentAdapterWrapper.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.structuremergeviewer; + +import org.eclipse.compare.ISharedDocumentAdapter; +import org.eclipse.compare.SharedDocumentAdapter; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.texteditor.IDocumentProvider; + +/** + * An implementation of {@link ISharedDocumentAdapter} that wraps another + * shared document adapter. + * <p> + * Clients may subclass this class. + * </p> + * @since 3.3 + */ +public class SharedDocumentAdapterWrapper implements ISharedDocumentAdapter { + + private ISharedDocumentAdapter wrappedAdapter; + + /** + * Helper method that returns the shared document adapter for the + * given typed element or <code>null</code> if there isn't one. + * @param element the typed element + * @return the shared document adapter for the given typed element + * or <code>null</code> + */ + public static ISharedDocumentAdapter getAdapter(Object element) { + return (ISharedDocumentAdapter)Utilities.getAdapter(element, ISharedDocumentAdapter.class, true); + } + + /** + * Create a shared document adapter that wraps the given adapter. + * @param wrappedAdapter the wrapped adapter + */ + public SharedDocumentAdapterWrapper(ISharedDocumentAdapter wrappedAdapter) { + super(); + this.wrappedAdapter = wrappedAdapter; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#connect(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput) + */ + public void connect(IDocumentProvider provider, IEditorInput documentKey) + throws CoreException { + wrappedAdapter.connect(provider, documentKey); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#disconnect(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput) + */ + public void disconnect(IDocumentProvider provider, IEditorInput documentKey) { + wrappedAdapter.disconnect(provider, documentKey); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#getDocumentKey(java.lang.Object) + */ + public IEditorInput getDocumentKey(Object element) { + return wrappedAdapter.getDocumentKey(element); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#saveDocument(org.eclipse.ui.texteditor.IDocumentProvider, org.eclipse.ui.IEditorInput, org.eclipse.jface.text.IDocument, boolean, org.eclipse.core.runtime.IProgressMonitor) + */ + public void flushDocument(IDocumentProvider provider, + IEditorInput documentKey, IDocument document, boolean overwrite) throws CoreException { + wrappedAdapter.flushDocument(provider, documentKey, document, overwrite); + } + + /** + * Return the wrapped adapter. + * @return the wrapped adapter + */ + public final ISharedDocumentAdapter getWrappedAdapter() { + return wrappedAdapter; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ISharedDocumentAdapter#disconnect(java.lang.Object) + */ + public void disconnect(Object element) { + IEditorInput input = getDocumentKey(element); + if (input == null) + return; + IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(input); + if (provider == null) + return; + disconnect(provider, input); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java new file mode 100644 index 000000000..799afba98 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java @@ -0,0 +1,433 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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.structuremergeviewer; + +import java.io.UnsupportedEncodingException; + +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IEditableContent; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ISharedDocumentAdapter; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.SharedDocumentAdapter; +import org.eclipse.compare.contentmergeviewer.IDocumentRange; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentExtension3; +import org.eclipse.jface.text.IDocumentPartitioner; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.services.IDisposable; +import org.eclipse.ui.texteditor.IDocumentProvider; + +/** + * An {@link IStructureCreator2} that attempts to use an {@link IDocumentProvider} + * to obtain a shared document for an {@link ITypedElement}. + * <p> + * Clients may subclass this class. + * </p> + * + * @since 3.3 + */ +public abstract class StructureCreator implements IStructureCreator2 { + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.IStructureCreator#getStructure(java.lang.Object) + */ + public IStructureComparator getStructure(Object input) { + String contents= null; + IDocument doc= CompareUI.getDocument(input); + if (doc == null) { + if (input instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) input; + try { + contents= Utilities.readString(sca); + } catch (CoreException e) { + // return null indicates the error. + CompareUIPlugin.log(e); + return null; + } + } + + if (contents == null) { + // Node has no contents + return null; + } + + doc= new Document(contents); + setupDocument(doc); + } + + try { + return createStructureComparator(input, doc, null, null); + } catch (CoreException e) { + CompareUIPlugin.log(e); + return null; + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.IStructureCreator2#createStructure(java.lang.Object, org.eclipse.core.runtime.IProgressMonitor) + */ + public IStructureComparator createStructure(final Object element, final IProgressMonitor monitor) throws CoreException { + final IStructureComparator[] result = new IStructureComparator[] { null }; + Runnable runnable = new Runnable() { + public void run() { + try { + result[0]= internalCreateStructure(element, monitor); + } catch (OperationCanceledException ex) { + return; + } + } + }; + Utilities.runInUIThread(runnable); + return result[0]; + } + + /* + * We need to create the structure in the UI thread since IDocument requires this + */ + private IStructureComparator internalCreateStructure(Object element, + IProgressMonitor monitor) { + final ISharedDocumentAdapter sda = SharedDocumentAdapterWrapper.getAdapter(element); + if (sda != null) { + final IEditorInput input = sda.getDocumentKey(element); + if (input != null) { + final IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(input); + if (provider != null) { + try { + sda.connect(provider, input); + IDocument document = provider.getDocument(input); + setupDocument(document); + return createStructureComparator(element, document, wrapSharedDocumentAdapter(sda, element, document), monitor); + } catch (CoreException e) { + // Connection to the document provider failed. + // Log and fall through to use simple structure + CompareUIPlugin.log(e); + } + } + } + } + return getStructure(element); + } + + /** + * Create an {@link IStructureComparator} for the given element using the + * contents available in the given document. If the provided + * {@link ISharedDocumentAdapter} is not <code>null</code> then the + * {@link IStructureComparator} returned by this method must implement the + * {@link IDisposable} interface and disconnect from the adapter when the + * comparator is disposed. The {@link StructureDiffViewer} class will call + * dispose if the {@link IStructureComparator} also implements + * {@link IDisposable}. Other clients must do the same. + * <p> + * It should be noted that the provided {@link ISharedDocumentAdapter} + * will provide the key associated with the given element when + * {@link ISharedDocumentAdapter#getDocumentKey(Object)} is called + * for any {@link IDocumentRange} node whose document matches the + * provided document. Thus, this adapter should also be returned + * by the structure comparator and its children when they are adapted + * to an {@link ISharedDocumentAdapter}. + * @param element the element + * @param document the document that has the contents for the element + * @param sharedDocumentAdapter the shared document adapter from which the + * document was obtained or <code>null</code> if the document + * is not shared. + * @param monitor a progress monitor or <code>null</code> if progress is not required + * + * @return a structure comparator + * @throws CoreException + */ + protected abstract IStructureComparator createStructureComparator( + final Object element, IDocument document, + final ISharedDocumentAdapter sharedDocumentAdapter, + IProgressMonitor monitor) throws CoreException; + + /** + * Setup the newly created document as appropriate. Any document partitioners + * should be added to a custom slot using the {@link IDocumentExtension3} interface + * in case the document is shared via a file buffer. + * @param document a document + */ + protected void setupDocument(IDocument document) { + String partitioning = getDocumentPartitioning(); + if (partitioning == null || !(document instanceof IDocumentExtension3)) { + if (document.getDocumentPartitioner() == null) { + IDocumentPartitioner partitioner= getDocumentPartitioner(); + if (partitioner != null) { + document.setDocumentPartitioner(partitioner); + partitioner.connect(document); + } + } + } else { + IDocumentExtension3 ex3 = (IDocumentExtension3) document; + if (ex3.getDocumentPartitioner(partitioning) == null) { + IDocumentPartitioner partitioner= getDocumentPartitioner(); + if (partitioner != null) { + ex3.setDocumentPartitioner(partitioning, partitioner); + partitioner.connect(document); + } + } + } + } + + /** + * Return the partitioner to be associated with the document or + * <code>null</code> is partitioning is not needed or if the subclass + * overrode {@link #setupDocument(IDocument)} directly. + * @return a partitioner + */ + protected IDocumentPartitioner getDocumentPartitioner() { + return null; + } + + /** + * Return the partitioning to which the partitioner returned from + * {@link #getDocumentPartitioner()} is to be associated. Return <code>null</code> + * only if partitioning is not needed or if the subclass + * overrode {@link #setupDocument(IDocument)} directly. + * @see IDocumentExtension3 + * @return a partitioning + */ + protected String getDocumentPartitioning() { + return null; + } + + /** + * Default implementation of save that extracts the contents from + * the document of an {@link IDocumentRange} and sets it on the + * input. If the input is an {@link IEncodedStreamContentAccessor}, + * the charset of the input is used to extract the contents from the + * document. If the input adapts to {@link ISharedDocumentAdapter} and + * the document of the {@link IDocumentRange} matches that of the + * input, then the save is issued through the shared document adapter. + * @see org.eclipse.compare.structuremergeviewer.IStructureCreator#save(org.eclipse.compare.structuremergeviewer.IStructureComparator, java.lang.Object) + */ + public void save(IStructureComparator node, Object input) { + if (node instanceof IDocumentRange && input instanceof IEditableContent) { + IDocument document= ((IDocumentRange)node).getDocument(); + // First check to see if we have a shared document + final ISharedDocumentAdapter sda = SharedDocumentAdapterWrapper.getAdapter(input); + if (sda != null) { + IEditorInput key = sda.getDocumentKey(input); + if (key != null) { + IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(key); + if (provider != null) { + IDocument providerDoc = provider.getDocument(key); + // We have to make sure that the document we are saving is the same as the shared document + if (providerDoc != null && providerDoc == document) { + if (save(provider, document, input, sda, key)) + return; + } + } + } + } + IEditableContent bca= (IEditableContent) input; + String contents= document.get(); + String encoding= null; + if (input instanceof IEncodedStreamContentAccessor) { + try { + encoding= ((IEncodedStreamContentAccessor)input).getCharset(); + } catch (CoreException e1) { + // ignore + } + } + if (encoding == null) + encoding= ResourcesPlugin.getEncoding(); + byte[] bytes; + try { + bytes= contents.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + bytes= contents.getBytes(); + } + bca.setContent(bytes); + } + } + + private boolean save(final IDocumentProvider provider, final IDocument document, + final Object input, final ISharedDocumentAdapter sda, final IEditorInput key) { + try { + sda.flushDocument(provider, key, document, false); + return true; + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + return false; + } + + /** + * Create an {@link ISharedDocumentAdapter} that will provide the document key for the given input + * object for any {@link DocumentRangeNode} instances whose document is the same as the + * provided document. + * @param input the input element + * @param document the document associated with the input element + * @return a shared document adapter that provides the proper document key for document range nodes + */ + private final ISharedDocumentAdapter wrapSharedDocumentAdapter(ISharedDocumentAdapter elementAdapter, final Object input, final IDocument document) { + // We need to wrap the adapter so that the proper document key gets returned + return new SharedDocumentAdapterWrapper(elementAdapter) { + public IEditorInput getDocumentKey(Object element) { + if (hasSameDocument(element)) { + return super.getDocumentKey(input); + } + return super.getDocumentKey(element); + } + private boolean hasSameDocument(Object element) { + if (element instanceof DocumentRangeNode) { + DocumentRangeNode drn = (DocumentRangeNode) element; + return drn.getDocument() == document; + } + return false; + } + }; + } + + /** + * Default implementation of {@link #createElement(Object, Object, IProgressMonitor)} + * that uses {@link #getPath(Object, Object)} to determine the + * path for the element, {@link #createStructure(Object, IProgressMonitor)} to create the structure + * and {@link #findElement(IStructureComparator, String[])} to find the + * element in the structure. Subclasses may override. + * @param element the element + * @param input the containing input + * @param monitor a progress monitor + * @return the sub-structure element in the input for the given element + * @throws CoreException if a parse error occurred + */ + public ITypedElement createElement(Object element, Object input, IProgressMonitor monitor) + throws CoreException { + String[] path= getPath(element, input); + if (path == null) { + // TODO: Temporary code until subclasses are updated + IStructureComparator locate = locate(element, input); + if (locate instanceof ITypedElement) { + return (ITypedElement)locate; + } + return null; + } + + // Build the structure + IStructureComparator structure= createStructure(input, monitor); + if (structure == null) // we couldn't parse the structure + return null; // so we can't find anything + + // find the path in the tree + return findElement(structure, path); + } + + /** + * Default implementation of {@link #locate(Object, Object)} that + * uses {@link #getPath(Object, Object)} to determine the + * path for the element, {@link #getStructure(Object)} to create the structure + * and {@link #findElement(IStructureComparator, String[])} to find the + * element in the structure. Subclasses may override. + * @param element the element + * @param input the containing input + * @return the sub-structure element in the input for the given element + */ + public IStructureComparator locate(Object element, Object input) { + String[] path= getPath(element, input); + if (path == null) + return null; + // Build the structure + IStructureComparator structure= getStructure(input); + if (structure == null) // we couldn't parse the structure + return null; // so we can't find anything + + // find the path in the tree + return (IStructureComparator)findElement(structure, path); + } + + /** + * Find the element at the given path in the given structure. + * This method is invoked from the {@link #createElement(Object, Object, IProgressMonitor)} + * and {@link #locate(Object, Object)} methods to find the element for + * the given path. + * @param structure the structure + * @param path the path of an element in the structure + * @return the element at the given path in the structure or <code>null</code> + */ + protected ITypedElement findElement(IStructureComparator structure, String[] path) { + return (ITypedElement)find(structure, path, 0); + } + + /** + * Recursively extracts the given path from the tree. + */ + private IStructureComparator find(IStructureComparator tree, String[] path, int index) { + if (tree != null) { + Object[] children= tree.getChildren(); + if (children != null) { + for (int i= 0; i < children.length; i++) { + IStructureComparator child= (IStructureComparator) children[i]; + if (child instanceof ITypedElement && child instanceof DocumentRangeNode) { + String n1= null; + if (child instanceof DocumentRangeNode) + n1= ((DocumentRangeNode)child).getId(); + if (n1 == null) + n1= ((ITypedElement)child).getName(); + String n2= path[index]; + if (n1.equals(n2)) { + if (index == path.length-1) + return child; + IStructureComparator result= find(child, path, index+1); + if (result != null) + return result; + } + } + } + } + } + return null; + } + + /** + * Return the path of the element in the structure of it's containing input + * or <code>null</code> if the element is not contained in the input. This method is + * invoked from {@link #createElement(Object, Object, IProgressMonitor)} and + * {@link #locate(Object, Object)} methods to determine + * the path to be passed to {@link #findElement(IStructureComparator, String[])}. + * By default, <code>null</code> is returned. Subclasses may override. + * @param element the element + * @param input the input + * @return the path of the element in the structure of it's containing input + * or <code>null</code> + */ + protected String[] getPath(Object element, Object input) { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.IStructureCreator2#destroy(java.lang.Object) + */ + public void destroy(Object object) { + IDisposable disposable = getDisposable(object); + if (disposable != null) + disposable.dispose(); + } + + private IDisposable getDisposable(Object object) { + if (object instanceof IDisposable) { + return (IDisposable) object; + } + if (object instanceof DocumentRangeNode) { + DocumentRangeNode node = (DocumentRangeNode) object; + return getDisposable(node.getParentNode()); + } + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java new file mode 100644 index 000000000..4b8700659 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java @@ -0,0 +1,682 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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.structuremergeviewer; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.IDocumentRange; +import org.eclipse.compare.internal.*; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.widgets.*; +import org.eclipse.ui.services.IDisposable; + + +/** + * A diff tree viewer that can be configured with a <code>IStructureCreator</code> + * to retrieve a hierarchical structure from the input object (an <code>ICompareInput</code>) + * and perform a two-way or three-way compare on it. + * <p> + * This class may be instantiated; it is not intended to be subclassed outside + * this package. + * </p> + * + * @see IStructureCreator + * @see ICompareInput + * @noextend This class is not intended to be subclassed by clients. + */ + +public class StructureDiffViewer extends DiffTreeViewer { + private Differencer fDifferencer; + private boolean fThreeWay= false; + + private StructureInfo fAncestorStructure = new StructureInfo(); + private StructureInfo fLeftStructure = new StructureInfo(); + private StructureInfo fRightStructure = new StructureInfo(); + + private IStructureCreator fStructureCreator; + private IDiffContainer fRoot; + private IContentChangeListener fContentChangedListener; + private CompareViewerSwitchingPane fParent; + private ICompareInputChangeListener fCompareInputChangeListener; + + /* + * A set of background tasks for updating the structure + */ + private IRunnableWithProgress diffTask = new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException { + monitor.beginTask(CompareMessages.StructureDiffViewer_0, 100); + diff(new SubProgressMonitor(monitor, 100)); + monitor.done(); + } + }; + + private IRunnableWithProgress inputChangedTask = new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException { + monitor.beginTask(CompareMessages.StructureDiffViewer_1, 100); + // TODO: Should we always force + compareInputChanged((ICompareInput)getInput(), true, new SubProgressMonitor(monitor, 100)); + monitor.done(); + } + }; + + /* + * A helper class for holding the input and generated structure + * for the ancestor, left and right inputs. + */ + private class StructureInfo { + private ITypedElement fInput; + private IStructureComparator fStructureComparator; + private IRunnableWithProgress refreshTask = new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException { + refresh(monitor); + } + }; + + public boolean setInput(ITypedElement newInput, boolean force, IProgressMonitor monitor) { + boolean changed = false; + if (force || newInput != fInput) { + removeDocumentRangeUpdaters(); + if (fInput instanceof IContentChangeNotifier && fContentChangedListener != null) + ((IContentChangeNotifier)fInput).removeContentChangeListener(fContentChangedListener); + fInput= newInput; + if (fInput == null) { + dispose(); // destroy fStructureComparator + fStructureComparator= null; + } else { + refresh(monitor); + changed= true; + } + if (fInput instanceof IContentChangeNotifier && fContentChangedListener != null) + ((IContentChangeNotifier)fInput).addContentChangeListener(fContentChangedListener); + } + return changed; + } + + /** + * Remove any document range updaters that were registered against the document. + */ + private void removeDocumentRangeUpdaters() { + if (fStructureComparator instanceof IDocumentRange) { + IDocument doc = ((IDocumentRange) fStructureComparator).getDocument(); + try { + doc.removePositionCategory(IDocumentRange.RANGE_CATEGORY); + } catch (BadPositionCategoryException ex) { + // Ignore + } + } + } + + public IStructureComparator getStructureComparator() { + return fStructureComparator; + } + + public void refresh(IProgressMonitor monitor) { + IStructureComparator oldComparator = fStructureComparator; + fStructureComparator= createStructure(monitor); + // Dispose of the old one after in case they are using a shared document + // (i.e. disposing it after will hold on to a reference to the document + // so it doesn't get freed and reloaded) + if (oldComparator instanceof IDisposable) { + IDisposable disposable = (IDisposable) oldComparator; + disposable.dispose(); + } + } + + public Object getInput() { + return fInput; + } + + private IStructureComparator createStructure(IProgressMonitor monitor) { + // Defend against concurrent disposal + Object input = fInput; + if (input == null) + return null; + if (fStructureCreator instanceof IStructureCreator2) { + IStructureCreator2 sc2 = (IStructureCreator2) fStructureCreator; + try { + return sc2.createStructure(input, monitor); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + } + return fStructureCreator.getStructure(input); + } + + public void dispose() { + if (fStructureComparator != null && fStructureCreator instanceof IStructureCreator2) { + IStructureCreator2 sc2 = (IStructureCreator2) fStructureCreator; + sc2.destroy(fStructureComparator); + } + } + + public IRunnableWithProgress getRefreshTask() { + return refreshTask; + } + } + + /** + * Creates a new viewer for the given SWT tree control with the specified configuration. + * + * @param tree the tree control + * @param configuration the configuration for this viewer + */ + public StructureDiffViewer(Tree tree, CompareConfiguration configuration) { + super(tree, configuration); + Composite c= tree.getParent(); + if (c instanceof CompareViewerSwitchingPane) + fParent= (CompareViewerSwitchingPane) c; + initialize(); + } + + /** + * Creates a new viewer under the given SWT parent with the specified configuration. + * + * @param parent the SWT control under which to create the viewer + * @param configuration the configuration for this viewer + */ + public StructureDiffViewer(Composite parent, CompareConfiguration configuration) { + super(parent, configuration); + if (parent instanceof CompareViewerSwitchingPane) + fParent= (CompareViewerSwitchingPane) parent; + initialize(); + } + + private void initialize() { + + setAutoExpandLevel(3); + + fContentChangedListener= new IContentChangeListener() { + public void contentChanged(IContentChangeNotifier changed) { + StructureDiffViewer.this.contentChanged(changed); + } + }; + fCompareInputChangeListener = new ICompareInputChangeListener() { + public void compareInputChanged(ICompareInput input) { + StructureDiffViewer.this.compareInputChanged(input, true); + } + }; + } + + /** + * Configures the <code>StructureDiffViewer</code> with a structure creator. + * The structure creator is used to create a hierarchical structure + * for each side of the viewer's input element of type <code>ICompareInput</code>. + * + * @param structureCreator the new structure creator + */ + public void setStructureCreator(IStructureCreator structureCreator) { + if (fStructureCreator != structureCreator) { + fStructureCreator= structureCreator; + Control tree= getControl(); + if (tree != null && !tree.isDisposed()) + tree.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle()); + } + } + + /** + * Returns the structure creator or <code>null</code> if no + * structure creator has been set with <code>setStructureCreator</code>. + * + * @return the structure creator or <code>null</code> + */ + public IStructureCreator getStructureCreator() { + return fStructureCreator; + } + + /** + * Reimplemented to get the descriptive title for this viewer from the <code>IStructureCreator</code>. + * @return the viewer's name + */ + public String getTitle() { + if (fStructureCreator != null) + return fStructureCreator.getName(); + return super.getTitle(); + } + + /** + * Overridden because the input of this viewer is not identical to the root of the tree. + * The tree's root is a IDiffContainer that was returned from the method <code>diff</code>. + * + * @return the root of the diff tree produced by method <code>diff</code> + */ + protected Object getRoot() { + return fRoot; + } + + /* + * (non-Javadoc) Method declared on StructuredViewer. + * Overridden to create the comparable structures from the input object + * and to feed them through the differencing engine. Note: for this viewer + * the value from <code>getInput</code> is not identical to <code>getRoot</code>. + */ + protected void inputChanged(Object input, Object oldInput) { + if (oldInput instanceof ICompareInput) { + ICompareInput old = (ICompareInput) oldInput; + old.removeCompareInputChangeListener(fCompareInputChangeListener); + } + if (input instanceof ICompareInput) { + ICompareInput ci = (ICompareInput) input; + ci.addCompareInputChangeListener(fCompareInputChangeListener); + compareInputChanged(ci); + if (input != oldInput) + initialSelection(); + } + } + + protected void initialSelection() { + expandToLevel(2); + } + + /* (non Javadoc) + * Overridden to unregister all listeners. + */ + protected void handleDispose(DisposeEvent event) { + Object input = getInput(); + if (input instanceof ICompareInput) { + ICompareInput ci = (ICompareInput) input; + ci.removeCompareInputChangeListener(fCompareInputChangeListener); + } + compareInputChanged(null); + fContentChangedListener= null; + super.handleDispose(event); + } + + /** + * Recreates the comparable structures for the input sides. + * @param input this viewer's new input + */ + protected void compareInputChanged(ICompareInput input) { + compareInputChanged(input, false); + } + + /* package */ void compareInputChanged(final ICompareInput input, final boolean force) { + if (input == null) { + // When closing, we don't need a progress monitor to handle the input change + compareInputChanged(input, force, null); + return; + } + CompareConfiguration cc = getCompareConfiguration(); + // The compare configuration is nulled when the viewer is disposed + if (cc != null) { + BusyIndicator.showWhile(Display.getDefault(), new Runnable() { + public void run() { + try { + inputChangedTask.run(new NullProgressMonitor()); + } catch (InvocationTargetException e) { + CompareUIPlugin.log(e.getTargetException()); + } catch (InterruptedException e) { + // Ignore + } + } + }); + } + } + + /* package */ void compareInputChanged(ICompareInput input, boolean force, IProgressMonitor monitor) { + ITypedElement t= null; + boolean changed= false; + + if (input != null) + t= input.getAncestor(); + fThreeWay= (t != null); + beginWork(monitor, 400); + try { + if (fAncestorStructure.setInput(t, force, subMonitor(monitor, 100))) + changed = true; + + if (input != null) + t= input.getLeft(); + if (fLeftStructure.setInput(t, force, subMonitor(monitor, 100))) + changed = true; + + if (input != null) + t= input.getRight(); + if (fRightStructure.setInput(t, force, subMonitor(monitor, 100))) + changed = true; + + // The compare configuration is nulled when the viewer is disposed + CompareConfiguration cc = getCompareConfiguration(); + if (changed && cc != null) + cc.getContainer().runAsynchronously(diffTask); + } finally { + endWork(monitor); + } + } + + private void endWork(IProgressMonitor monitor) { + if (monitor != null) + monitor.done(); + } + + private IProgressMonitor subMonitor(IProgressMonitor monitor, int work) { + if (monitor != null) { + if (monitor.isCanceled() || getControl().isDisposed()) + throw new OperationCanceledException(); + return new SubProgressMonitor(monitor, work); + } + return null; + } + + private void beginWork(IProgressMonitor monitor, int totalWork) { + if (monitor != null) + monitor.beginTask(null, totalWork); + } + + /** + * Calls <code>diff</code> whenever the byte contents changes. + * @param changed the object that sent out the notification + */ + protected void contentChanged(final IContentChangeNotifier changed) { + + if (fStructureCreator == null) + return; + + if (changed == null) { + getCompareConfiguration().getContainer().runAsynchronously(fAncestorStructure.getRefreshTask()); + getCompareConfiguration().getContainer().runAsynchronously(fLeftStructure.getRefreshTask()); + getCompareConfiguration().getContainer().runAsynchronously(fRightStructure.getRefreshTask()); + } else if (changed == fAncestorStructure.getInput()) { + getCompareConfiguration().getContainer().runAsynchronously(fAncestorStructure.getRefreshTask()); + } else if (changed == fLeftStructure.getInput()) { + getCompareConfiguration().getContainer().runAsynchronously(fLeftStructure.getRefreshTask()); + } else if (changed == fRightStructure.getInput()) { + getCompareConfiguration().getContainer().runAsynchronously(fRightStructure.getRefreshTask()); + } else { + return; + } + getCompareConfiguration().getContainer().runAsynchronously(diffTask); + } + + /** + * This method is called from within <code>diff()</code> before the + * difference tree is being built. Clients may override this method to + * perform their own pre-processing. This default implementation does + * nothing. + * + * @param ancestor the ancestor input to the differencing operation + * @param left the left input to the differencing operation + * @param right the right input to the differencing operation + * @since 2.0 + * @deprecated Clients should override + * {@link #preDiffHook(IStructureComparator, IStructureComparator, IStructureComparator, IProgressMonitor)} + */ + protected void preDiffHook(IStructureComparator ancestor, IStructureComparator left, IStructureComparator right) { + // we do nothing here + } + + /** + * This method is called from within {@link #diff(IProgressMonitor)} before + * the difference tree is being built. This method may be called from a + * background (non-UI) thread). + * <p> + * For backwards compatibility, this default implementation calls + * {@link #preDiffHook(IStructureComparator, IStructureComparator, IStructureComparator)} + * from the UI thread. Clients should override this method even if they + * don't perform pre-processing to avoid the call to the UI thread. + * + * @param ancestor the ancestor input to the differencing operation + * @param left the left input to the differencing operation + * @param right the right input to the differencing operation + * @param monitor a progress monitor or null if progress is not required + * @since 3.3 + */ + protected void preDiffHook(final IStructureComparator ancestor, final IStructureComparator left, final IStructureComparator right, IProgressMonitor monitor) { + syncExec(new Runnable() { + public void run() { + preDiffHook(ancestor, left, right); + } + }); + } + + /** + * Runs the difference engine and refreshes the tree. This method may be called + * from a background (non-UI) thread). + * @param monitor a progress monitor or <code>null</code> if progress in not required + */ + protected void diff(IProgressMonitor monitor) { + try { + beginWork(monitor, 150); + + IStructureComparator ancestorComparator = fAncestorStructure.getStructureComparator(); + IStructureComparator leftComparator = fLeftStructure.getStructureComparator(); + IStructureComparator rightComparator = fRightStructure.getStructureComparator(); + + preDiffHook(ancestorComparator, + leftComparator, + rightComparator, + subMonitor(monitor, 25)); + + String message= null; + + if ((fThreeWay && ancestorComparator == null) || leftComparator == null || rightComparator == null) { + // could not get structure of one (or more) of the legs + fRoot= null; + message= CompareMessages.StructureDiffViewer_StructureError; + + } else { // calculate difference of the two (or three) structures + + if (fDifferencer == null) + fDifferencer= new Differencer() { + protected boolean contentsEqual(Object o1, Object o2) { + return StructureDiffViewer.this.contentsEqual(o1, o2); + } + protected Object visit(Object data, int result, Object ancestor, Object left, Object right) { + Object o= super.visit(data, result, ancestor, left, right); + if (fLeftIsLocal && o instanceof DiffNode) + ((DiffNode)o).swapSides(fLeftIsLocal); + return o; + } + }; + + fRoot= (IDiffContainer) fDifferencer.findDifferences(fThreeWay, subMonitor(monitor, 100), null, + ancestorComparator, leftComparator, rightComparator); + + if (fRoot == null || fRoot.getChildren().length == 0) { + message= CompareMessages.StructureDiffViewer_NoStructuralDifferences; + } else { + postDiffHook(fDifferencer, fRoot, subMonitor(monitor, 25)); + } + } + + if (Display.getCurrent() != null) + refreshAfterDiff(message); + else { + final String theMessage = message; + Display.getDefault().asyncExec(new Runnable() { + public void run() { + refreshAfterDiff(theMessage); + } + }); + } + } finally { + endWork(monitor); + } + } + + private void refreshAfterDiff(String message) { + if (getControl().isDisposed()) + return; + if (fParent != null) + fParent.setTitleArgument(message); + + refresh(getRoot()); + // Setting the auto-expand level doesn't do anything for refreshes + expandToLevel(3); + } + + /** + * Runs the difference engine and refreshes the tree. + */ + protected void diff() { + try { + CompareConfiguration compareConfiguration = getCompareConfiguration(); + // A null compare configuration indicates that the viewer was disposed + if (compareConfiguration != null) { + compareConfiguration.getContainer().run(true, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(CompareMessages.StructureDiffViewer_2, 100); + diffTask.run(new SubProgressMonitor(monitor, 100)); + monitor.done(); + } + }); + } + } catch (InvocationTargetException e) { + // Shouldn't happen since the run doesn't throw + CompareUIPlugin.log(e.getTargetException()); + handleFailedRefresh(e.getTargetException().getMessage()); + } catch (InterruptedException e) { + // Canceled by user + handleFailedRefresh(CompareMessages.StructureDiffViewer_3); + } + } + + private void handleFailedRefresh(final String message) { + Runnable runnable = new Runnable() { + public void run() { + if (getControl().isDisposed()) + return; + refreshAfterDiff(message); + } + }; + if (Display.getCurrent() != null) + runnable.run(); + else + Display.getDefault().asyncExec(runnable); + } + + /** + * This method is called from within <code>diff()</code> after the + * difference tree has been built. Clients may override this method to + * perform their own post-processing. This default implementation does + * nothing. + * + * @param differencer the differencer used to perform the differencing + * @param root the non-<code>null</code> root node of the difference tree + * @since 2.0 + * @deprecated Subclasses should override + * {@link #postDiffHook(Differencer, IDiffContainer, IProgressMonitor)} + * instead + */ + protected void postDiffHook(Differencer differencer, IDiffContainer root) { + // we do nothing here + } + + /** + * This method is called from within {@link #diff(IProgressMonitor)} after + * the difference tree has been built. This method may be called from a + * background (non-UI) thread). + * <p> + * For backwards compatibility, this default implementation calls + * {@link #postDiffHook(Differencer, IDiffContainer)} from the UI thread. + * Clients should override this method even if they don't perform post + * processing to avoid the call to the UI thread. + * + * @param differencer the differencer used to perform the differencing + * @param root the non-<code>null</code> root node of the difference tree + * @param monitor a progress monitor or <code>null</code> if progress is + * not required + * @since 3.3 + */ + protected void postDiffHook(final Differencer differencer, final IDiffContainer root, IProgressMonitor monitor) { + syncExec(new Runnable() { + public void run() { + postDiffHook(differencer, root); + } + }); + } + + /* + * Performs a byte compare on the given objects. + * Called from the difference engine. + * Returns <code>null</code> if no structure creator has been set. + */ + private boolean contentsEqual(Object o1, Object o2) { + if (fStructureCreator != null) { + boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false); + String s1= fStructureCreator.getContents(o1, ignoreWhiteSpace); + String s2= fStructureCreator.getContents(o2, ignoreWhiteSpace); + if (s1 == null || s2 == null) + return false; + return s1.equals(s2); + } + return false; + } + + /** + * Tracks property changes of the configuration object. + * Clients may override to track their own property changes. + * In this case they must call the inherited method. + * @param event the property changed event that triggered the call to this method + */ + protected void propertyChange(PropertyChangeEvent event) { + String key= event.getProperty(); + if (key.equals(CompareConfiguration.IGNORE_WHITESPACE)) { + diff(); + } else if (key.equals("ANCESTOR_STRUCTURE_REFRESH")) { //$NON-NLS-1$ + fAncestorStructure.refresh(new NullProgressMonitor()); + diff(); + } else if (key.equals("LEFT_STRUCTURE_REFRESH")) { //$NON-NLS-1$ + fLeftStructure.refresh(new NullProgressMonitor()); + diff(); + } else if (key.equals("RIGHT_STRUCTURE_REFRESH")) { //$NON-NLS-1$ + fRightStructure.refresh(new NullProgressMonitor()); + diff(); + } else if (key.equals("ALL_STRUCTURE_REFRESH")) { //$NON-NLS-1$ + fAncestorStructure.refresh(new NullProgressMonitor()); + fLeftStructure.refresh(new NullProgressMonitor()); + fRightStructure.refresh(new NullProgressMonitor()); + diff(); + } else { + super.propertyChange(event); + } + } + + /** + * Overridden to call the <code>save</code> method on the structure creator after + * nodes have been copied from one side to the other side of an input object. + * + * @param leftToRight if <code>true</code> the left side is copied to the right side. + * If <code>false</code> the right side is copied to the left side + */ + protected void copySelected(boolean leftToRight) { + super.copySelected(leftToRight); + + if (fStructureCreator != null) + fStructureCreator.save( + leftToRight ? fRightStructure.getStructureComparator() : fLeftStructure.getStructureComparator(), + leftToRight ? fRightStructure.getInput() : fLeftStructure.getInput()); + } + + private void syncExec(final Runnable runnable) { + if (getControl().isDisposed()) + return; + if (Display.getCurrent() != null) + runnable.run(); + else + getControl().getDisplay().syncExec(new Runnable() { + public void run() { + if (!getControl().isDisposed()) + runnable.run(); + } + }); + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureRootNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureRootNode.java new file mode 100644 index 000000000..3cc2748de --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureRootNode.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.structuremergeviewer; + +import org.eclipse.compare.*; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.text.IDocument; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.services.IDisposable; + +/** + * A node that acts as the root of the tree returned from a {@link StructureCreator}. + * This node performs the following tasks tasks: + * <ol> + * <li>It adapts to an {@link ISharedDocumentAdapter} that provides the proper + * document key (@see {@link #getAdapter(Class)}).</li> + * <li>It invokes {@link IStructureCreator#save(IStructureComparator, Object)} + * when {@link #nodeChanged(DocumentRangeNode)} is called.</li> + * <li>It disposes of the {@link IDisposable} provided in the constructor when + * {@link #dispose()} is called.</li> + * </ol> + * <p> + * This class may be subclassed by clients. + * + * @since 3.3 + */ +public class StructureRootNode extends DocumentRangeNode implements IDisposable, ITypedElement { + + /** + * The integer constant (value <code>0</code>) that is used as the type code of the root node. + * @see #getTypeCode() + */ + public static final int ROOT_TYPE = 0; + + /** + * The string constant (value <code>"root"</code>) that is used as the id of the root node. + * @see #getId() + */ + public static final String ROOT_ID = "root"; //$NON-NLS-1$ + + private final Object fInput; + private final StructureCreator fCreator; + private ISharedDocumentAdapter fAdapter; + + /** + * Create the structure root node. + * @param document the document + * @param input the input associated with the document + * @param creator the structure creator that is creating the node + * @param adapter the shared document adapter from which the document was obtained or <code>null</code> + * if the document was not obtained from an {@link ISharedDocumentAdapter} + */ + public StructureRootNode(IDocument document, Object input, StructureCreator creator, ISharedDocumentAdapter adapter) { + super(null, ROOT_TYPE, ROOT_ID, document, 0, document.getLength()); + fInput = input; + fCreator = creator; + fAdapter = adapter; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.services.IDisposable#dispose() + */ + public void dispose() { + if (fAdapter != null) { + fAdapter.disconnect(fInput); + } + } + + /** + * Override {@link IAdaptable#getAdapter(Class)} in order to provide + * an {@link ISharedDocumentAdapter} that provides the proper look up key based + * on the input from which this structure node was created. + * @param adapter the adapter class to look up + * @return the object adapted to the given class or <code>null</code> + * @see IAdaptable#getAdapter(Class) + */ + public Object getAdapter(Class adapter) { + if (adapter == ISharedDocumentAdapter.class) { + return fAdapter; + } + return super.getAdapter(adapter); + } + + /** + * Override in order to invoke {@link IStructureCreator#save(IStructureComparator, Object)} when the + * contents of a node have changed. + * @param node the changed node + */ + protected void nodeChanged(DocumentRangeNode node) { + fCreator.save(this, fInput); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#replace(org.eclipse.compare.ITypedElement, org.eclipse.compare.ITypedElement) + */ + public ITypedElement replace(ITypedElement child, ITypedElement other) { + // TODO: I believe the parent implementation is flawed but didn't to remove + // it in case I was missing something so I overrode it instead + nodeChanged(this); + return child; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getImage() + */ + public Image getImage() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getName() + */ + public String getName() { + return getId(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getType() + */ + public String getType() { + return FOLDER_TYPE; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#isReadOnly() + */ + public boolean isReadOnly() { + if (fInput instanceof IEditableContentExtension) { + IEditableContentExtension ext = (IEditableContentExtension) fInput; + return ext.isReadOnly(); + } + return super.isReadOnly(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#validateEdit(org.eclipse.swt.widgets.Shell) + */ + public IStatus validateEdit(Shell shell) { + if (fInput instanceof IEditableContentExtension) { + IEditableContentExtension ext = (IEditableContentExtension) fInput; + return ext.validateEdit(shell); + } + return super.validateEdit(shell); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/package.html b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/package.html new file mode 100644 index 000000000..60257a4d8 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/package.html @@ -0,0 +1,79 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.75 [en] (WinNT; U) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides support for finding and displaying the differences +between hierarchically structured data. +<h2> +Package Specification</h2> + +The class <b>Differencer</b> is a differencing engine for hierarchically +structured data. It takes two or three inputs and performs a two-way or +three-way compare on them. +<p> + +If the input elements to the differencing engine implement the <b>IStructureComparator</b> +interface the engine recursively applies itself to the children of +the input element. Leaf elements must implement the <b>org.eclipse.compare.IStreamContentAccessor</b> +interface so that the differencer can perform a bytewise comparison on their contents. +<p> + +One good example for this is <b>org.eclipse.compare.ResourceNode</b> which implements both interfaces +(and more) for Eclipse workspace resources (org.eclipse.core.resources.IResource). +<p> + +Another example is the <b>DocumentRangeNode</b> which can be used to compare hierarchical structures +that are superimposed on a document, that is where nodes and leafs correspond to ranges in a document +(<b>org.eclipse.compare.contentmergeviewer.IDocumentRange</b>). +<br> +Typically <b>DocumentRangeNode</b>s are created while parsing a document and they represent +the semantic entities of the document (e.g. a Java class, method or field). +The two subclasses <b>JavaNode</b> (in org.eclipse.jdt.internal.ui.compare) +and <b>PropertyNode</b> (in org.eclipse.jdt.internal.ui.compare) are good examples for this. +<p> + +By default the differencing engine returns the result of the compare operation +as a tree of <b>DiffNode</b> objects. However, this can be changed by overriding +a single method of the engine. +<p> + +Every <b>DiffNode</b> describes the changes among the two or three inputs. +<p> + +A tree of <b>DiffNodes</b> can be displayed in a <b>DiffTreeViewer</b>. +The <b>DiffTreeViewer</b> requires that inner nodes of the tree implement +the <b>IDiffContainer</b> interface and leafs the <b>IDiffElement</b> interface. +<p> + +The typical steps to compare hierarchically structured data and to display +the differences would be to: +<ul> +<li> +map the input data into a tree of <b>IStructureComparator</b> and <b>IStreamContentAccessor</b>s,</li> + +<li> +perform the compare operation by means of the <b>Differencer</b>, and</li> + +<li> +feed the differencing result into the <b>DiffTreeViewer</b>.</li> + +</ul> + +The <b>StructureDiffViewer</b> is a specialized <b>DiffTreeViewer</b> +that automates the three steps from above. It takes a single input object +of type <b>ICompareInput</b> from which it retrieves the two or three +input elements to compare. Then it uses a <b>IStructureCreator</b> to +extract a tree of <b>IStructureComparator</b> and <b>IStreamContentAccessor</b> +from them. These trees are then compared with the differencing engine and +the result is displayed in the tree viewer. +<p> + + + +</body> +</html> |