Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.compare/compare/org')
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/BufferedContent.java131
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java710
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareEditorInput.java1571
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareNavigator.java101
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareUI.java436
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerPane.java334
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareViewerSwitchingPane.java397
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/EditionSelectionDialog.java1196
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/HistoryItem.java110
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareContainer.java111
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareInputLabelProvider.java79
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ICompareNavigator.java38
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeListener.java30
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IContentChangeNotifier.java39
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContent.java70
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IEditableContentExtension.java48
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IEncodedStreamContentAccessor.java39
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IModificationDate.java31
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/INavigatable.java89
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IPropertyChangeNotifier.java41
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IResourceProvider.java26
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ISharedDocumentAdapter.java90
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamContentAccessor.java36
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IStreamMerger.java69
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ITypedElement.java68
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/IViewerCreator.java34
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/NavigationAction.java95
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ResourceNode.java230
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/SharedDocumentAdapter.java125
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/Splitter.java129
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/ZipFileStructureCreator.java326
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java1343
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IDocumentRange.java54
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IFlushable.java36
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/IMergeViewerContentProvider.java155
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ITokenComparator.java57
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java5327
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties111
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TokenComparator.java132
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/package.html45
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java38
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java43
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java144
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties45
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java486
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java43
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java140
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java29
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties23
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java98
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java128
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java82
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java64
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java112
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java285
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java286
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java771
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java123
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java104
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java245
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java397
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java153
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java135
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties143
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java157
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java27
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java489
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java256
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java1421
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java20
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties38
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java41
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java838
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java52
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java91
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java168
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java202
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java65
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java237
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java133
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java53
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java53
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java28
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java27
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java23
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java41
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java139
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java144
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java30
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties32
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java49
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java1039
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java44
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java201
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java48
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java35
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java73
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java452
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java20
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties40
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java21
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java162
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java553
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java165
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java66
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java47
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java57
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java53
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java91
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java31
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java32
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java915
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java79
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java19
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java57
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java134
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java60
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java1409
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java63
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java31
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties15
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java97
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java123
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java56
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java47
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java108
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java113
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java990
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java253
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java433
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java68
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java19
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java125
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java128
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java111
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties117
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java87
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java231
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java236
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java38
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java763
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java747
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java224
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java115
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java106
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java68
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java382
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/package.html113
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/ApplyPatchOperation.java234
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/IFilePatch.java86
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/patch/WorkspacePatcherUI.java57
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffContainer.java107
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffElement.java93
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffNode.java364
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java728
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewerResources.properties53
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/Differencer.java534
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DocumentRangeNode.java468
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInput.java142
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/ICompareInputChangeListener.java38
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffContainer.java62
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IDiffElement.java61
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureComparator.java45
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator.java109
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/IStructureCreator2.java93
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/SharedDocumentAdapterWrapper.java104
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureCreator.java433
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java682
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureRootNode.java156
-rw-r--r--bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/package.html79
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 &nothing
+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 &nothing
+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>

Back to the top