diff options
Diffstat (limited to 'bundles/org.eclipse.compare/compare/org/eclipse/compare/internal')
107 files changed, 20768 insertions, 0 deletions
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java new file mode 100644 index 000000000..f76927a20 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AbstractViewer.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.Viewer; + + +public abstract class AbstractViewer extends Viewer { + + public void setInput(Object input) { + // empty default implementation + } + + public Object getInput() { + return null; + } + + public ISelection getSelection() { + return null; + } + + public void setSelection(ISelection s, boolean reveal) { + // empty default implementation + } + + public void refresh() { + // empty default implementation + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java new file mode 100644 index 000000000..e101563b6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AdapterFactory.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.ui.IContributorResourceAdapter; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; + +public class AdapterFactory implements IAdapterFactory { + + public Object getAdapter(final Object adaptableObject, Class adapterType) { + if (IContributorResourceAdapter.class.equals(adapterType) + && adaptableObject instanceof CompareEditorInput) { + return new IContributorResourceAdapter() { + public IResource getAdaptedResource(IAdaptable adaptable) { + Object ei = ((CompareEditorInput) adaptableObject) + .getAdapter(IEditorInput.class); + if (ei instanceof IFileEditorInput) { + return ((IFileEditorInput) ei).getFile(); + } + return null; + } + }; + } + return null; + } + + public Class[] getAdapterList() { + return new Class[] { IContributorResourceAdapter.class }; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java new file mode 100644 index 000000000..1fdd5c1f6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ResourceBundle; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.viewers.ISelection; + +import org.eclipse.ui.actions.WorkspaceModifyOperation; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + + +public class AddFromHistoryAction extends BaseCompareAction { + + private static final String BUNDLE_NAME= "org.eclipse.compare.internal.AddFromHistoryAction"; //$NON-NLS-1$ + + public AddFromHistoryAction() { + // empty default implementation + } + + protected boolean isEnabled(ISelection selection) { + return Utilities.getResources(selection).length == 1; + } + + protected void run(ISelection selection) { + + ResourceBundle bundle= ResourceBundle.getBundle(BUNDLE_NAME); + String title= Utilities.getString(bundle, "title"); //$NON-NLS-1$ + + Shell parentShell= CompareUIPlugin.getShell(); + AddFromHistoryDialog dialog= null; + + Object[] s= Utilities.getResources(selection); + + for (int i= 0; i < s.length; i++) { + Object o= s[i]; + if (o instanceof IContainer) { + IContainer container= (IContainer) o; + + ProgressMonitorDialog pmdialog= new ProgressMonitorDialog(parentShell); + IProgressMonitor pm= pmdialog.getProgressMonitor(); + IFile[] states= null; + try { + states= container.findDeletedMembersWithHistory(IResource.DEPTH_INFINITE, pm); + } catch (CoreException ex) { + pm.done(); + } + + // There could be a recently deleted file at the same path as + // the container. If such a file is the only one to restore, we + // should not suggest to restore it, so set states to null. + if (states.length == 1 && states[0].getFullPath().equals(container.getFullPath())) + states = null; + + if (states == null || states.length <= 0) { + String msg= Utilities.getString(bundle, "noLocalHistoryError"); //$NON-NLS-1$ + MessageDialog.openInformation(parentShell, title, msg); + return; + } + + if (dialog == null) { + dialog= new AddFromHistoryDialog(parentShell, bundle); + dialog.setHelpContextId(ICompareContextIds.ADD_FROM_HISTORY_DIALOG); + } + + if (dialog.select(container, states)) { + AddFromHistoryDialog.HistoryInput[] selected = dialog + .getSelected(); + if (selected != null && selected.length > 0) { + try { + updateWorkspace(bundle, parentShell, selected); + } catch (InterruptedException x) { + // Do nothing. Operation has been canceled by user. + } catch (InvocationTargetException x) { + String reason = x.getTargetException().getMessage(); + MessageDialog.openError(parentShell, title, + Utilities.getFormattedString(bundle, + "replaceError", reason)); //$NON-NLS-1$ + } + } + } + } + } + } + + void createContainers(IResource resource) throws CoreException { + IContainer container= resource.getParent(); + if (container instanceof IFolder) { + IFolder parent= (IFolder) container; + if (parent != null && !parent.exists()) { + createContainers(parent); + parent.create(false, true, null); + } + } + } + + private void updateWorkspace(final ResourceBundle bundle, Shell shell, + final AddFromHistoryDialog.HistoryInput[] selected) + throws InvocationTargetException, InterruptedException { + + WorkspaceModifyOperation operation= new WorkspaceModifyOperation() { + public void execute(IProgressMonitor pm) throws InvocationTargetException { + try { + String taskName= Utilities.getString(bundle, "taskName"); //$NON-NLS-1$ + pm.beginTask(taskName, selected.length); + + for (int i= 0; i < selected.length; i++) { + IFile file= selected[i].fFile; + IFileState fileState= selected[i].fFileState; + createContainers(file); + + SubProgressMonitor subMonitor= new SubProgressMonitor(pm, 1); + try { + file.create(fileState.getContents(), false, subMonitor); + } finally { + subMonitor.done(); + } + } + } catch (CoreException e) { + throw new InvocationTargetException(e); + } finally { + pm.done(); + } + } + }; + + ProgressMonitorDialog pmdialog= new ProgressMonitorDialog(shell); + pmdialog.run(false, true, operation); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties new file mode 100644 index 000000000..0a97df87f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryAction.properties @@ -0,0 +1,45 @@ +############################################################################### +# Copyright (c) 2000, 2010 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +# @(#)AddFromHistoryAction.properties +# +# Resources for AddFromHistoryAction.java + +title= Restore from Local History + +memberPaneTitle= {0} - Available Files in Local History: + +treeTitleFormat= Local History of ''{0}'' +dateIcon= obj16/day_obj.gif +timeIcon= obj16/resource_obj.gif + +memberDescription= Check files to restore from local history: +editionDescription= Select an edition of a file: + +treeFormat= {0} +workspaceTreeFormat= {0} (Workspace File) +parseErrorFormat= {0} (Parse Error) + +editionLabel= Local History ({0}) +workspaceEditionLabel= Workspace File + +targetLabel= {0} + +todayFormat= Today ({0}) +yesterdayFormat= Yesterday ({0}) +dayFormat= {0} + +buttonLabel= &Restore + +noLocalHistoryError= No deleted resources in local history for selected container. +replaceError=Cannot replace resource (reason: {0}). + +taskName=Restoring diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java new file mode 100644 index 000000000..0bb24bc9e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/AddFromHistoryDialog.java @@ -0,0 +1,486 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.*; +import com.ibm.icu.text.DateFormat; +import com.ibm.icu.text.MessageFormat; +import java.util.ArrayList; +import com.ibm.icu.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.ResourceBundle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +import org.eclipse.jface.dialogs.*; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +import org.eclipse.compare.*; + + +public class AddFromHistoryDialog extends ResizableDialog { + + static class HistoryInput implements ITypedElement, IEncodedStreamContentAccessor, IModificationDate { + IFile fFile; + IFileState fFileState; + + HistoryInput(IFile file, IFileState fileState) { + fFile= file; + fFileState= fileState; + } + public InputStream getContents() throws CoreException { + return new BufferedInputStream(fFileState.getContents()); + } + public String getCharset() { + String charset= null; + try { + charset= fFileState.getCharset(); + } catch (CoreException e) { + // fall through + } + if (charset == null) + charset= Utilities.getCharset(fFile); + return charset; + } + public String getName() { + return fFile.getName(); + } + public String getType() { + return fFile.getFileExtension(); + } + public Image getImage() { + return CompareUI.getImage(fFile); + } + public long getModificationDate() { + return fFileState.getModificationTime(); + } + } + + static class FileHistory { + private IFile fFile; + private IFileState[] fStates; + private int fSelected; + + FileHistory(IFile file) { + fFile= file; + } + + IFile getFile() { + return fFile; + } + + IFileState[] getStates() { + if (fStates == null) { + try { + fStates= fFile.getHistory(new NullProgressMonitor()); + } catch (CoreException ex) { + // NeedWork + } + } + return fStates; + } + + IFileState getSelectedState() { + return getStates()[fSelected]; + } + + void setSelected(IFileState state) { + for (int i= 0; i < fStates.length; i++) { + if (fStates[i] == state) { + fSelected= i; + return; + } + } + } + + HistoryInput getHistoryInput() { + return new HistoryInput(fFile, getSelectedState()); + } + + boolean isSelected(int index) { + return index == fSelected; + } + } + + private CompareConfiguration fCompareConfiguration; + private ArrayList fArrayList= new ArrayList(); + private FileHistory fCurrentFileHistory; + + // SWT controls + private CompareViewerSwitchingPane fContentPane; + private Button fCommitButton; + private Table fMemberTable; + private CompareViewerPane fMemberPane; + private Tree fEditionTree; + private CompareViewerPane fEditionPane; + private Image fDateImage; + private Image fTimeImage; + + + public AddFromHistoryDialog(Shell parent, ResourceBundle bundle) { + super(parent, bundle); + + String iconName= Utilities.getString(fBundle, "dateIcon", "obj16/day_obj.gif"); //$NON-NLS-2$ //$NON-NLS-1$ + ImageDescriptor id= CompareUIPlugin.getImageDescriptor(iconName); + if (id != null) + fDateImage= id.createImage(); + iconName= Utilities.getString(fBundle, "timeIcon", "obj16/resource_obj.gif"); //$NON-NLS-1$ //$NON-NLS-2$ + id= CompareUIPlugin.getImageDescriptor(iconName); + if (id != null) + fTimeImage= id.createImage(); + } + + public boolean select(IContainer root, IFile[] inputFiles) { + + create(); // create widgets + + String format= Utilities.getString(fBundle, "memberPaneTitle"); //$NON-NLS-1$ + String title= MessageFormat.format(format, new Object[] { root.getName() }); + fMemberPane.setImage(CompareUI.getImage(root)); + fMemberPane.setText(title); + + // sort input files + final int count= inputFiles.length; + final IFile[] files= new IFile[count]; + for (int i= 0; i < count; i++) + files[i]= inputFiles[i]; + if (count > 1) + internalSort(files, 0, count-1); + + + String prefix= root.getFullPath().toString(); + + if (fMemberTable != null && !fMemberTable.isDisposed()) { + for (int i = 0; i < files.length; i++) { + IFile file = files[i]; + String path = file.getFullPath().toString(); + + // ignore a recently deleted file at the same path as the + // container + if (path.equals(prefix)) + continue; + + if (path.startsWith(prefix)) + path = path.substring(prefix.length() + 1); + TableItem ti = new TableItem(fMemberTable, SWT.NONE); + ti.setImage(CompareUI.getImage(file)); + ti.setText(path); + ti.setData(new FileHistory(file)); + } + } + + open(); + + return (getReturnCode() == OK) && (fArrayList.size() > 0); + } + + HistoryInput[] getSelected() { + HistoryInput[] selected= new HistoryInput[fArrayList.size()]; + Iterator iter= fArrayList.iterator(); + for (int i= 0; iter.hasNext(); i++) { + FileHistory h= (FileHistory) iter.next(); + selected[i]= h.getHistoryInput(); + } + return selected; + } + + protected synchronized Control createDialogArea(Composite parent2) { + + Composite parent= (Composite) super.createDialogArea(parent2); + + getShell().setText(Utilities.getString(fBundle, "title")); //$NON-NLS-1$ + + org.eclipse.compare.Splitter vsplitter= new org.eclipse.compare.Splitter(parent, SWT.VERTICAL); + vsplitter.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL + | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); + + vsplitter.addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fDateImage != null) + fDateImage.dispose(); + if (fTimeImage != null) + fTimeImage.dispose(); + } + } + ); + + // we need two panes: the left for the elements, the right one for the editions + Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); + + Composite c= new Composite(hsplitter, SWT.NONE); + GridLayout layout= new GridLayout(); + layout.marginWidth= 0; + layout.marginHeight= 2; + layout.verticalSpacing= 2; + layout.numColumns= 1; + c.setLayout(layout); + Label l1= new Label(c, SWT.NONE); + l1.setText(Utilities.getString(fBundle, "memberDescription")); //$NON-NLS-1$ + fMemberPane= new CompareViewerPane(c, SWT.BORDER | SWT.FLAT); + GridData gd= new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); + fMemberPane.setLayoutData(gd); + + fMemberTable= new Table(fMemberPane, SWT.CHECK | SWT.H_SCROLL | SWT.V_SCROLL); + fMemberTable.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (e.detail == SWT.CHECK) { + if (e.item instanceof TableItem) { + TableItem ti= (TableItem) e.item; + if (ti.getChecked()) + fArrayList.add(ti.getData()); + else + fArrayList.remove(ti.getData()); + + if (fCommitButton != null) + fCommitButton.setEnabled(fArrayList.size() > 0); + } + } else { + handleMemberSelect(e.item); + } + } + } + ); + + fMemberPane.setContent(fMemberTable); + + c= new Composite(hsplitter, SWT.NONE); + layout= new GridLayout(); + layout.marginWidth= 0; + layout.marginHeight= 2; + layout.verticalSpacing= 2; + layout.numColumns= 1; + c.setLayout(layout); + Label l2= new Label(c, SWT.NONE); + l2.setText(Utilities.getString(fBundle, "editionDescription")); //$NON-NLS-1$ + fEditionPane= new CompareViewerPane(c, SWT.BORDER | SWT.FLAT); + gd= new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); + fEditionPane.setLayoutData(gd); + + fEditionTree= new Tree(fEditionPane, SWT.H_SCROLL | SWT.V_SCROLL); + fEditionTree.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + feedContent(e.item); + } + } + ); + fEditionPane.setContent(fEditionTree); + + applyDialogFont(parent); // to avoid applying font to compare viewer + fContentPane= new CompareViewerSwitchingPane(vsplitter, SWT.BORDER | SWT.FLAT) { + protected Viewer getViewer(Viewer oldViewer, Object input) { + return CompareUI.findContentViewer(oldViewer, input, this, fCompareConfiguration); + } + }; + vsplitter.setWeights(new int[] { 30, 70 }); + + return parent; + } + + /* + * Feeds selection from member viewer to edition viewer. + */ + private void handleMemberSelect(Widget w) { + Object data= null; + if (w != null) + data= w.getData(); + if (data instanceof FileHistory) { + + FileHistory h= (FileHistory) data; + fCurrentFileHistory= h; + + IFile file= h.getFile(); + IFileState[] states= h.getStates(); + + fEditionPane.setImage(CompareUI.getImage(file)); + String pattern= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ + String title= MessageFormat.format(pattern, new Object[] { file.getName() }); + fEditionPane.setText(title); + + if (fEditionTree != null) { + fEditionTree.setRedraw(false); + fEditionTree.removeAll(); + for (int i= 0; i < states.length; i++) { + addEdition(new HistoryInput(file, states[i]), h.isSelected(i)); + } + fEditionTree.setRedraw(true); + } + } else + fCurrentFileHistory= null; + } + + /* + * Adds the given Pair to the edition tree. + * It takes care of creating tree nodes for different dates. + */ + private void addEdition(HistoryInput input, boolean isSelected) { + if (fEditionTree == null || fEditionTree.isDisposed()) + return; + + IFileState state= input.fFileState; + + // find last day + TreeItem[] days= fEditionTree.getItems(); + TreeItem lastDay= null; + if (days.length > 0) + lastDay= days[days.length-1]; + + long ldate= state.getModificationTime(); + long day= dayNumber(ldate); + Date date= new Date(ldate); + if (lastDay == null || day != dayNumber(((Date)lastDay.getData()).getTime())) { + lastDay= new TreeItem(fEditionTree, SWT.NONE); + lastDay.setImage(fDateImage); + String df= DateFormat.getDateInstance().format(date); + long today= dayNumber(System.currentTimeMillis()); + + String formatKey; + if (day == today) + formatKey= "todayFormat"; //$NON-NLS-1$ + else if (day == today-1) + formatKey= "yesterdayFormat"; //$NON-NLS-1$ + else + formatKey= "dayFormat"; //$NON-NLS-1$ + String pattern= Utilities.getString(fBundle, formatKey); + if (pattern != null) + df= MessageFormat.format(pattern, new String[] { df }); + lastDay.setText(df); + lastDay.setData(date); + } + TreeItem ti= new TreeItem(lastDay, SWT.NONE); + ti.setImage(fTimeImage); + ti.setText(DateFormat.getTimeInstance().format(date)); + ti.setData(input); + + if (isSelected) { + lastDay.setExpanded(true); + fEditionTree.setSelection(new TreeItem[] { ti }); + feedContent(ti); + } + } + + /* + * Returns the number of s since Jan 1st, 1970. + * The given date is converted to GMT and daylight saving is taken into account too. + */ + private long dayNumber(long date) { + int ONE_DAY_MS= 24*60*60 * 1000; // one day in milli seconds + + Calendar calendar= Calendar.getInstance(); + long localTimeOffset= calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + + return (date + localTimeOffset) / ONE_DAY_MS; + } + + /* + * Feeds the tree viewer's selection to the contentviewer + */ + private void feedContent(Widget w) { + if (fContentPane != null && !fContentPane.isDisposed()) { + Object o= w.getData(); + if (o instanceof HistoryInput) { + HistoryInput selected= (HistoryInput) o; + fContentPane.setInput(selected); + fContentPane.setText(getEditionLabel(selected)); + fContentPane.setImage(fTimeImage); + + if (fCurrentFileHistory != null) + fCurrentFileHistory.setSelected(selected.fFileState); + } else { + fContentPane.setInput(null); + } + } + } + + protected String getEditionLabel(HistoryInput input) { + String format= Utilities.getString(fBundle, "historyEditionLabel", null); //$NON-NLS-1$ + if (format == null) + format= Utilities.getString(fBundle, "editionLabel"); //$NON-NLS-1$ + if (format == null) + format= "x{0}"; //$NON-NLS-1$ + + long modDate= input.getModificationDate(); + String date= DateFormat.getDateTimeInstance().format(new Date(modDate)); + + return MessageFormat.format(format, new Object[] { date }); + } + + /* (non-Javadoc) + * Method declared on Dialog. + */ + protected void createButtonsForButtonBar(Composite parent) { + String buttonLabel= Utilities.getString(fBundle, "buttonLabel", IDialogConstants.OK_LABEL); //$NON-NLS-1$ + // a 'Cancel' and a 'Add' button + fCommitButton= createButton(parent, IDialogConstants.OK_ID, buttonLabel, true); + fCommitButton.setEnabled(false); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + } + + /* + * Returns true if the pathname of f1 comes after f2 + */ + private static boolean greaterThan(IFile f1, IFile f2) { + String[] ss1= f1.getFullPath().segments(); + String[] ss2= f2.getFullPath().segments(); + int l1= ss1.length; + int l2= ss2.length; + int n= Math.max(l1, l2); + + for (int i= 0; i < n; i++) { + String s1= i < l1 ? ss1[i] : ""; //$NON-NLS-1$ + String s2= i < l2 ? ss2[i] : ""; //$NON-NLS-1$ + int rc= s1.compareToIgnoreCase(s2); + if (rc != 0) + return rc < 0; + } + return false; + } + + private static void internalSort(IFile[] keys, int left, int right) { + + int original_left= left; + int original_right= right; + + IFile mid= keys[(left + right) / 2]; + do { + while (greaterThan(keys[left], mid)) + left++; + + while (greaterThan(mid, keys[right])) + right--; + + if (left <= right) { + IFile tmp= keys[left]; + keys[left]= keys[right]; + keys[right]= tmp; + left++; + right--; + } + } while (left <= right); + + if (original_left < right) + internalSort(keys, original_left, right); + + if (left < original_right) + internalSort(keys, left, original_right); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java new file mode 100644 index 000000000..0998e18d4 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BaseCompareAction.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IActionDelegate; + + +public abstract class BaseCompareAction implements IActionDelegate { + + private ISelection fSelection; + + /* (non-Javadoc) + * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction) + */ + final public void run(IAction action) { + run(fSelection); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection) + */ + final public void selectionChanged(IAction action, ISelection selection) { + fSelection= selection; + if (action != null) + action.setEnabled(isEnabled(fSelection)); + } + + protected boolean isEnabled(ISelection selection) { + return false; + } + + abstract protected void run(ISelection selection); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java new file mode 100644 index 000000000..c6d9d2e91 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.PlatformUI; + +import com.ibm.icu.text.MessageFormat; + +/** + * A simple compare viewer for binary files. + * Shows the position of the first non-matching byte. + */ +public class BinaryCompareViewer extends AbstractViewer { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.BinaryCompareViewerResources"; //$NON-NLS-1$ + + private static final int EOF = -1; + private ICompareInput fInput; + private ResourceBundle fBundle; + private boolean fLeftIsLocal; + + private Composite fComposite; + private Label fMessage; + + public BinaryCompareViewer(Composite parent, final CompareConfiguration cc) { + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICompareContextIds.BINARY_COMPARE_VIEW); + + fBundle= ResourceBundle.getBundle(BUNDLE_NAME); + + fComposite= new Composite(parent, SWT.NONE); + RowLayout rowLayout = new RowLayout(); + rowLayout.type = SWT.VERTICAL; + fComposite.setLayout(rowLayout); + + fMessage= new Label(fComposite, SWT.WRAP); + fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, Utilities.getString(fBundle, "title")); //$NON-NLS-1$ + + fLeftIsLocal= Utilities.getBoolean(cc, "LEFT_IS_LOCAL", false); //$NON-NLS-1$ + + if (cc != null && cc.getContainer() instanceof CompareEditorInput) { + Label compareAsTextLabel = new Label(fComposite, SWT.WRAP); + compareAsTextLabel + .setText(Utilities.getString(fBundle, "compareAsText")); //$NON-NLS-1$ + } + } + + public Control getControl() { + return fComposite; + } + + public void setInput(Object input) { + if (fComposite != null && input instanceof ICompareInput) { + fInput= (ICompareInput) input; + + InputStream left= null; + InputStream right= null; + + String message= null; + try { + left= getStream(fInput.getLeft()); + right= getStream(fInput.getRight()); + + if (left != null && right != null) { + int pos= 0; + while (true) { + int l= left.read(); + int r= right.read(); + if (l != r) { + String format= Utilities.getString(fBundle, "diffMessageFormat"); //$NON-NLS-1$ + message= MessageFormat.format(format, new String[] { Integer.toString(pos) } ); + break; + } + if (l == EOF) + break; + pos++; + } + } else if (left == null && right == null) { + message= Utilities.getString(fBundle, "deleteConflictMessage"); //$NON-NLS-1$ + } else if (left == null) { + if (fLeftIsLocal) + message= Utilities.getString(fBundle, "deletedMessage"); //$NON-NLS-1$ + else + message= Utilities.getString(fBundle, "addedMessage"); //$NON-NLS-1$ + } else if (right == null) { + if (fLeftIsLocal) + message= Utilities.getString(fBundle, "addedMessage"); //$NON-NLS-1$ + else + message= Utilities.getString(fBundle, "deletedMessage"); //$NON-NLS-1$ + } + } catch (CoreException ex) { + message = Utilities.getString(fBundle, "errorMessage"); //$NON-NLS-1$ + CompareUIPlugin.log(ex); + } catch (IOException ex) { + message = Utilities.getString(fBundle, "errorMessage"); //$NON-NLS-1$ + CompareUIPlugin.log(ex); + } finally { + Utilities.close(left); + Utilities.close(right); + } + if (message != null) + fMessage.setText(message); + fComposite.layout(); + } + } + + public Object getInput() { + return fInput; + } + + private InputStream getStream(ITypedElement input) throws CoreException { + if (input instanceof IStreamContentAccessor) + return ((IStreamContentAccessor)input).getContents(); + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java new file mode 100644 index 000000000..e66bd88b6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerCreator.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.*; + +/** + * A factory object for the <code>BinaryCompareViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class BinaryCompareViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new BinaryCompareViewer(parent, mp); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties new file mode 100644 index 000000000..225cb447b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewerResources.properties @@ -0,0 +1,23 @@ +############################################################################### +# Copyright (c) 2000, 2009 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +# @(#)BinaryCompareViewerResources.properties +# +# Resource strings for BinaryCompareViewer.java + +title= Binary Compare + +diffMessageFormat= First bytes differ at position {0} +deleteConflictMessage= Delete Conflict +addedMessage= Added Resource +deletedMessage= Removed Resource +errorMessage= Internal Error +compareAsText= Switch to Text Compare using the drop down menu above diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java new file mode 100644 index 000000000..af8af9041 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedCanvas.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.events.*; + +/** + * A Canvas which reduces flicker by drawing in an off screen buffer. + */ +public abstract class BufferedCanvas extends Canvas { + + //private static final boolean USE_DOUBLE_BUFFER= !"carbon".equals(SWT.getPlatform()); //$NON-NLS-1$ + private static final boolean USE_DOUBLE_BUFFER= true; + + /** The drawable for double buffering */ + Image fBuffer; + + public BufferedCanvas(Composite parent, int flags) { + super(parent, flags | SWT.NO_BACKGROUND); + + addPaintListener( + new PaintListener() { + public void paintControl(PaintEvent event) { + doubleBufferPaint(event.gc); + } + } + ); + + addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fBuffer != null) { + fBuffer.dispose(); + fBuffer= null; + } + } + } + ); + } + + public void repaint() { + if (!isDisposed()) { + GC gc= new GC(this); + doubleBufferPaint(gc); + gc.dispose(); + } + } + + /* + * Double buffer drawing. + */ + void doubleBufferPaint(GC dest) { + + if (!USE_DOUBLE_BUFFER) { + doPaint(dest); + return; + } + + Point size= getSize(); + + if (size.x <= 1 || size.y <= 1) // we test for <= 1 because on X11 controls have initial size 1,1 + return; + + if (fBuffer != null) { + Rectangle r= fBuffer.getBounds(); + if (r.width != size.x || r.height != size.y) { + fBuffer.dispose(); + fBuffer= null; + } + } + if (fBuffer == null) + fBuffer= new Image(getDisplay(), size.x, size.y); + + GC gc= new GC(fBuffer); + try { + gc.setBackground(getBackground()); + gc.fillRectangle(0, 0, size.x, size.y); + doPaint(gc); + } finally { + gc.dispose(); + } + + dest.drawImage(fBuffer, 0, 0); + } + + abstract public void doPaint(GC gc); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java new file mode 100644 index 000000000..ccf566c21 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BufferedResourceNode.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.*; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.compare.*; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; + +/** + * A buffer for a workspace resource. + */ +public class BufferedResourceNode extends ResourceNode { + + private boolean fDirty= false; + private IFile fDeleteFile; + + /** + * Creates a <code>ResourceNode</code> for the given resource. + * + * @param resource the resource + */ + public BufferedResourceNode(IResource resource) { + super(resource); + } + + /* + * Returns <code>true</code> if buffer contains uncommitted changes. + */ + public boolean isDirty() { + return fDirty; + } + + protected IStructureComparator createChild(IResource child) { + return new BufferedResourceNode(child); + } + + public void setContent(byte[] contents) { + fDirty= true; + super.setContent(contents); + } + + /* + * Commits buffered contents to resource. + */ + public void commit(IProgressMonitor pm) throws CoreException { + if (fDirty) { + + if (fDeleteFile != null) { + fDeleteFile.delete(true, true, pm); + return; + } + + IResource resource= getResource(); + if (resource instanceof IFile) { + + byte[] bytes= getContent(); + ByteArrayInputStream is= new ByteArrayInputStream(bytes); + try { + IFile file= (IFile) resource; + if (file.exists()) + file.setContents(is, false, true, pm); + else + file.create(is, false, pm); + fDirty= false; + } finally { + if (is != null) + try { + is.close(); + } catch(IOException ex) { + // Silently ignored + } + } + } + } + } + + public ITypedElement replace(ITypedElement child, ITypedElement other) { + + if (child == null) { // add resource + // create a node without a resource behind it! + IResource resource= getResource(); + if (resource instanceof IFolder) { + IFolder folder= (IFolder) resource; + IFile file= folder.getFile(other.getName()); + child= new BufferedResourceNode(file); + } + } + + if (other == null) { // delete resource + IResource resource= getResource(); + if (resource instanceof IFolder) { + IFolder folder= (IFolder) resource; + IFile file= folder.getFile(child.getName()); + if (file != null && file.exists()) { + fDeleteFile= file; + fDirty= true; + } + } + return null; + } + + if (other instanceof IStreamContentAccessor && child instanceof IEditableContent) { + IEditableContent dst= (IEditableContent) child; + + try { + InputStream is= ((IStreamContentAccessor)other).getContents(); + byte[] bytes= Utilities.readBytes(is); + if (bytes != null) + dst.setContent(bytes); + } catch (CoreException ex) { + // NeedWork + } + } + return child; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java new file mode 100644 index 000000000..85fd9b48e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ChangePropertyAction.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ResourceBundle; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.compare.CompareConfiguration; + +/** + * Toggles a boolean property of an <code>CompareConfiguration</code>. + */ +public class ChangePropertyAction extends Action implements IPropertyChangeListener, DisposeListener { + + private CompareConfiguration fCompareConfiguration; + private String fPropertyKey; + private ResourceBundle fBundle; + private String fPrefix; + + public static ChangePropertyAction createIgnoreWhiteSpaceAction(ResourceBundle bundle, CompareConfiguration compareConfiguration) { + return new ChangePropertyAction(bundle, compareConfiguration, "action.IgnoreWhiteSpace.", CompareConfiguration.IGNORE_WHITESPACE); //$NON-NLS-1$ + } + public static ChangePropertyAction createShowPseudoConflictsAction(ResourceBundle bundle, CompareConfiguration compareConfiguration) { + return new ChangePropertyAction(bundle, compareConfiguration, "action.ShowPseudoConflicts.", CompareConfiguration.SHOW_PSEUDO_CONFLICTS); //$NON-NLS-1$ + } + + public ChangePropertyAction(ResourceBundle bundle, CompareConfiguration cc, String rkey, String pkey) { + fPropertyKey= pkey; + fBundle= bundle; + fPrefix= rkey; + Utilities.initAction(this, fBundle, fPrefix); + setCompareConfiguration(cc); + } + + public void run() { + boolean b= !Utilities.getBoolean(fCompareConfiguration, fPropertyKey, false); + setChecked(b); + if (fCompareConfiguration != null) + fCompareConfiguration.setProperty(fPropertyKey, new Boolean(b)); + } + + public void setChecked(boolean state) { + super.setChecked(state); + Utilities.initToggleAction(this, fBundle, fPrefix, state); + } + + public void setCompareConfiguration(CompareConfiguration cc) { + if (fCompareConfiguration != null) + fCompareConfiguration.removePropertyChangeListener(this); + fCompareConfiguration= cc; + if (fCompareConfiguration != null) + fCompareConfiguration.addPropertyChangeListener(this); + setChecked(Utilities.getBoolean(fCompareConfiguration, fPropertyKey, false)); + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(fPropertyKey)) { + setChecked(Utilities.getBoolean(fCompareConfiguration, fPropertyKey, false)); + } + } + + public void dispose(){ + if (fCompareConfiguration != null) + fCompareConfiguration.removePropertyChangeListener(this); + } + + public void widgetDisposed(DisposeEvent e) { + dispose(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java new file mode 100644 index 000000000..0ff9e5444 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareAction.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959 + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; + + +/* + * The "Compare with each other" action + */ +public class CompareAction extends BaseCompareAction implements IObjectActionDelegate { + + protected ResourceCompareInput fInput; + protected IWorkbenchPage fWorkbenchPage; + protected boolean showSelectAncestorDialog = true; + + public void run(ISelection selection) { + if (fInput != null) { + // Pass the shell so setSelection can prompt the user for which + // resource should be the ancestor + boolean ok = fInput.setSelection(selection, fWorkbenchPage + .getWorkbenchWindow().getShell(), showSelectAncestorDialog); + if (!ok) return; + fInput.initializeCompareConfiguration(); + CompareUI.openCompareEditorOnPage(fInput, fWorkbenchPage); + fInput= null; // don't reuse this input! + } + } + + protected boolean isEnabled(ISelection selection) { + if (fInput == null) { + CompareConfiguration cc= new CompareConfiguration(); + // buffered merge mode: don't ask for confirmation + // when switching between modified resources + cc.setProperty(CompareEditor.CONFIRM_SAVE_PROPERTY, new Boolean(false)); + + // uncomment following line to have separate outline view + //cc.setProperty(CompareConfiguration.USE_OUTLINE_VIEW, new Boolean(true)); + + fInput= new ResourceCompareInput(cc); + } + return fInput.isEnabled(selection); + } + + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + fWorkbenchPage= targetPart.getSite().getPage(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java new file mode 100644 index 000000000..2a106c004 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContainer.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.ICompareContainer; +import org.eclipse.compare.ICompareNavigator; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ui.*; +import org.eclipse.ui.services.IServiceLocator; + +public class CompareContainer implements ICompareContainer { + + private WorkerJob worker; + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) + */ + public void setStatusMessage(String message) { + // Do nothing, by default + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener) + */ + public void addCompareInputChangeListener(ICompareInput input, + ICompareInputChangeListener listener) { + input.addCompareInputChangeListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener) + */ + public void removeCompareInputChangeListener(ICompareInput input, + ICompareInputChangeListener listener) { + input.removeCompareInputChangeListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider) + */ + public void registerContextMenu(MenuManager menu, + ISelectionProvider selectionProvider) { + // Nothing to do + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getServiceLocator() + */ + public IServiceLocator getServiceLocator() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getActionBars() + */ + public IActionBars getActionBars() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.operation.IRunnableContext#run(boolean, boolean, org.eclipse.jface.operation.IRunnableWithProgress) + */ + public void run(boolean fork, boolean cancelable, + IRunnableWithProgress runnable) + throws InvocationTargetException, InterruptedException { + PlatformUI.getWorkbench().getProgressService().run(fork, cancelable, runnable); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getNavigator() + */ + public ICompareNavigator getNavigator() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#runAsynchronously(org.eclipse.jface.operation.IRunnableWithProgress) + */ + public synchronized void runAsynchronously(IRunnableWithProgress runnable) { + if (worker == null) + worker = createWorkerJob(); + worker.add(runnable); + } + + protected WorkerJob createWorkerJob() { + WorkerJob workerJob = new WorkerJob(getWorkerJobName()); + return workerJob; + } + + protected String getWorkerJobName() { + return CompareMessages.CompareContainer_0; + } + + public IWorkbenchPart getWorkbenchPart() { + return null; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java new file mode 100644 index 000000000..d92ad78ba --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareContentViewerSwitchingPane.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareViewerSwitchingPane; +import org.eclipse.compare.Splitter; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.MenuAdapter; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.PlatformUI; + +public class CompareContentViewerSwitchingPane extends + CompareViewerSwitchingPane { + + private static final String OPTIMIZED_INFO_IMAGE_NAME = "obj16/message_info.gif"; //$NON-NLS-1$ + public static final String OPTIMIZED_ALGORITHM_USED = "OPTIMIZED_ALGORITHM_USED"; //$NON-NLS-1$ + + private CompareEditorInput fCompareEditorInput; + + private ViewerDescriptor fSelectedViewerDescriptor; + + private ToolBar toolBar; + private CLabel clOptimized; + + private boolean menuShowing; + + public CompareContentViewerSwitchingPane(Splitter parent, int style, + CompareEditorInput cei) { + super(parent, style); + fCompareEditorInput = cei; + } + + private CompareConfiguration getCompareConfiguration() { + return fCompareEditorInput.getCompareConfiguration(); + } + + protected Viewer getViewer(Viewer oldViewer, Object input) { + if (fSelectedViewerDescriptor != null) { + ViewerDescriptor[] array = CompareUIPlugin.getDefault().findContentViewerDescriptor( + oldViewer, input, getCompareConfiguration()); + List list = array != null ? Arrays.asList(array) + : Collections.EMPTY_LIST; + if (list.contains(fSelectedViewerDescriptor)) { + // use selected viewer only when appropriate for the new input + fCompareEditorInput + .setContentViewerDescriptor(fSelectedViewerDescriptor); + Viewer viewer = fCompareEditorInput.findContentViewer( + oldViewer, (ICompareInput) input, this); + return viewer; + } + // fallback to default otherwise + fSelectedViewerDescriptor = null; + } + if (input instanceof ICompareInput) { + fCompareEditorInput.setContentViewerDescriptor(null); + Viewer viewer = fCompareEditorInput.findContentViewer(oldViewer, + (ICompareInput) input, this); + fCompareEditorInput.setContentViewerDescriptor(fSelectedViewerDescriptor); + return viewer; + } + return null; + } + + protected Control createTopLeft(Composite p) { + final Composite composite = new Composite(p, SWT.NONE) { + public Point computeSize(int wHint, int hHint, boolean changed) { + return super.computeSize(wHint, Math.max(24, hHint), changed); + } + }; + + RowLayout layout = new RowLayout(); + layout.marginTop = 0; + composite.setLayout(layout); + + CLabel cl = new CLabel(composite, SWT.NONE); + cl.setText(null); + + toolBar = new ToolBar(composite, SWT.FLAT); + toolBar.setVisible(false); // hide by default + final ToolItem toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + toolItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage( + /* IWorkbenchGraphicConstants */"IMG_LCL_VIEW_MENU")); //$NON-NLS-1$ + toolItem + .setToolTipText(CompareMessages.CompareContentViewerSwitchingPane_switchButtonTooltip); + toolItem.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + showMenu(); + } + }); + toolBar.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent e) { + showMenu(); + } + }); + + clOptimized = new CLabel(composite, SWT.NONE); + clOptimized + .setText(CompareMessages.CompareContentViewerSwitchingPane_optimized); + clOptimized + .setToolTipText(CompareMessages.CompareContentViewerSwitchingPane_optimizedTooltip); + clOptimized.setImage(CompareUIPlugin.getImageDescriptor( + OPTIMIZED_INFO_IMAGE_NAME).createImage()); + clOptimized.setVisible(false); // hide by default + clOptimized.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + Image img = clOptimized.getImage(); + if ((img != null) && (!img.isDisposed())) { + img.dispose(); + } + } + }); + + return composite; + } + + protected boolean inputChanged(Object input) { + return getInput() != input + || fCompareEditorInput.getContentViewerDescriptor() != fSelectedViewerDescriptor; + } + + public void setInput(Object input) { + super.setInput(input); + if (getViewer() == null || !Utilities.okToUse(getViewer().getControl())) + return; + ViewerDescriptor[] vd = CompareUIPlugin.getDefault() + .findContentViewerDescriptor(getViewer(), getInput(), + getCompareConfiguration()); + toolBar.setVisible(vd != null && vd.length > 1); + CompareConfiguration cc = getCompareConfiguration(); + Boolean isOptimized = (Boolean) cc.getProperty(OPTIMIZED_ALGORITHM_USED); + clOptimized.setVisible(isOptimized != null && isOptimized.booleanValue()); + } + + private void showMenu() { + if (menuShowing) + return; + menuShowing= true; + + ViewerDescriptor[] vd = CompareUIPlugin.getDefault() + .findContentViewerDescriptor(getViewer(), getInput(), + getCompareConfiguration()); + + // 1. create + final Menu menu = new Menu(getShell(), SWT.POP_UP); + + // add default + String label = CompareMessages.CompareContentViewerSwitchingPane_defaultViewer; + MenuItem defaultItem = new MenuItem(menu, SWT.RADIO); + defaultItem.setText(label); + defaultItem.addSelectionListener(createSelectionListener(null)); + defaultItem.setSelection(fSelectedViewerDescriptor == null); + + new MenuItem(menu, SWT.SEPARATOR); + + // add others + for (int i = 0; i < vd.length; i++) { + final ViewerDescriptor vdi = vd[i]; + label = vdi.getLabel(); + if (label == null || label.equals("")) { //$NON-NLS-1$ + String l = CompareUIPlugin.getDefault().findContentTypeNameOrType((ICompareInput) getInput(), vdi, getCompareConfiguration()); + if (l == null) + // couldn't figure out the label, skip the viewer + continue; + label = NLS.bind(CompareMessages.CompareContentViewerSwitchingPane_discoveredLabel, new Object[] {l}); + } + MenuItem item = new MenuItem(menu, SWT.RADIO); + item.setText(label); + item.addSelectionListener(createSelectionListener(vdi)); + item.setSelection(vdi == fSelectedViewerDescriptor); + } + + // 2. show + Rectangle bounds = toolBar.getItem(0).getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + + // 3. dispose on close + menu.addMenuListener(new MenuAdapter() { + public void menuHidden(MenuEvent e) { + menuShowing= false; + e.display.asyncExec(new Runnable() { + public void run() { + menu.dispose(); + } + }); + } + }); + } + + private SelectionListener createSelectionListener(final ViewerDescriptor vd) { + return new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + MenuItem mi = (MenuItem) e.widget; + if (mi.getSelection()) { + Viewer oldViewer = getViewer(); + fSelectedViewerDescriptor = vd; + CompareContentViewerSwitchingPane.this.setInput(oldViewer + .getInput()); + } + } + + public void widgetDefaultSelected(SelectionEvent e) { + // nothing to do + } + }; + } + + public void setText(String label) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) { + cl.setText(label); + c.layout(); + } + return; + } + } + } + + public void setImage(Image image) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) + cl.setImage(image); + return; + } + } + } + + public void addMouseListener(MouseListener listener) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + cl.addMouseListener(listener); + } + } + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java new file mode 100644 index 000000000..7a7fcd0ac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareDialog.java @@ -0,0 +1,286 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; + +/** + * This is a dialog that can host a {@link CompareEditorInput}. + * <p> + * This class can be used as is or can be subclassed. + * + * @since 3.3 + */ +public class CompareDialog extends TrayDialog implements IPropertyChangeListener { + + private final CompareEditorInput fCompareEditorInput; + private Button fCommitButton; + private Label statusLabel; + boolean hasSettings = true; + private final DialogCompareContainer fContainer = new DialogCompareContainer(); + + private class DialogCompareContainer extends CompareContainer { + + /* (non-Javadoc) + * @see org.eclipse.jface.operation.IRunnableContext#run(boolean, boolean, org.eclipse.jface.operation.IRunnableWithProgress) + */ + public void run(boolean fork, boolean cancelable, + IRunnableWithProgress runnable) throws InvocationTargetException, + InterruptedException { + ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell()); + dialog.run(fork, cancelable, runnable); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) + */ + public void setStatusMessage(String message) { + if (statusLabel != null && !statusLabel.isDisposed()) { + if (message == null) { + statusLabel.setText(""); //$NON-NLS-1$ + } else { + statusLabel.setText(message); + } + } + } + } + + /** + * Create a dialog to host the given input. + * @param shell a shell + * @param input the dialog input + */ + public CompareDialog(Shell shell, CompareEditorInput input) { + super(shell); + setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX); + Assert.isNotNull(input); + fCompareEditorInput= input; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.ResizableDialog#close() + */ + public boolean close() { + if (super.close()) { + if (fCompareEditorInput != null) + fCompareEditorInput.removePropertyChangeListener(this); + return true; + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite) + */ + protected void createButtonsForButtonBar(Composite parent) { + fCommitButton= createButton(parent, IDialogConstants.OK_ID, getOKButtonLabel(), true); + fCommitButton.setEnabled(isOKEnabled()); + if (isCancelable()) { + createButton(parent, IDialogConstants.CANCEL_ID, getCancelButtonLabel(), false); + } + } + + private String getCancelButtonLabel() { + return fCompareEditorInput.getCancelButtonLabel(); + } + + private boolean isCancelable() { + return isInputEditable() || isElementSelectionDialog(); + } + + private String getOKButtonLabel() { + return fCompareEditorInput.getOKButtonLabel(); + } + + private boolean isElementSelectionDialog() { + return fCompareEditorInput.isEditionSelectionDialog(); + } + + /** + * Return whether the compare editor input of this dialog is editable. + * By default, the input is editable if the compare configuration + * indicates that either the left or right sides are editable. + * Subclasses may override. + * @return whether the compare editor input of this dialog is editable + * @see CompareConfiguration#isLeftEditable() + * @see CompareConfiguration#isRightEditable() + */ + protected boolean isInputEditable() { + return fCompareEditorInput.getCompareConfiguration().isLeftEditable() + || fCompareEditorInput.getCompareConfiguration().isRightEditable(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(CompareEditorInput.DIRTY_STATE) + || event.getProperty().equals(CompareEditorInput.PROP_SELECTED_EDITION)) { + if (fCommitButton != null && fCompareEditorInput != null) + fCommitButton.setEnabled(isOKEnabled()); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE)) { + getShell().setText(fCompareEditorInput.getTitle()); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE_IMAGE)) { + getShell().setImage(fCompareEditorInput.getTitleImage()); + } + } + + private boolean isOKEnabled() { + if (isInputEditable()) + return fCompareEditorInput.isDirty(); + if (isElementSelectionDialog()) + return getSelectedElement() != null; + return true; + } + + private Object getSelectedElement() { + return fCompareEditorInput.getSelectedEdition(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + protected Control createDialogArea(Composite parent2) { + + Composite parent= (Composite) super.createDialogArea(parent2); + + Control c= fCompareEditorInput.createContents(parent); + c.setLayoutData(new GridData(GridData.FILL_BOTH)); + + statusLabel = new Label(parent, SWT.NONE); + statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + Shell shell= c.getShell(); + shell.setText(fCompareEditorInput.getTitle()); + shell.setImage(fCompareEditorInput.getTitleImage()); + applyDialogFont(parent); + return parent; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.window.Window#open() + */ + public int open() { + // Before opening, set the container of the input and listen + // for changes to the input + fCompareEditorInput.addPropertyChangeListener(this); + fCompareEditorInput.setContainer(fContainer); + return super.open(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int) + */ + protected void buttonPressed(int buttonId) { + if (buttonId == OK) { + if (!fCompareEditorInput.okPressed()) + return; + } else if (buttonId == CANCEL) { + fCompareEditorInput.cancelPressed(); + } + super.buttonPressed(buttonId); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings() + */ + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings compareSettings = CompareUIPlugin.getDefault().getDialogSettings(); + String sectionName = this.getClass().getName(); + IDialogSettings dialogSettings = compareSettings.getSection(sectionName); + if (dialogSettings == null) { + hasSettings = false; + dialogSettings = compareSettings.addNewSection(sectionName); + } + return dialogSettings; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.ResizableDialog#configureShell(org.eclipse.swt.widgets.Shell) + */ + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + if (getHelpContextId() != null) + PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, getHelpContextId()); + } + + /** + * Return the help content id for this dialog or <code>null</code>. + * By default, a generic help content id is returned. Subclasses may + * override. + * @return the help content id for this dialog or <code>null</code> + */ + public String getHelpContextId() { + return ICompareContextIds.COMPARE_DIALOG; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#getInitialSize() + */ + protected Point getInitialSize() { + Point initialSize = super.getInitialSize(); + if (hasSettings) { + return initialSize; + } + return getDefaultSize(); + } + + /** + * If we don't have settings we need to come up with a reasonable default + * since we can't depend on the compare editor input layout returning a + * good default size. + * @return the default size of the dialog + */ + protected Point getDefaultSize() { + int width= 0; + int height= 0; + Shell shell= getParentShell(); + if (shell != null) { + Point parentSize= shell.getSize(); + width= parentSize.x-100; + height= parentSize.y-100; + } + if (width < 700) + width= 700; + if (height < 500) + height= 500; + return new Point(width, height); + } + + /** + * Return the compare editor input for this dialog. + * @return the compare editor input for this dialog + */ + protected final CompareEditorInput getInput() { + return fCompareEditorInput; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java new file mode 100644 index 000000000..f3fbc5261 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditor.java @@ -0,0 +1,771 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashSet; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.IPropertyChangeNotifier; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.IStatusLineManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorActionBarContributor; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IReusableEditor; +import org.eclipse.ui.ISaveablesLifecycleListener; +import org.eclipse.ui.ISaveablesSource; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartConstants; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.Saveable; +import org.eclipse.ui.SaveablesLifecycleEvent; +import org.eclipse.ui.actions.WorkspaceModifyOperation; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.part.EditorPart; +import org.eclipse.ui.part.IShowInSource; +import org.eclipse.ui.part.PageBook; +import org.eclipse.ui.services.IServiceLocator; +import org.eclipse.ui.texteditor.ITextEditorExtension3; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + * A CompareEditor takes a ICompareEditorInput as input. + * Most functionality is delegated to the ICompareEditorInput. + */ +public class CompareEditor extends EditorPart implements IReusableEditor, ISaveablesSource, IPropertyChangeListener, ISaveablesLifecycleListener { + + public final static String CONFIRM_SAVE_PROPERTY= "org.eclipse.compare.internal.CONFIRM_SAVE_PROPERTY"; //$NON-NLS-1$ + + private static final int UNINITIALIZED = 0; + private static final int INITIALIZING = 1; + private static final int NO_DIFF = 2; + private static final int CANCELED = 3; + private static final int INITIALIZED = 4; + private static final int ERROR = 5; + private static final int STILL_INITIALIZING = 6; + private static final int CREATING_CONTROL = 7; + private static final int DONE = 8; + + private IActionBars fActionBars; + + private PageBook fPageBook; + + /** the SWT control from the compare editor input*/ + private Control fControl; + /** the outline page */ + private CompareOutlinePage fOutlinePage; + + private CompareSaveable fSaveable; + + private Control initializingPage; + private Control emptyPage; + + private int state = UNINITIALIZED; + private HashSet knownSaveables; + + private final EditorCompareContainer fContainer = new EditorCompareContainer(); + + private class EditorCompareContainer extends CompareContainer { + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider) + */ + public void registerContextMenu(MenuManager menu, ISelectionProvider provider) { + if (getSite() instanceof IEditorSite) { + IEditorSite es = (IEditorSite) getSite(); + es.registerContextMenu(menu, provider, true); + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String) + */ + public void setStatusMessage(String message) { + if (fActionBars != null) { + IStatusLineManager slm= fActionBars.getStatusLineManager(); + if (slm != null) { + slm.setMessage(message); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareContainer#getServiceLocator() + */ + public IServiceLocator getServiceLocator() { + return getSite(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#createWorkerJob() + */ + protected WorkerJob createWorkerJob() { + WorkerJob workerJob = new WorkerJob(getWorkerJobName()) { + public boolean belongsTo(Object family) { + if (family == CompareEditor.this) + return true; + return super.belongsTo(family); + } + }; + return workerJob; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#getWorkerJobName() + */ + protected String getWorkerJobName() { + return NLS.bind(CompareMessages.CompareEditor_2, getTitle()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#getWorkbenchPart() + */ + public IWorkbenchPart getWorkbenchPart() { + return CompareEditor.this; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.CompareContainer#getActionBars() + */ + public IActionBars getActionBars() { + return CompareEditor.this.getActionBars(); + } + } + + /** + * No-argument constructor required for extension points. + */ + public CompareEditor() { + // empty default implementation + } + + /* (non-Javadoc) + * Method declared on IAdaptable + */ + public Object getAdapter(Class key) { + + if (key.equals(IContentOutlinePage.class)) { + Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW); + if (object instanceof Boolean && ((Boolean)object).booleanValue()) { + if (fOutlinePage != null) { + if (fOutlinePage.getControl() != null && fOutlinePage.getControl().isDisposed()) { + fOutlinePage = null; + } else { + return fOutlinePage; + } + } + fOutlinePage= new CompareOutlinePage(this); + return fOutlinePage; + } + } + + if (key == IShowInSource.class + || key == OutlineViewerCreator.class + || key == IFindReplaceTarget.class) { + Object input = getEditorInput(); + if (input != null) { + return Utilities.getAdapter(input, key); + } + } + + if (key == IEditorInput.class) { + return getEditorInput().getAdapter(IEditorInput.class); + } + + if (key == ITextEditorExtension3.class) { + return getEditorInput().getAdapter(ITextEditorExtension3.class); + } + + return super.getAdapter(key); + } + + /* + * Helper method used by ComapreEditorConfiguration to get at the compare configuration of the editor + */ + /* package */ CompareConfiguration getCompareConfiguration() { + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) + return ((CompareEditorInput)input).getCompareConfiguration(); + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput) + */ + public void init(IEditorSite site, IEditorInput input) throws PartInitException { + + if (!(input instanceof CompareEditorInput)) + throw new PartInitException(Utilities.getString("CompareEditor.invalidInput")); //$NON-NLS-1$ + + setSite(site); + setInput(input); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput) + */ + public void setInput(IEditorInput input) { + if (!(input instanceof CompareEditorInput)) { + IStatus s= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, Utilities.getString("CompareEditor.invalidInput"), null); //$NON-NLS-1$ + String title= Utilities.getString("CompareEditor.error.setinput.title"); //$NON-NLS-1$ + String msg= Utilities.getString("CompareEditor.error.setinput.message"); //$NON-NLS-1$ + ErrorDialog.openError(getSite().getShell(), title, msg, s); + return; + } + doSetInput(input); + // Need to refresh the contributor (see #67888) + refreshActionBarsContributor(); + } + + public void refreshActionBarsContributor() { + IEditorSite editorSite= getEditorSite(); + if (editorSite != null) { + IEditorActionBarContributor actionBarContributor= editorSite.getActionBarContributor(); + if (actionBarContributor != null) { + actionBarContributor.setActiveEditor(null); + actionBarContributor.setActiveEditor(this); + } + } + } + + private void doSetInput(IEditorInput input) { + IEditorInput oldInput= getEditorInput(); + disconnectFromInput(oldInput); + Point oldSize = null; + boolean hadPreviousInput = oldInput != null; + if (hadPreviousInput) { + // Cancel any jobs associated with the old input + Job.getJobManager().cancel(this); + if (fControl != null && !fControl.isDisposed()) { + oldSize= fControl.getSize(); + if (emptyPage == null) + emptyPage = new Composite(fPageBook, SWT.NONE); + fPageBook.showPage(emptyPage); + fControl.dispose(); + fControl = null; + } + } + + super.setInput(input); + + if (fOutlinePage != null) + fOutlinePage.reset(); + + final CompareEditorInput cei= (CompareEditorInput) input; + cei.setContainer(fContainer); + setTitleImage(cei.getTitleImage()); + setPartName(cei.getTitle()); + setTitleToolTip(cei.getToolTipText()); + + if (input instanceof IPropertyChangeNotifier) + ((IPropertyChangeNotifier)input).addPropertyChangeListener(this); + + setState(cei.getCompareResult() == null ? INITIALIZING : INITIALIZED); + if (fPageBook != null) + createCompareControl(); + if (fControl != null && oldSize != null) + fControl.setSize(oldSize); + + boolean hasResult = cei.getCompareResult() != null; + if (!hasResult) { + initializeInBackground(cei, hadPreviousInput); + } + + firePropertyChange(IWorkbenchPartConstants.PROP_INPUT); + + // We only need to notify of new Saveables if we are changing inputs + if (hadPreviousInput && hasResult) { + registerSaveable(); + } + } + + private void registerSaveable() { + ISaveablesLifecycleListener lifecycleListener= (ISaveablesLifecycleListener) getSite().getService(ISaveablesLifecycleListener.class); + lifecycleListener.handleLifecycleEvent( + new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_OPEN, internalGetSaveables(true), false)); + } + + private void disconnectFromInput(IEditorInput oldInput) { + if (oldInput != null) { + + if (oldInput instanceof IPropertyChangeNotifier) + ((IPropertyChangeNotifier)oldInput).removePropertyChangeListener(this); + + // Let the workbench know that the old input's saveables are no longer needed + if (knownSaveables != null && !knownSaveables.isEmpty()) { + ISaveablesLifecycleListener lifecycleListener= (ISaveablesLifecycleListener) getSite().getService(ISaveablesLifecycleListener.class); + lifecycleListener.handleLifecycleEvent( + new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_CLOSE, (Saveable[]) knownSaveables.toArray(new Saveable[knownSaveables.size()]), false)); + knownSaveables.clear(); + } + } + } + + protected void initializeInBackground(final CompareEditorInput cei, final boolean hadPreviousInput) { + // Need to cancel any running jobs associated with the oldInput + Job job = new Job(NLS.bind(CompareMessages.CompareEditor_0, cei.getTitle())) { + protected IStatus run(final IProgressMonitor monitor) { + final int[] newState = new int[] { ERROR }; + try { + IStatus status = CompareUIPlugin.getDefault().prepareInput(cei, monitor); + if (status.isOK()) { + // We need to update the saveables list + newState[0] = INITIALIZED; + return Status.OK_STATUS; + } + if (status.getCode() == CompareUIPlugin.NO_DIFFERENCE) { + newState[0] = NO_DIFF; + return Status.OK_STATUS; + } + newState[0] = ERROR; + return status; + } catch (OperationCanceledException e) { + newState[0] = CANCELED; + return Status.CANCEL_STATUS; + } finally { + if (monitor.isCanceled()) + newState[0] = CANCELED; + Display.getDefault().syncExec(new Runnable() { + public void run() { + if (fPageBook.isDisposed()) + return; + // we need to register the saveable if we had a previous input or if + // there are knownSaveables (which means that the workbench called + // getSaveables and got an empty list + if (hadPreviousInput || (knownSaveables != null && !isAllSaveablesKnown())) { + registerSaveable(); + } + setState(newState[0]); + createCompareControl(); + } + }); + monitor.done(); + } + } + public boolean belongsTo(Object family) { + if (family == CompareEditor.this || family == cei) + return true; + return cei.belongsTo(family); + } + }; + job.setUser(true); + Utilities.schedule(job, getSite()); + } + + /* + * Helper method used to find an action bars using the Utilities#findActionsBars(Control) + */ + public IActionBars getActionBars() { + return fActionBars; + } + + /* + * Set the action bars so the Utilities class can access it. + */ + /* package */ void setActionBars(IActionBars actionBars) { + fActionBars= actionBars; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) + */ + public void createPartControl(Composite parent) { + parent.setData(this); + fPageBook = new PageBook(parent, SWT.NONE); + createCompareControl(); + IContextService service = (IContextService)getSite().getService(IContextService.class); + if (service != null) { + service.activateContext("org.eclipse.compare.compareEditorScope"); //$NON-NLS-1$ + service.activateContext("org.eclipse.ui.textEditorScope"); //$NON-NLS-1$ + } + } + + private void createCompareControl() { + if (fPageBook.isDisposed()) + return; + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) { + CompareEditorInput ci = (CompareEditorInput) input; + if (ci.getCompareResult() == null) { + if (getState() == INITIALIZING) { + getSite().setSelectionProvider(new CompareEditorSelectionProvider()); + setPageLater(); + } else if (getState() == STILL_INITIALIZING) { + if (initializingPage == null) { + initializingPage = getInitializingMessagePane(fPageBook); + } + fPageBook.showPage(initializingPage); + } else if (getState() == CANCELED) { + // Close the editor when we are canceled + closeEditor(); + } else if (getState() == NO_DIFF) { + // Prompt and close the editor as well + setState(DONE); + closeEditor(); + CompareUIPlugin.getDefault().handleNoDifference(); + } else if (getState() == ERROR) { + // If an error occurred, close the editor + // (the message would be displayed by the progress view) + closeEditor(); + } + } else if (fControl == null && getState() != CREATING_CONTROL) { + if (getState() == CANCELED) { + // Close the editor when we are canceled, even when compare + // result has been already prepared + closeEditor(); + return; + } + // Set the state in case this method gets called again + setState(CREATING_CONTROL); + if (getSite().getSelectionProvider() == null) + getSite().setSelectionProvider(new CompareEditorSelectionProvider()); + try { + fControl = ci.createContents(fPageBook); + } catch (SWTException e) { + // closed while creating + if (e.code == SWT.ERROR_WIDGET_DISPOSED) { + setState(CANCELED); + return; + } + } + fPageBook.showPage(fControl); + PlatformUI.getWorkbench().getHelpSystem().setHelp(fControl, ICompareContextIds.COMPARE_EDITOR); + if (isActive()) { + setFocus(); + } + setState(INITIALIZED); + } + } + } + + private boolean isActive() { + return getSite().getPage().getActivePart() == this; + } + + private void setPageLater() { + Display.getCurrent().timerExec(1000, new Runnable() { + public void run() { + synchronized(CompareEditor.this) { + if (getState() == INITIALIZING) { + setState(STILL_INITIALIZING); + createCompareControl(); + } + } + } + }); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#dispose() + */ + public void dispose() { + IEditorInput input= getEditorInput(); + if (input instanceof IPropertyChangeNotifier) + ((IPropertyChangeNotifier)input).removePropertyChangeListener(this); + super.dispose(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.WorkbenchPart#setFocus() + */ + public void setFocus() { + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) + if (!((CompareEditorInput)input).setFocus2()) + fPageBook.setFocus(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed() + */ + public boolean isSaveAsAllowed() { + return false; + } + + /* (non-Javadoc) + * Always throws an AssertionFailedException. + * @see org.eclipse.ui.part.EditorPart#doSaveAs() + */ + public void doSaveAs() { + Assert.isTrue(false); // Save As not supported for CompareEditor + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor) + */ + public void doSave(IProgressMonitor progressMonitor) { + + final IEditorInput input= getEditorInput(); + + WorkspaceModifyOperation operation= new WorkspaceModifyOperation() { + public void execute(IProgressMonitor pm) throws CoreException { + if (input instanceof CompareEditorInput) + ((CompareEditorInput)input).saveChanges(pm); + } + }; + + Shell shell= getSite().getShell(); + + try { + + operation.run(progressMonitor); + + firePropertyChange(PROP_DIRTY); + + } catch (InterruptedException x) { + // NeedWork + } catch (OperationCanceledException x) { + // NeedWork + } catch (InvocationTargetException x) { + String title= Utilities.getString("CompareEditor.saveError.title"); //$NON-NLS-1$ + String reason= x.getTargetException().getMessage(); + MessageDialog.openError(shell, title, Utilities.getFormattedString("CompareEditor.cantSaveError", reason)); //$NON-NLS-1$ + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#isDirty() + */ + public boolean isDirty() { + IEditorInput input= getEditorInput(); + if (input instanceof CompareEditorInput) + return ((CompareEditorInput)input).isDirty(); + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(CompareEditorInput.DIRTY_STATE)) { + Object old_value= event.getOldValue(); + Object new_value= event.getNewValue(); + if (old_value == null || new_value == null || !old_value.equals(new_value)) + firePropertyChange(PROP_DIRTY); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE)) { + setPartName(((CompareEditorInput)getEditorInput()).getTitle()); + setTitleToolTip(((CompareEditorInput)getEditorInput()).getToolTipText()); + } else if (event.getProperty().equals(CompareEditorInput.PROP_TITLE_IMAGE)) { + setTitleImage(((CompareEditorInput)getEditorInput()).getTitleImage()); + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.ISaveablesSource#getModels() + */ + public Saveable[] getSaveables() { + return internalGetSaveables(knownSaveables == null); + } + + private Saveable[] internalGetSaveables(boolean init) { + IEditorInput input= getEditorInput(); + Saveable[] sourceSaveables = getSaveables(input); + if (init || knownSaveables == null) { + recordSaveables(sourceSaveables); + } else { + for (int i = 0; i < sourceSaveables.length; i++) { + Saveable saveable = sourceSaveables[i]; + if (!knownSaveables.contains(saveable)) { + CompareUIPlugin.logErrorMessage(NLS.bind("Saveable {0} was not added using a saveables lifecycle event.", saveable.getName())); //$NON-NLS-1$ + knownSaveables.add(saveable); + } + } + if (sourceSaveables.length != knownSaveables.size()) { + CompareUIPlugin.logErrorMessage("Saveables were removed without an appropriate event"); //$NON-NLS-1$ + knownSaveables.clear(); + recordSaveables(sourceSaveables); + } + } + return sourceSaveables; + } + + private boolean isAllSaveablesKnown() { + IEditorInput input= getEditorInput(); + Saveable[] sourceSaveables = getSaveables(input); + if (knownSaveables == null) { + return sourceSaveables.length == 0; + } + if (sourceSaveables.length != knownSaveables.size()) { + return false; + } + for (int i = 0; i < sourceSaveables.length; i++) { + Saveable saveable = sourceSaveables[i]; + if (!knownSaveables.contains(saveable)) { + return false; + } + } + return true; + } + + private void recordSaveables(Saveable[] sourceSaveables) { + if (knownSaveables == null) + knownSaveables = new HashSet(); + for (int i = 0; i < sourceSaveables.length; i++) { + Saveable saveable = sourceSaveables[i]; + knownSaveables.add(saveable); + } + } + + private Saveable[] getSaveables(IEditorInput input) { + if (input instanceof ISaveablesSource) { + ISaveablesSource source= (ISaveablesSource) input; + return source.getSaveables(); + } + return new Saveable[] { getSaveable() }; + } + + private Saveable getSaveable() { + if (fSaveable == null) { + fSaveable= new CompareSaveable(); + } + return fSaveable; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.ISaveablesSource#getActiveModels() + */ + public Saveable[] getActiveSaveables() { + IEditorInput input= getEditorInput(); + if (input instanceof ISaveablesSource) { + ISaveablesSource source= (ISaveablesSource) input; + return source.getActiveSaveables(); + } + return new Saveable[] { getSaveable() }; + } + + private class CompareSaveable extends Saveable { + + public String getName() { + return CompareEditor.this.getPartName(); + } + + public String getToolTipText() { + return CompareEditor.this.getTitleToolTip(); + } + + public ImageDescriptor getImageDescriptor() { + return ImageDescriptor.createFromImage(CompareEditor.this.getTitleImage()); + } + + public void doSave(IProgressMonitor monitor) throws CoreException { + CompareEditor.this.doSave(monitor); + } + + public boolean isDirty() { + return CompareEditor.this.isDirty(); + } + + public boolean equals(Object object) { + return object == this; + } + + public int hashCode() { + return CompareEditor.this.hashCode(); + } + } + + private Composite getInitializingMessagePane(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setBackground(getBackgroundColor(parent)); + GridLayout layout = new GridLayout(); + layout.numColumns = 3; + composite.setLayout(layout); + + createDescriptionLabel(composite, CompareMessages.CompareEditor_1); + return composite; + } + + private Color getBackgroundColor(Composite parent) { + return parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + } + + private Label createDescriptionLabel(Composite parent, String text) { + Label description = new Label(parent, SWT.WRAP); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalSpan = 2; + description.setLayoutData(data); + description.setText(text); + description.setBackground(getBackgroundColor(parent)); + return description; + } + + private void closeEditor() { + getSite().getPage().closeEditor(CompareEditor.this, false); + } + + private synchronized void setState(int state) { + this.state = state; + } + + private int getState() { + return state; + } + + public void handleLifecycleEvent(SaveablesLifecycleEvent event) { + ISaveablesLifecycleListener lifecycleListener= (ISaveablesLifecycleListener) getSite().getService(ISaveablesLifecycleListener.class); + if (event.getEventType() == SaveablesLifecycleEvent.POST_CLOSE) { + // We may get a post close for a saveable that is not known to the workbench. + // Only pass on the event for known saveables + if (knownSaveables == null || knownSaveables.isEmpty()) + return; + java.util.List result = new ArrayList(); + Saveable[] all = event.getSaveables(); + for (int i = 0; i < all.length; i++) { + Saveable saveable = all[i]; + if (knownSaveables.contains(saveable)) + result.add(saveable); + knownSaveables.remove(saveable); + } + if (result.isEmpty()) + return; + event = new SaveablesLifecycleEvent(this, + SaveablesLifecycleEvent.POST_CLOSE, + (Saveable[]) result.toArray(new Saveable[result.size()]), + false); + } else if (event.getEventType() == SaveablesLifecycleEvent.POST_OPEN) { + recordSaveables(event.getSaveables()); + } + lifecycleListener.handleLifecycleEvent(event); + } + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java new file mode 100644 index 000000000..fc427fe23 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorContributor.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ResourceBundle; + +import org.eclipse.jface.action.*; + +import org.eclipse.ui.*; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.help.IWorkbenchHelpSystem; +import org.eclipse.ui.part.EditorActionBarContributor; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; + +import org.eclipse.compare.*; + + +public class CompareEditorContributor extends EditorActionBarContributor { + + private IEditorPart fActiveEditorPart= null; + + private ChangePropertyAction fIgnoreWhitespace; + private NavigationAction fNext; + private NavigationAction fPrevious; + + private NavigationAction fToolbarNext; + private NavigationAction fToolbarPrevious; + + public CompareEditorContributor() { + ResourceBundle bundle= CompareUI.getResourceBundle(); + + IWorkbenchHelpSystem helpSystem= PlatformUI.getWorkbench().getHelpSystem(); + + fIgnoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle, null); + helpSystem.setHelp(fIgnoreWhitespace, ICompareContextIds.IGNORE_WHITESPACE_ACTION); + + fNext= new NavigationAction(bundle, true); + helpSystem.setHelp(fNext, ICompareContextIds.GLOBAL_NEXT_DIFF_ACTION); + + fPrevious= new NavigationAction(bundle, false); + helpSystem.setHelp(fPrevious, ICompareContextIds.GLOBAL_PREVIOUS_DIFF_ACTION); + + fToolbarNext= new NavigationAction(bundle, true); + helpSystem.setHelp(fToolbarNext, ICompareContextIds.NEXT_DIFF_ACTION); + + fToolbarPrevious= new NavigationAction(bundle, false); + helpSystem.setHelp(fToolbarPrevious, ICompareContextIds.PREVIOUS_DIFF_ACTION); + } + + /* + * @see EditorActionBarContributor#contributeToToolBar(IToolBarManager) + */ + public void contributeToToolBar(IToolBarManager tbm) { + tbm.add(new Separator()); + tbm.add(fIgnoreWhitespace); + tbm.add(fToolbarNext); + tbm.add(fToolbarPrevious); + } + + /* + * @see EditorActionBarContributor#contributeToMenu(IMenuManager) + */ + public void contributeToMenu(IMenuManager menuManager) { + // empty implementation + } + + public void setActiveEditor(IEditorPart targetEditor) { + + if (fActiveEditorPart == targetEditor) + return; + + fActiveEditorPart= targetEditor; + + if (fActiveEditorPart != null) { + IEditorInput input= fActiveEditorPart.getEditorInput(); + if (input instanceof CompareEditorInput) { + CompareEditorInput compareInput= (CompareEditorInput) input; + fNext.setCompareEditorInput(compareInput); + fPrevious.setCompareEditorInput(compareInput); + // Begin fix http://bugs.eclipse.org/bugs/show_bug.cgi?id=20105 + fToolbarNext.setCompareEditorInput(compareInput); + fToolbarPrevious.setCompareEditorInput(compareInput); + // End fix http://bugs.eclipse.org/bugs/show_bug.cgi?id=20105 + } + } + + if (targetEditor instanceof CompareEditor) { + IActionBars actionBars= getActionBars(); + + CompareEditor editor= (CompareEditor) targetEditor; + editor.setActionBars(actionBars); + + actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), fNext); + actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), fPrevious); + + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION, fNext); + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION, fPrevious); + + CompareConfiguration cc= editor.getCompareConfiguration(); + fIgnoreWhitespace.setCompareConfiguration(cc); + } else { + IActionBars actionBars= getActionBars(); + actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), null); + actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), null); + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION, null); + actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION, null); + } + } + + public void dispose() { + setActiveEditor(null); + super.dispose(); + fIgnoreWhitespace.dispose(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java new file mode 100644 index 000000000..9fc2ccf8b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorInputNavigator.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.compare.*; + +/** + * Supports cross-pane navigation through the differences contained in a {@link CompareEditorInput} + * or a similar type of compare container. + * @see INavigatable + */ +public class CompareEditorInputNavigator extends CompareNavigator { + + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + private boolean fNextFirstTime= true; + private Object[] fPanes; + + /** + * Create a navigator for navigating the given panes + * @param panes the panes to navigate. + */ + public CompareEditorInputNavigator(Object[] panes) { + fPanes= panes; + } + + /** + * Return the set of panes that this navigator is navigating. + * The {@link INavigatable} is obtain from each pane using the + * adaptable mechanism. + * @return the set of panes that this navigator is navigating + */ + public Object[] getPanes() { + return fPanes; + } + + protected INavigatable[] getNavigatables() { + List result = new ArrayList(); + Object[] panes = getPanes(); + for (int i = 0; i < panes.length; i++) { + Object pane = panes[i]; + INavigatable navigator= getNavigator(pane); + if (navigator != null) + result.add(navigator); + } + return (INavigatable[]) result.toArray(new INavigatable[result.size()]); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ICompareNavigator#selectChange(boolean) + */ + public boolean selectChange(boolean next) { + // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + if (next && fNextFirstTime && mustOpen()) { + fNextFirstTime= false; + if (openElement()) + return false; + } + return super.selectChange(next); + } + + /* + * Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + */ + private boolean mustOpen() { + Object[] panes = getPanes(); + if (panes == null || panes.length == 0) + return false; + for (int i= 1; i < panes.length; i++) { + Object pane= panes[i]; + INavigatable nav = getNavigator(pane); + if (nav != null && nav.getInput() != null) + return false; + } + return true; + } + + /* + * Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=20106 + */ + private boolean openElement() { + Object[] panes = getPanes(); + if (panes == null || panes.length == 0) + return false; + INavigatable nav = getNavigator(panes[0]); + if (nav != null) { + if (!nav.openSelectedChange()) + // selected change not opened, open first instead + nav.selectChange(INavigatable.FIRST_CHANGE); + return true; + } + return false; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java new file mode 100644 index 000000000..771688bcd --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareEditorSelectionProvider.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.widgets.Widget; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; + +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; + +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.TextSelection; +import org.eclipse.jface.text.TextViewer; + + +/** + * A selection provider for view parts with more that one viewer. Tracks the + * focus of the viewers to provide the correct selection. + * + * This is a modified version of + * org.eclipse.jdt.internal.ui.viewsupport.SelectionProviderMediator + */ +public class CompareEditorSelectionProvider implements IPostSelectionProvider { + + private class InternalListener implements ISelectionChangedListener, FocusListener { + /* + * @see ISelectionChangedListener#selectionChanged + */ + public void selectionChanged(SelectionChangedEvent event) { + doSelectionChanged(event); + } + + /* + * @see FocusListener#focusGained + */ + public void focusGained(FocusEvent e) { + // expecting a StyledText widget here + doFocusChanged(e.widget); + } + + /* + * @see FocusListener#focusLost + */ + public void focusLost(FocusEvent e) { + // do not reset due to focus behavior on GTK + //fViewerInFocus= null; + } + } + + private class InternalPostSelectionListener implements ISelectionChangedListener { + public void selectionChanged(SelectionChangedEvent event) { + doPostSelectionChanged(event); + } + + } + + private TextViewer[] fViewers; + + private TextViewer fViewerInFocus; + private ListenerList fSelectionChangedListeners; + private ListenerList fPostSelectionChangedListeners; + + public CompareEditorSelectionProvider() { + fSelectionChangedListeners = new ListenerList(); + fPostSelectionChangedListeners = new ListenerList(); + // nothing more to do here, Compare Editor is initializing + } + + /** + * @param viewers All viewers that can provide a selection + * @param viewerInFocus the viewer currently in focus or <code>null</code> + */ + public void setViewers(TextViewer[] viewers, TextViewer viewerInFocus) { + Assert.isNotNull(viewers); + fViewers= viewers; + InternalListener listener= new InternalListener(); + fViewerInFocus= viewerInFocus; + + for (int i= 0; i < fViewers.length; i++) { + TextViewer viewer= fViewers[i]; + viewer.addSelectionChangedListener(listener); + viewer.addPostSelectionChangedListener(new InternalPostSelectionListener()); + StyledText textWidget = viewer.getTextWidget(); + textWidget.addFocusListener(listener); + } + } + + private void doFocusChanged(Widget control) { + for (int i= 0; i < fViewers.length; i++) { + if (fViewers[i].getTextWidget() == control) { + propagateFocusChanged(fViewers[i]); + return; + } + } + } + + final void doPostSelectionChanged(SelectionChangedEvent event) { + ISelectionProvider provider= event.getSelectionProvider(); + if (provider == fViewerInFocus) { + firePostSelectionChanged(); + } + } + + final void doSelectionChanged(SelectionChangedEvent event) { + ISelectionProvider provider= event.getSelectionProvider(); + if (provider == fViewerInFocus) { + fireSelectionChanged(); + } + } + + final void propagateFocusChanged(TextViewer viewer) { + if (viewer != fViewerInFocus) { // OK to compare by identity + fViewerInFocus= viewer; + fireSelectionChanged(); + firePostSelectionChanged(); + } + } + + private void fireSelectionChanged() { + if (fSelectionChangedListeners != null) { + SelectionChangedEvent event= new SelectionChangedEvent(this, getSelection()); + + Object[] listeners= fSelectionChangedListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ISelectionChangedListener listener= (ISelectionChangedListener) listeners[i]; + listener.selectionChanged(event); + } + } + } + + private void firePostSelectionChanged() { + if (fPostSelectionChangedListeners != null) { + SelectionChangedEvent event= new SelectionChangedEvent(this, getSelection()); + + Object[] listeners= fPostSelectionChangedListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + ISelectionChangedListener listener= (ISelectionChangedListener) listeners[i]; + listener.selectionChanged(event); + } + } + } + + /* + * @see ISelectionProvider#addSelectionChangedListener + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + fSelectionChangedListeners.add(listener); + } + + /* + * @see ISelectionProvider#removeSelectionChangedListener + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + fSelectionChangedListeners.remove(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IPostSelectionProvider#addPostSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + fPostSelectionChangedListeners.add(listener); + } + + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IPostSelectionProvider#removePostSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + fPostSelectionChangedListeners.remove(listener); + } + + /* + * @see ISelectionProvider#getSelection + */ + public ISelection getSelection() { + if (fViewerInFocus != null) { + return fViewerInFocus.getSelection(); + } + return TextSelection.emptySelection(); + } + + /* + * @see ISelectionProvider#setSelection + */ + public void setSelection(ISelection selection) { + setSelection(selection, true); + } + + public void setSelection(ISelection selection, boolean reveal) { + if (fViewerInFocus != null) { + if (reveal && !isSelectionInsideVisibleRegion(fViewerInFocus, selection)) + resetVisibleRegion(); + fViewerInFocus.setSelection(selection, reveal); + } + } + + /** + * Resets the visible region for all text viewers of this selection provider. + * + * @since 3.6 + */ + private void resetVisibleRegion() { + if (fViewers == null) + return; + + for (int i= 0; i < fViewers.length; i++) + fViewers[i].setVisibleRegion(0, fViewers[i].getDocument().getLength()); + } + + /** + * Tells whether the given selection is inside the text viewer's visible region. + * + * @param textViewer the text viewer + * @param selection the selection + * @return <code>true</code> if the selection is inside the text viewer's visible region + * @since 3.6 + */ + private static boolean isSelectionInsideVisibleRegion(TextViewer textViewer, ISelection selection) { + if (!(selection instanceof ITextSelection)) + return false; + + ITextSelection textSelection= (ITextSelection)selection; + IRegion visibleRegion= textViewer.getVisibleRegion(); + + return textSelection.getOffset() >= visibleRegion.getOffset() && textSelection.getOffset() + textSelection.getLength() <= visibleRegion.getOffset() + visibleRegion.getLength(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java new file mode 100644 index 000000000..3a17994be --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareFilter.java @@ -0,0 +1,397 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import com.ibm.icu.text.MessageFormat; +import java.util.StringTokenizer; + +import org.eclipse.core.resources.*; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.runtime.IStatus; + + +public class CompareFilter { + private static final char[][] NO_CHAR_CHAR= new char[0][]; + + private char[][] fExtraResourceFileFilters; + private String[] fExtraResourceFolderFilters; + + + public CompareFilter() { + // nothing to do + } + + /* + * Returns true if path matches filter, that is if path should be filtered. + */ + public boolean filter(String path0, boolean folder, boolean isArchive) { + if (!folder && fExtraResourceFileFilters != null) { + char[] name= path0.toCharArray(); + for (int i= 0, l= fExtraResourceFileFilters.length; i < l; i++) + if (match(fExtraResourceFileFilters[i], name, true)) + return true; + } + if (folder && fExtraResourceFolderFilters != null) { + for (int i= 0, l= fExtraResourceFolderFilters.length; i < l; i++) + if (fExtraResourceFolderFilters[i].equals(path0)) + return true; + } + return false; + } + + public static String validateResourceFilters(String text) { + IWorkspace workspace= ResourcesPlugin.getWorkspace(); + String[] filters= getTokens(text, ","); //$NON-NLS-1$ + for (int i= 0; i < filters.length; i++) { + String fileName= filters[i].replace('*', 'x'); + int resourceType= IResource.FILE; + int lastCharacter= fileName.length() - 1; + if (lastCharacter >= 0 && fileName.charAt(lastCharacter) == '/') { + fileName= fileName.substring(0, lastCharacter); + resourceType= IResource.FOLDER; + } + IStatus status= workspace.validateName(fileName, resourceType); + if (status.matches(IStatus.ERROR)) { + String format= Utilities.getString("ComparePreferencePage.filter.invalidsegment.error"); //$NON-NLS-1$ + return MessageFormat.format(format, new String[] { status.getMessage() } ); + } + } + return null; + } + + public void setFilters(String filterSequence) { + char[][] filters= filterSequence != null && filterSequence.length() > 0 + ? splitAndTrimOn(',', filterSequence.toCharArray()) + : null; + if (filters == null) { + fExtraResourceFileFilters= null; + fExtraResourceFolderFilters= null; + } else { + int fileCount= 0, folderCount= 0; + for (int i= 0, l= filters.length; i < l; i++) { + char[] f= filters[i]; + if (f.length == 0) + continue; + if (f[f.length - 1] == '/') + folderCount++; + else + fileCount++; + } + fExtraResourceFileFilters= new char[fileCount][]; + fExtraResourceFolderFilters= new String[folderCount]; + for (int i= 0, l= filters.length; i < l; i++) { + char[] f= filters[i]; + if (f.length == 0) + continue; + if (f[f.length - 1] == '/') + fExtraResourceFolderFilters[--folderCount]= new String(subarray(f, 0, f.length - 1)); + else + fExtraResourceFileFilters[--fileCount]= f; + } + } + } + + ///////// + + private static String[] getTokens(String text, String separator) { + StringTokenizer tok= new StringTokenizer(text, separator); + int nTokens= tok.countTokens(); + String[] res= new String[nTokens]; + for (int i= 0; i < res.length; i++) + res[i]= tok.nextToken().trim(); + return res; + } + + /** + * Answers true if the pattern matches the given name, false otherwise. + * This char[] pattern matching accepts wild-cards '*' and '?'. + * + * When not case sensitive, the pattern is assumed to already be + * lowercased, the name will be lowercased character per character as + * comparing. If name is null, the answer is false. If pattern is null, the + * answer is true if name is not null. <br><br>For example: + * <ol> + * <li> + * + * <pre> + * pattern = { '?', 'b', '*' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => true + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * pattern = { '?', 'b', '?' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => false + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * pattern = { 'b', '*' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => false + * </pre> + * + * + * </li> + * </ol> + * + * @param pattern + * the given pattern + * @param name + * the given name + * @param isCaseSensitive + * flag to know whether or not the matching should be case + * sensitive + * @return true if the pattern matches the given name, false otherwise + */ + private boolean match(char[] pattern, char[] name, boolean isCaseSensitive) { + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + return match(pattern, 0, pattern.length, name, 0, name.length, isCaseSensitive); + } + + /** + * Answers true if the a sub-pattern matches the subpart of the given name, + * false otherwise. char[] pattern matching, accepting wild-cards '*' and + * '?'. Can match only subset of name/pattern. end positions are + * non-inclusive. The subpattern is defined by the patternStart and + * pattternEnd positions. When not case sensitive, the pattern is assumed + * to already be lowercased, the name will be lowercased character per + * character as comparing. <br><br>For example: + * <ol> + * <li> + * + * <pre> + * pattern = { '?', 'b', '*' } patternStart = 1 patternEnd = 3 name = { 'a', 'b', 'c' , 'd' } nameStart = 1 nameEnd = 4 isCaseSensitive = true result => true + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * pattern = { '?', 'b', '*' } patternStart = 1 patternEnd = 2 name = { 'a', 'b', 'c' , 'd' } nameStart = 1 nameEnd = 2 isCaseSensitive = true result => false + * </pre> + * + * + * </li> + * </ol> + * + * @param pattern + * the given pattern + * @param patternStart + * the given pattern start + * @param patternEnd + * the given pattern end + * @param name + * the given name + * @param nameStart + * the given name start + * @param nameEnd + * the given name end + * @param isCaseSensitive + * flag to know if the matching should be case sensitive + * @return true if the a sub-pattern matches the subpart of the given name, + * false otherwise + */ + private boolean match(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd, + boolean isCaseSensitive) { + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + int iPattern= patternStart; + int iName= nameStart; + if (patternEnd < 0) + patternEnd= pattern.length; + if (nameEnd < 0) + nameEnd= name.length; + /* check first segment */ + char patternChar= 0; + while ((iPattern < patternEnd) && (patternChar= pattern[iPattern]) != '*') { + if (iName == nameEnd) + return false; + if (patternChar != (isCaseSensitive ? name[iName] : Character.toLowerCase(name[iName])) && patternChar != '?') { + return false; + } + iName++; + iPattern++; + } + /* check sequence of star+segment */ + int segmentStart; + if (patternChar == '*') { + segmentStart= ++iPattern; // skip star + } else { + segmentStart= 0; // force iName check + } + int prefixStart= iName; + checkSegment : while (iName < nameEnd) { + if (iPattern == patternEnd) { + iPattern= segmentStart; // mismatch - restart current segment + iName= ++prefixStart; + continue checkSegment; + } + /* segment is ending */ + if ((patternChar= pattern[iPattern]) == '*') { + segmentStart= ++iPattern; // skip start + if (segmentStart == patternEnd) { + return true; + } + prefixStart= iName; + continue checkSegment; + } + /* check current name character */ + if ((isCaseSensitive ? name[iName] : Character.toLowerCase(name[iName])) != patternChar && patternChar != '?') { + iPattern= segmentStart; // mismatch - restart current segment + iName= ++prefixStart; + continue checkSegment; + } + iName++; + iPattern++; + } + return (segmentStart == patternEnd) || (iName == nameEnd && iPattern == patternEnd) + || (iPattern == patternEnd - 1 && pattern[iPattern] == '*'); + } + + /** + * Return a new array which is the split of the given array using the given + * divider and triming each subarray to remove whitespaces equals to ' '. + * <br><br>For example: + * <ol> + * <li> + * + * <pre> + * divider = 'b' array = { 'a' , 'b', 'b', 'a', 'b', 'a' } result => { { 'a' }, { }, { 'a' }, { 'a' } } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * divider = 'c' array = { 'a' , 'b', 'b', 'a', 'b', 'a' } result => { { 'a', 'b', 'b', 'a', 'b', 'a' } } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * divider = 'b' array = { 'a' , ' ', 'b', 'b', 'a', 'b', 'a' } result => { { 'a' }, { }, { 'a' }, { 'a' } } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * divider = 'c' array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' } result => { { 'a', 'b', 'b', 'a', 'b', 'a' } } + * </pre> + * + * + * </li> + * </ol> + * + * @param divider + * the given divider + * @param array + * the given array + * @return a new array which is the split of the given array using the + * given divider and triming each subarray to remove whitespaces + * equals to ' ' + */ + private char[][] splitAndTrimOn(char divider, char[] array) { + int length= array == null ? 0 : array.length; + if (length == 0) + return NO_CHAR_CHAR; + int wordCount= 1; + for (int i= 0; i < length; i++) + if (array[i] == divider) + wordCount++; + char[][] split= new char[wordCount][]; + int last= 0, currentWord= 0; + for (int i= 0; i < length; i++) { + if (array[i] == divider) { + int start= last, end= i - 1; + while (start < i && array[start] == ' ') + start++; + while (end > start && array[end] == ' ') + end--; + split[currentWord]= new char[end - start + 1]; + System.arraycopy(array, start, split[currentWord++], 0, end - start + 1); + last= i + 1; + } + } + int start= last, end= length - 1; + while (start < length && array[start] == ' ') + start++; + while (end > start && array[end] == ' ') + end--; + split[currentWord]= new char[end - start + 1]; + System.arraycopy(array, start, split[currentWord++], 0, end - start + 1); + return split; + } + + /** + * Answers a new array which is a copy of the given array starting at the + * given start and ending at the given end. The given start is inclusive + * and the given end is exclusive. Answers null if start is greater than + * end, if start is lower than 0 or if end is greater than the length of + * the given array. If end equals -1, it is converted to the array length. + * <br><br>For example: + * <ol> + * <li> + * + * <pre> + * array = { 'a' , 'b' } start = 0 end = 1 result => { 'a' } + * </pre> + * + * + * </li> + * <li> + * + * <pre> + * array = { 'a', 'b' } start = 0 end = -1 result => { 'a' , 'b' } + * </pre> + * + * + * </li> + * </ol> + * + * @param array + * the given array + * @param start + * the given starting index + * @param end + * the given ending index + * @return a new array which is a copy of the given array starting at the + * given start and ending at the given end + * @exception NullPointerException + * if the given array is null + */ + private char[] subarray(char[] array, int start, int end) { + if (end == -1) + end= array.length; + if (start > end) + return null; + if (start < 0) + return null; + if (end > array.length) + return null; + char[] result= new char[end - start]; + System.arraycopy(array, start, result, 0, end - start); + return result; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java new file mode 100644 index 000000000..809e79d21 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareHandlerService.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.compare.ICompareContainer; +import org.eclipse.core.expressions.Expression; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.commands.ActionHandler; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.*; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.services.IServiceLocator; + +public class CompareHandlerService { + + private final List fActivations = new ArrayList(); + private final Expression fExpression; + private ICompareContainer fContainer; + private boolean fDisposed; + private List fPaneActivations = new ArrayList(); + private IHandlerService fHandlerService; + + public static CompareHandlerService createFor(ICompareContainer container, Shell shell) { + IServiceLocator serviceLocator = container.getServiceLocator(); + if (serviceLocator != null) { + IHandlerService service = (IHandlerService)serviceLocator.getService(IHandlerService.class); + if (service != null) + return new CompareHandlerService(container, null); + } + if (container.getWorkbenchPart() == null && shell != null) { + // We're in a dialog so we can use an active shell expression + IHandlerService service = (IHandlerService)PlatformUI.getWorkbench().getService(IHandlerService.class); + if (service != null) { + Expression e = new ActiveShellExpression(shell); + return new CompareHandlerService(container, e); + } + } + return new CompareHandlerService(null, null); + } + + private CompareHandlerService(ICompareContainer container, + Expression expression) { + fContainer = container; + fExpression = expression; + initialize(); + } + + public void registerAction(IAction action, String commandId) { + IHandlerService handlerService = getHandlerService(); + if (handlerService == null) + return; + action.setActionDefinitionId(commandId); + IHandlerActivation activation; + if (fExpression == null) { + activation = handlerService.activateHandler(commandId, new ActionHandler(action)); + } else { + activation = handlerService.activateHandler(commandId, new ActionHandler(action), fExpression); + } + if (activation != null) { + fActivations .add(activation); + } + } + + private IHandlerService getHandlerService() { + if (fDisposed) + return null; + return fHandlerService; + } + + private void initialize() { + if (fHandlerService == null) { + IServiceLocator serviceLocator = fContainer.getServiceLocator(); + if (serviceLocator != null) { + IHandlerService service = (IHandlerService)serviceLocator.getService(IHandlerService.class); + if (service != null) + fHandlerService = service; + } + if (fHandlerService == null && fContainer.getWorkbenchPart() == null && fExpression != null) { + // We're in a dialog so we can use an active shell expression + IHandlerService service = (IHandlerService)PlatformUI.getWorkbench().getService(IHandlerService.class); + if (service != null) { + fHandlerService = service; + } + } + } + } + + public void setGlobalActionHandler(String actionId, IAction actionHandler) { + IActionBars bars = getActionBars(); + if (bars != null) { + bars.setGlobalActionHandler(actionId, actionHandler); + return; + } else if (fExpression != null && actionHandler != null && actionHandler.getActionDefinitionId() != null) { + IHandlerService service = getHandlerService(); + if (service != null) { + IHandlerActivation activation = service.activateHandler(actionHandler.getActionDefinitionId(), new ActionHandler(actionHandler), fExpression); + fPaneActivations.add(activation); + return; + } + } + // Remove the action definition id since we won't get key bindings + if (actionHandler != null) + actionHandler.setActionDefinitionId(null); + } + + private void updateActionBars() { + IActionBars bars = getActionBars(); + if (bars != null) + bars.updateActionBars(); + } + + private void clearPaneActionHandlers() { + if (!fPaneActivations.isEmpty()) { + IHandlerService service = getHandlerService(); + if (service != null) { + service.deactivateHandlers(fPaneActivations); + fPaneActivations.clear(); + } + } + } + + private IActionBars getActionBars() { + return fContainer.getActionBars(); + } + + public void dispose() { + clearPaneActionHandlers(); + IHandlerService service = getHandlerService(); + if (service == null) + return; + service.deactivateHandlers(fActivations); + fActivations.clear(); + fDisposed = true; + } + + public void updatePaneActionHandlers(Runnable runnable) { + clearPaneActionHandlers(); + runnable.run(); + updateActionBars(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java new file mode 100644 index 000000000..6be02f6ff --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959, Bug 73923 + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.osgi.util.NLS; + +public final class CompareMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.CompareMessages";//$NON-NLS-1$ + + private CompareMessages() { + // Do not instantiate + } + + public static String CompareContainer_0; + public static String CompareDialog_commit_button; + public static String CompareDialog_error_message; + public static String CompareDialog_error_title; + public static String CompareEditor_0; + public static String CompareEditor_1; + public static String CompareEditor_2; + public static String DocumentMerger_0; + public static String DocumentMerger_1; + public static String DocumentMerger_2; + public static String DocumentMerger_3; + public static String CompareEditorInput_0; + public static String ComparePlugin_internal_error; + public static String ComparePreferencePage_0; + public static String ComparePreferencePage_1; + public static String ComparePreferencePage_2; + public static String ComparePreferencePage_3; + public static String ComparePreferencePage_4; + public static String CompareUIPlugin_0; + public static String CompareUIPlugin_1; + public static String ContentMergeViewer_resource_changed_description; + public static String ContentMergeViewer_resource_changed_title; + public static String ExceptionDialog_seeErrorLogMessage; + public static String CompareViewerSwitchingPane_Titleformat; + public static String NavigationEndDialog_0; + public static String NavigationEndDialog_1; + public static String ShowWhitespaceAction_0; + public static String StructureDiffViewer_0; + public static String StructureDiffViewer_1; + public static String StructureDiffViewer_2; + public static String StructureDiffViewer_3; + public static String StructureDiffViewer_NoStructuralDifferences; + public static String StructureDiffViewer_StructureError; + public static String TextMergeViewer_0; + public static String TextMergeViewer_1; + public static String TextMergeViewer_10; + public static String TextMergeViewer_11; + public static String TextMergeViewer_12; + public static String TextMergeViewer_13; + public static String TextMergeViewer_14; + public static String TextMergeViewer_15; + public static String TextMergeViewer_16; + public static String TextMergeViewer_17; + public static String TextMergeViewer_2; + public static String TextMergeViewer_3; + public static String TextMergeViewer_4; + public static String TextMergeViewer_5; + public static String TextMergeViewer_6; + public static String TextMergeViewer_7; + public static String TextMergeViewer_8; + public static String TextMergeViewer_9; + public static String TextMergeViewer_accessible_ancestor; + public static String TextMergeViewer_accessible_left; + public static String TextMergeViewer_accessible_right; + public static String TextMergeViewer_cursorPosition_format; + public static String TextMergeViewer_beforeLine_format; + public static String TextMergeViewer_range_format; + public static String TextMergeViewer_changeType_addition; + public static String TextMergeViewer_changeType_deletion; + public static String TextMergeViewer_changeType_change; + public static String TextMergeViewer_direction_outgoing; + public static String TextMergeViewer_direction_incoming; + public static String TextMergeViewer_direction_conflicting; + public static String TextMergeViewer_diffType_format; + public static String TextMergeViewer_diffDescription_noDiff_format; + public static String TextMergeViewer_diffDescription_diff_format; + public static String TextMergeViewer_statusLine_format; + public static String TextMergeViewer_atEnd_title; + public static String TextMergeViewer_atEnd_message; + public static String TextMergeViewer_atBeginning_title; + public static String TextMergeViewer_atBeginning_message; + public static String CompareNavigator_atEnd_title; + public static String CompareNavigator_atEnd_message; + public static String CompareNavigator_atBeginning_title; + public static String CompareNavigator_atBeginning_message; + public static String WorkerJob_0; + public static String SelectAncestorDialog_title; + public static String SelectAncestorDialog_message; + public static String SelectAncestorDialog_option; + public static String CompareWithOtherResourceDialog_ancestor; + public static String CompareWithOtherResourceDialog_rightPanel; + public static String CompareWithOtherResourceDialog_leftPanel; + public static String CompareWithOtherResourceDialog_dialogTitle; + public static String CompareWithOtherResourceDialog_dialogMessage; + public static String CompareWithOtherResourceDialog_error_not_comparable; + public static String CompareWithOtherResourceDialog_error_empty; + public static String CompareWithOtherResourceDialog_clear; + public static String CompareWithOtherResourceDialog_info; + public static String CompareWithOtherResourceDialog_externalFile_errorTitle; + public static String CompareWithOtherResourceDialog_externalFile_errorMessage; + public static String CompareWithOtherResourceDialog_externalFileMainButton; + public static String CompareWithOtherResourceDialog_externalFileRadioButton; + public static String CompareWithOtherResourceDialog_externalFolderMainButton; + public static String CompareWithOtherResourceDialog_externalFolderRadioButton; + public static String CompareWithOtherResourceDialog_workspaceMainButton; + public static String CompareWithOtherResourceDialog_workspaceRadioButton; + public static String CompareContentViewerSwitchingPane_defaultViewer; + public static String CompareContentViewerSwitchingPane_switchButtonTooltip; + public static String CompareContentViewerSwitchingPane_discoveredLabel; + public static String CompareContentViewerSwitchingPane_optimized; + public static String CompareContentViewerSwitchingPane_optimizedTooltip; + public static String CompareStructureViewerSwitchingPane_defaultViewer; + public static String CompareStructureViewerSwitchingPane_switchButtonTooltip; + public static String CompareStructureViewerSwitchingPane_discoveredLabel; + + public static String ReaderCreator_fileIsNotAccessible; + + static { + NLS.initializeMessages(BUNDLE_NAME, CompareMessages.class); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties new file mode 100644 index 000000000..451ac3bb6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareMessages.properties @@ -0,0 +1,143 @@ +############################################################################### +# Copyright (c) 2000, 2010 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +# Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource +# Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959, Bug 73923 +############################################################################### + +ComparePlugin_internal_error= Internal Error +ExceptionDialog_seeErrorLogMessage= See error log for details. + +# +# Title format for CompareViewerSwitchingPane +# +CompareViewerSwitchingPane_Titleformat= {0} ({1}) + +# +# Title message for StructureDiffViewer if no structural differences could be found +# +StructureDiffViewer_NoStructuralDifferences= No Structural Differences +StructureDiffViewer_StructureError= Cannot Compare Structures +StructureDiffViewer_0=Generating Structure Differences +StructureDiffViewer_1=Computing Structure Differences +StructureDiffViewer_2=Generating Structure Differences +StructureDiffViewer_3=Refresh Canceled + +# +# TextMergeViewer +# +TextMergeViewer_cursorPosition_format= {0} : {1} + +TextMergeViewer_beforeLine_format= before line {0} +TextMergeViewer_range_format= {0} : {1} + +TextMergeViewer_changeType_addition= addition +TextMergeViewer_changeType_deletion= deletion +TextMergeViewer_changeType_change= change + +TextMergeViewer_direction_outgoing= outgoing +TextMergeViewer_direction_incoming= incoming +TextMergeViewer_direction_conflicting= conflicting + +TextMergeViewer_diffType_format= {0} {1} +TextMergeViewer_accessible_left=Left: {0} + +TextMergeViewer_diffDescription_noDiff_format= no diff +TextMergeViewer_diffDescription_diff_format= {0} #{1} (Left: {2}, Right: {3}) +TextMergeViewer_statusLine_format= Left: {0}, Right: {1}, {2} + +TextMergeViewer_atEnd_title= Go to Next Difference +TextMergeViewer_0=End Reached +TextMergeViewer_1=You have reached the end of the currently displayed element. What would you like to do? +TextMergeViewer_2=&Go to the beginning of this element +TextMergeViewer_3=&Display the next element +TextMergeViewer_4=Beginning Reached +TextMergeViewer_5=You have reached the beginning of the currently displayed element. What would you like to do? +TextMergeViewer_6=&Go to the end of this element +TextMergeViewer_7=&Display the previous element +TextMergeViewer_8=End Reached +TextMergeViewer_9=You have reached the last difference. Would you like go to the beginning of this element? +TextMergeViewer_10=Beginning Reached +TextMergeViewer_11=You have reached the first difference. Would you like to go to the end of this element? +TextMergeViewer_12=Element is Read Only +TextMergeViewer_13=The element being edited is read-only +TextMergeViewer_14=Element is Read Only +TextMergeViewer_15=The element being edited is read-only +TextMergeViewer_16=Show &Line Numbers +TextMergeViewer_17=Do ¬hing +TextMergeViewer_atEnd_message= End of document reached. Continue from beginning? + +TextMergeViewer_atBeginning_title= Go to Previous Difference +TextMergeViewer_accessible_right=Right: {0} +TextMergeViewer_atBeginning_message= Beginning of document reached. Continue from end? +TextMergeViewer_accessible_ancestor=Ancestor: {0} + +CompareNavigator_atEnd_title= End Reached +CompareDialog_commit_button=C&ommit +CompareDialog_error_title=Error Saving Changes +ComparePreferencePage_0=When the end/beginning is reached while navigating an element +ComparePreferencePage_1=Pro&mpt +ComparePreferencePage_2=G&o to the beginning/end of the element +ComparePreferencePage_3=Disp&lay the next/previous element +ComparePreferencePage_4=Do ¬hing +CompareDialog_error_message=The changes were not saved: {0} +CompareNavigator_atEnd_message= You have reached the last difference. + +CompareNavigator_atBeginning_title= Beginning Reached +CompareNavigator_atBeginning_message= You have reached the first difference. +ContentMergeViewer_resource_changed_title=Resources Changed +ContentMergeViewer_resource_changed_description=The resources being compared have changed outside the compare editor. Do you want to save your changes? Any unsaved changes will be discarded. +NavigationEndDialog_0=Remember decision +NavigationEndDialog_1=Navigation Options +CompareUIPlugin_0=Opening Compare Editor +CompareUIPlugin_1=Opening Compare Dialog +CompareContainer_0=Updating Comparison State +CompareEditor_0=Initializing Compare Editor for {0} +CompareEditor_1=Initializing... +CompareEditor_2=Update comparison {0} +DocumentMerger_0=Computing Differences... +DocumentMerger_1=Too many differences found +DocumentMerger_2=Finding Differences... +DocumentMerger_3=Too many differences found +CompareEditorInput_0=&Select +WorkerJob_0=Multiple errors occurred while processing compare editor events + +SelectAncestorDialog_title=Select Common Ancestor +SelectAncestorDialog_message=Which resource would you like to use as the common ancestor in the three-way compare? +SelectAncestorDialog_option=''{0}'' +ShowWhitespaceAction_0=Show &Whitespace Characters + +CompareWithOtherResourceDialog_ancestor=Ancestor +CompareWithOtherResourceDialog_rightPanel=Right +CompareWithOtherResourceDialog_leftPanel=Left +CompareWithOtherResourceDialog_dialogTitle=Compare with Other Resource +CompareWithOtherResourceDialog_dialogMessage=Select resources to compare +CompareWithOtherResourceDialog_error_not_comparable=Selected resources are not comparable. +CompareWithOtherResourceDialog_error_empty=Both left and right panel must contain a valid path. +CompareWithOtherResourceDialog_clear=&Clear +CompareWithOtherResourceDialog_info=Drag files from a view or between dialog's fields. +CompareWithOtherResourceDialog_externalFile_errorTitle=Compare With Other Resource Error +CompareWithOtherResourceDialog_externalFile_errorMessage=Cannot create a link to an external resource. +CompareWithOtherResourceDialog_externalFileMainButton=Browse... +CompareWithOtherResourceDialog_externalFileRadioButton=&External file +CompareWithOtherResourceDialog_externalFolderMainButton=Browse... +CompareWithOtherResourceDialog_externalFolderRadioButton=External folder +CompareWithOtherResourceDialog_workspaceMainButton=&Browse... +CompareWithOtherResourceDialog_workspaceRadioButton=&Workspace + +CompareContentViewerSwitchingPane_defaultViewer=Default Compare +CompareContentViewerSwitchingPane_switchButtonTooltip=Switch Compare Viewer +CompareContentViewerSwitchingPane_discoveredLabel={0} Compare +CompareContentViewerSwitchingPane_optimized=Differences shown might not be optimal +CompareContentViewerSwitchingPane_optimizedTooltip=To avoid long computation time a faster comparison algorithm has been used. As a result, the differences highlighted in the viewer may be larger than necessary. +CompareStructureViewerSwitchingPane_defaultViewer=Default Structure Compare +CompareStructureViewerSwitchingPane_switchButtonTooltip=Switch Structure Compare Viewer +CompareStructureViewerSwitchingPane_discoveredLabel={0} Structure Compare + +ReaderCreator_fileIsNotAccessible=Cannot create a reader because the file is inaccessible. diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java new file mode 100644 index 000000000..0c7b8c27b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareOutlinePage.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.*; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.part.IPageSite; +import org.eclipse.ui.part.Page; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + */ +public class CompareOutlinePage extends Page implements IContentOutlinePage, IPropertyChangeListener { + + private CompareEditor fCompareEditor; + private Control fControl; + private CompareViewerSwitchingPane fStructurePane; + private OutlineViewerCreator fCreator; + + CompareOutlinePage(CompareEditor editor) { + fCompareEditor= editor; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite) + */ + public void createControl(Composite parent) { + final Splitter h= new Splitter(parent, SWT.HORIZONTAL); + fStructurePane= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) { + protected Viewer getViewer(Viewer oldViewer, Object input) { + if (input instanceof ICompareInput) + return findStructureViewer(oldViewer, (ICompareInput)input, this); + return null; + } + }; + h.setVisible(fStructurePane, true); + fControl = h; + IPageSite site = getSite(); + site.setSelectionProvider(fStructurePane); + h.layout(); + reset(); + } + + private Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) { + OutlineViewerCreator creator = getCreator(); + if (creator != null) + return creator.findStructureViewer(oldViewer, input, parent, getCompareConfiguration()); + return null; + } + + private CompareConfiguration getCompareConfiguration() { + return fCompareEditor.getCompareConfiguration(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#getControl() + */ + public Control getControl() { + return fControl; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#setFocus() + */ + public void setFocus() { + if (fStructurePane != null) + fStructurePane.setFocus(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if (fStructurePane != null) + fStructurePane.addSelectionChangedListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() + */ + public ISelection getSelection() { + if (fStructurePane != null) + return fStructurePane.getSelection(); + return StructuredSelection.EMPTY; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + if (fStructurePane != null) + fStructurePane.removeSelectionChangedListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) + */ + public void setSelection(ISelection selection) { + if (fStructurePane != null) + fStructurePane.setSelection(selection); + } + + private void setInput(Object input) { + if (fStructurePane != null) { + fStructurePane.setInput(input); + ((Splitter)fControl).layout(); + } + } + + public OutlineViewerCreator getCreator() { + if (fCreator == null) { + fCreator = (OutlineViewerCreator)Utilities.getAdapter(fCompareEditor, OutlineViewerCreator.class); + if (fCreator != null) + fCreator.addPropertyChangeListener(this); + } + return fCreator; + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(OutlineViewerCreator.PROP_INPUT)) { + fStructurePane.setInput(event.getNewValue()); + ((Splitter)fControl).layout(); + } + } + + public void dispose() { + super.dispose(); + if (fCreator != null) + fCreator.removePropertyChangeListener(this); + fCreator = null; + } + + public void reset() { + if (fCreator != null) + fCreator.removePropertyChangeListener(this); + fCreator = null; + OutlineViewerCreator creator = getCreator(); + if (creator != null) + setInput(creator.getInput()); + else + setInput(null); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java new file mode 100644 index 000000000..23162e562 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferenceInitializer.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; + +public class ComparePreferenceInitializer extends AbstractPreferenceInitializer { + + public ComparePreferenceInitializer() { + // Nothing to do + } + + public void initializeDefaultPreferences() { + IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore(); + ComparePreferencePage.initDefaults(store); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java new file mode 100644 index 000000000..f4fe72b73 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java @@ -0,0 +1,489 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.swt.widgets.Text; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.preference.RadioGroupFieldEditor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.PreferenceLinkArea; +import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.contentmergeviewer.TextMergeViewer; +import org.eclipse.compare.internal.core.ComparePlugin; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.Differencer; + + +public class ComparePreferencePage extends PreferencePage implements IWorkbenchPreferencePage { + + class FakeInput implements ITypedElement, IEncodedStreamContentAccessor { + static final String UTF_16= "UTF-16"; //$NON-NLS-1$ + String fContent; + + FakeInput(String name) { + fContent= loadPreviewContentFromFile(name); + } + public Image getImage() { + return null; + } + public String getName() { + return "no name"; //$NON-NLS-1$ + } + public String getType() { + return "no type"; //$NON-NLS-1$ + } + public InputStream getContents() { + return new ByteArrayInputStream(Utilities.getBytes(fContent, UTF_16)); + } + public String getCharset() { + return UTF_16; + } + } + + private static final String PREFIX= CompareUIPlugin.PLUGIN_ID + "."; //$NON-NLS-1$ + public static final String OPEN_STRUCTURE_COMPARE= PREFIX + "OpenStructureCompare"; //$NON-NLS-1$ + public static final String USE_OUTLINE_VIEW= PREFIX + "UseOutlineView"; //$NON-NLS-1$ + public static final String SYNCHRONIZE_SCROLLING= PREFIX + "SynchronizeScrolling"; //$NON-NLS-1$ + public static final String SHOW_PSEUDO_CONFLICTS= PREFIX + "ShowPseudoConflicts"; //$NON-NLS-1$ + public static final String INITIALLY_SHOW_ANCESTOR_PANE= PREFIX + "InitiallyShowAncestorPane"; //$NON-NLS-1$ + public static final String PREF_SAVE_ALL_EDITORS= PREFIX + "SaveAllEditors"; //$NON-NLS-1$ + public static final String IGNORE_WHITESPACE= PREFIX + "IgnoreWhitespace"; //$NON-NLS-1$ + + //public static final String USE_SPLINES= PREFIX + "UseSplines"; //$NON-NLS-1$ + public static final String USE_SINGLE_LINE= PREFIX + "UseSingleLine"; //$NON-NLS-1$ + public static final String HIGHLIGHT_TOKEN_CHANGES= PREFIX + "HighlightTokenChanges"; //$NON-NLS-1$ + //public static final String USE_RESOLVE_UI= PREFIX + "UseResolveUI"; //$NON-NLS-1$ + public static final String CAPPING_DISABLED= PREFIX + "CappingDisable"; //$NON-NLS-1$ + public static final String PATH_FILTER= PREFIX + "PathFilter"; //$NON-NLS-1$ + public static final String ADDED_LINES_REGEX= PREFIX + "AddedLinesRegex"; //$NON-NLS-1$ + public static final String REMOVED_LINES_REGEX= PREFIX + "RemovedLinesRegex"; //$NON-NLS-1$ + + + private TextMergeViewer fPreviewViewer; + private IPropertyChangeListener fPreferenceChangeListener; + private CompareConfiguration fCompareConfiguration; + private OverlayPreferenceStore fOverlayStore; + private Map fCheckBoxes= new HashMap(); + private Text fFilters; + private Text addedLinesRegex; + private Text removedLinesRegex; + private SelectionListener fCheckBoxListener; + + + public final OverlayPreferenceStore.OverlayKey[] fKeys= new OverlayPreferenceStore.OverlayKey[] { + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, OPEN_STRUCTURE_COMPARE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_OUTLINE_VIEW), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, SYNCHRONIZE_SCROLLING), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, SHOW_PSEUDO_CONFLICTS), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, INITIALLY_SHOW_ANCESTOR_PANE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, IGNORE_WHITESPACE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PREF_SAVE_ALL_EDITORS), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ADDED_LINES_REGEX), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, REMOVED_LINES_REGEX), + //new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_SPLINES), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_SINGLE_LINE), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, HIGHLIGHT_TOKEN_CHANGES), + //new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, USE_RESOLVE_UI), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, CAPPING_DISABLED), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, PATH_FILTER), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION), + new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL), + }; + private RadioGroupFieldEditor editor; + + + public static void initDefaults(IPreferenceStore store) { + store.setDefault(OPEN_STRUCTURE_COMPARE, true); + store.setDefault(USE_OUTLINE_VIEW, false); + store.setDefault(SYNCHRONIZE_SCROLLING, true); + store.setDefault(SHOW_PSEUDO_CONFLICTS, false); + store.setDefault(INITIALLY_SHOW_ANCESTOR_PANE, false); + store.setDefault(IGNORE_WHITESPACE, false); + store.setDefault(PREF_SAVE_ALL_EDITORS, false); + store.setDefault(ADDED_LINES_REGEX, ""); //$NON-NLS-1$ + store.setDefault(REMOVED_LINES_REGEX, ""); //$NON-NLS-1$ + //store.setDefault(USE_SPLINES, false); + store.setDefault(USE_SINGLE_LINE, true); + store.setDefault(HIGHLIGHT_TOKEN_CHANGES, true); + //store.setDefault(USE_RESOLVE_UI, false); + store.setDefault(CAPPING_DISABLED, false); + store.setDefault(PATH_FILTER, ""); //$NON-NLS-1$ + store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, ICompareUIConstants.PREF_VALUE_PROMPT); + store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, ICompareUIConstants.PREF_VALUE_LOOP); + } + + public ComparePreferencePage() { + + //setDescription(Utilities.getString("ComparePreferencePage.description")); //$NON-NLS-1$ + + setPreferenceStore(CompareUIPlugin.getDefault().getPreferenceStore()); + + fOverlayStore= new OverlayPreferenceStore(getPreferenceStore(), fKeys); + fPreferenceChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + String key= event.getProperty(); + if (key.equals(INITIALLY_SHOW_ANCESTOR_PANE)) { + boolean b= fOverlayStore.getBoolean(INITIALLY_SHOW_ANCESTOR_PANE); + if (fCompareConfiguration != null) { + fCompareConfiguration.setProperty(INITIALLY_SHOW_ANCESTOR_PANE, new Boolean(b)); + } + } + } + }; + fOverlayStore.addPropertyChangeListener(fPreferenceChangeListener); + } + + /* + * @see IWorkbenchPreferencePage#init() + */ + public void init(IWorkbench workbench) { + // empty + } + + /* + * @see PreferencePage#performOk() + */ + public boolean performOk() { + fOverlayStore.setValue(ADDED_LINES_REGEX, addedLinesRegex.getText()); + fOverlayStore.setValue(REMOVED_LINES_REGEX, removedLinesRegex.getText()); + + editor.store(); + fOverlayStore.propagate(); + + ComparePlugin.getDefault().setCappingDisabled( + getPreferenceStore().getBoolean( + ComparePreferencePage.CAPPING_DISABLED)); + return true; + } + + /* + * @see PreferencePage#performDefaults() + */ + protected void performDefaults() { + + fOverlayStore.loadDefaults(); + initializeFields(); + + super.performDefaults(); + } + + /* + * @see DialogPage#dispose() + */ + public void dispose() { + + if (fOverlayStore != null) { + if (fPreferenceChangeListener != null) { + fOverlayStore.removePropertyChangeListener(fPreferenceChangeListener); + fPreferenceChangeListener= null; + } + fOverlayStore.stop(); + fOverlayStore= null; + } + + super.dispose(); + } + + static public boolean getSaveAllEditors() { + IPreferenceStore store= CompareUIPlugin.getDefault().getPreferenceStore(); + return store.getBoolean(PREF_SAVE_ALL_EDITORS); + } + + static public void setSaveAllEditors(boolean value) { + IPreferenceStore store= CompareUIPlugin.getDefault().getPreferenceStore(); + store.setValue(PREF_SAVE_ALL_EDITORS, value); + } + + /* + * @see PreferencePage#createContents(Composite) + */ + protected Control createContents(Composite parent) { + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICompareContextIds.COMPARE_PREFERENCE_PAGE); + + fOverlayStore.load(); + fOverlayStore.start(); + + TabFolder folder= new TabFolder(parent, SWT.NONE); + folder.setLayout(new TabFolderLayout()); + folder.setLayoutData(new GridData(GridData.FILL_BOTH)); + + TabItem item= new TabItem(folder, SWT.NONE); + item.setText(Utilities.getString("ComparePreferencePage.generalTab.label")); //$NON-NLS-1$ + //item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CFILE)); + item.setControl(createGeneralPage(folder)); + + item= new TabItem(folder, SWT.NONE); + item.setText(Utilities.getString("ComparePreferencePage.textCompareTab.label")); //$NON-NLS-1$ + //item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_CFILE)); + item.setControl(createTextComparePage(folder)); + + initializeFields(); + Dialog.applyDialogFont(folder); + return folder; + } + + private Control createGeneralPage(Composite parent) { + Composite composite= new Composite(parent, SWT.NULL); + GridLayout layout= new GridLayout(); + layout.numColumns= 1; + composite.setLayout(layout); + + addCheckBox(composite, "ComparePreferencePage.structureCompare.label", OPEN_STRUCTURE_COMPARE, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.structureOutline.label", USE_OUTLINE_VIEW, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.ignoreWhitespace.label", IGNORE_WHITESPACE, 0); //$NON-NLS-1$ + + // a spacer + new Label(composite, SWT.NONE); + + addCheckBox(composite, "ComparePreferencePage.saveBeforePatching.label", PREF_SAVE_ALL_EDITORS, 0); //$NON-NLS-1$ + + // a spacer + new Label(composite, SWT.NONE); + + Label l= new Label(composite, SWT.WRAP); + l.setText(Utilities.getString("ComparePreferencePage.regex.description")); //$NON-NLS-1$ + + Composite c2= new Composite(composite, SWT.NONE); + c2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout= new GridLayout(2, false); + layout.marginWidth= 0; + c2.setLayout(layout); + + l= new Label(c2, SWT.NONE); + l.setText(Utilities.getString("ComparePreferencePage.regexAdded.label")); //$NON-NLS-1$ + addedLinesRegex = new Text(c2, SWT.BORDER); + addedLinesRegex.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + addedLinesRegex.setText(fOverlayStore.getString(ADDED_LINES_REGEX)); + + l= new Label(c2, SWT.NONE); + l.setText(Utilities.getString("ComparePreferencePage.regexRemoved.label")); //$NON-NLS-1$ + removedLinesRegex = new Text(c2, SWT.BORDER); + removedLinesRegex.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + removedLinesRegex.setText(fOverlayStore.getString(REMOVED_LINES_REGEX)); + + // a spacer + new Label(composite, SWT.NONE); + + l= new Label(composite, SWT.WRAP); + l.setText(Utilities.getString("ComparePreferencePage.filter.description")); //$NON-NLS-1$ + + Composite c3= new Composite(composite, SWT.NONE); + c3.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout= new GridLayout(2, false); + layout.marginWidth= 0; + c3.setLayout(layout); + + l= new Label(c3, SWT.NONE); + l.setText(Utilities.getString("ComparePreferencePage.filter.label")); //$NON-NLS-1$ + + fFilters= new Text(c3, SWT.BORDER); + fFilters.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + fFilters.setText(fOverlayStore.getString(PATH_FILTER)); + fFilters.addModifyListener( + new ModifyListener() { + public void modifyText(ModifyEvent e) { + String filters= fFilters.getText(); + String message= CompareFilter.validateResourceFilters(filters); + setValid(message == null); + setMessage(null); + setErrorMessage(message); + fOverlayStore.setValue(PATH_FILTER, filters); + } + } + ); + + return composite; + } + + private Control createTextComparePage(Composite parent) { + + Composite composite= new Composite(parent, SWT.NULL); + GridLayout layout= new GridLayout(); + layout.numColumns= 1; + composite.setLayout(layout); + + addCheckBox(composite, "ComparePreferencePage.synchronizeScrolling.label", SYNCHRONIZE_SCROLLING, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.initiallyShowAncestorPane.label", INITIALLY_SHOW_ANCESTOR_PANE, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.showPseudoConflicts.label", SHOW_PSEUDO_CONFLICTS, 0); //$NON-NLS-1$ + + //addCheckBox(composite, "ComparePreferencePage.useSplines.label", USE_SPLINES, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.useSingleLine.label", USE_SINGLE_LINE, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.highlightTokenChanges.label", HIGHLIGHT_TOKEN_CHANGES, 0); //$NON-NLS-1$ + //addCheckBox(composite, "ComparePreferencePage.useResolveUI.label", USE_RESOLVE_UI, 0); //$NON-NLS-1$ + addCheckBox(composite, "ComparePreferencePage.disableCapping.label", CAPPING_DISABLED, 0); //$NON-NLS-1$ + + Composite radioGroup = new Composite(composite, SWT.NULL); + radioGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + editor = new RadioGroupFieldEditor(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, CompareMessages.ComparePreferencePage_0, 1, + new String[][] { + new String[] { CompareMessages.ComparePreferencePage_1, ICompareUIConstants.PREF_VALUE_PROMPT }, + new String[] { CompareMessages.ComparePreferencePage_2, ICompareUIConstants.PREF_VALUE_LOOP }, + new String[] { CompareMessages.ComparePreferencePage_3, ICompareUIConstants.PREF_VALUE_NEXT }, + new String[] { CompareMessages.ComparePreferencePage_4, ICompareUIConstants.PREF_VALUE_DO_NOTHING} + }, + radioGroup, true); + editor.setPreferenceStore(fOverlayStore); + editor.fillIntoGrid(radioGroup, 1); + + // a spacer + Label separator= new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL); + separator.setVisible(false); + + Label previewLabel= new Label(composite, SWT.NULL); + previewLabel.setText(Utilities.getString("ComparePreferencePage.preview.label")); //$NON-NLS-1$ + + Control previewer= createPreviewer(composite); + GridData gd= new GridData(GridData.FILL_BOTH); + gd.widthHint= convertWidthInCharsToPixels(60); + gd.heightHint= convertHeightInCharsToPixels(13); + previewer.setLayoutData(gd); + + PreferenceLinkArea area = new PreferenceLinkArea(composite, SWT.NONE, + "org.eclipse.ui.preferencePages.ColorsAndFonts", Utilities.getString("ComparePreferencePage.colorAndFontLink"), //$NON-NLS-1$ //$NON-NLS-2$ + (IWorkbenchPreferenceContainer)getContainer(), "selectCategory:org.eclipse.compare.contentmergeviewer.TextMergeViewer"); //$NON-NLS-1$ + + GridData data= new GridData(SWT.FILL, SWT.CENTER, false, false); + area.getControl().setLayoutData(data); + + return composite; + } + + private Control createPreviewer(Composite parent) { + + fCompareConfiguration= new CompareConfiguration(fOverlayStore); + fCompareConfiguration.setAncestorLabel(Utilities.getString("ComparePreferencePage.ancestor.label")); //$NON-NLS-1$ + + fCompareConfiguration.setLeftLabel(Utilities.getString("ComparePreferencePage.left.label")); //$NON-NLS-1$ + fCompareConfiguration.setLeftEditable(false); + + fCompareConfiguration.setRightLabel(Utilities.getString("ComparePreferencePage.right.label")); //$NON-NLS-1$ + fCompareConfiguration.setRightEditable(false); + + fPreviewViewer= new TextMergeViewer(parent, SWT.BORDER, fCompareConfiguration); + + fPreviewViewer.setInput( + new DiffNode(Differencer.CONFLICTING, + new FakeInput("ComparePreferencePage.previewAncestor"), //$NON-NLS-1$ + new FakeInput("ComparePreferencePage.previewLeft"), //$NON-NLS-1$ + new FakeInput("ComparePreferencePage.previewRight") //$NON-NLS-1$ + ) + ); + + Control c= fPreviewViewer.getControl(); + c.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + if (fCompareConfiguration != null) + fCompareConfiguration.dispose(); + } + }); + + return c; + } + + private void initializeFields() { + + Iterator e= fCheckBoxes.keySet().iterator(); + while (e.hasNext()) { + Button b= (Button) e.next(); + String key= (String) fCheckBoxes.get(b); + b.setSelection(fOverlayStore.getBoolean(key)); + } + + if (fFilters != null) + fFilters.setText(fOverlayStore.getString(PATH_FILTER)); + if (addedLinesRegex != null) + addedLinesRegex.setText(fOverlayStore.getString(ADDED_LINES_REGEX)); + if (removedLinesRegex != null) + removedLinesRegex.setText(fOverlayStore.getString(REMOVED_LINES_REGEX)); + + editor.load(); + } + + // overlay stuff + + private Button addCheckBox(Composite parent, String labelKey, String key, int indentation) { + + String label= Utilities.getString(labelKey); + + Button checkBox= new Button(parent, SWT.CHECK); + checkBox.setText(label); + + GridData gd= new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalIndent= indentation; + gd.horizontalSpan= 2; + checkBox.setLayoutData(gd); + + if (fCheckBoxListener == null) { + fCheckBoxListener= new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + Button button= (Button) e.widget; + fOverlayStore.setValue((String) fCheckBoxes.get(button), button.getSelection()); + } + }; + } + checkBox.addSelectionListener(fCheckBoxListener); + + fCheckBoxes.put(checkBox, key); + + return checkBox; + } + + private String loadPreviewContentFromFile(String key) { + + String preview= Utilities.getString(key); + String separator= System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuffer buffer= new StringBuffer(); + for (int i= 0; i < preview.length(); i++) { + char c= preview.charAt(i); + if (c == '\n') + buffer.append(separator); + else + buffer.append(c); + } + return buffer.toString(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java new file mode 100644 index 000000000..04bfdf3c5 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareStructureViewerSwitchingPane.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareViewerSwitchingPane; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.MenuAdapter; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.PlatformUI; + +public class CompareStructureViewerSwitchingPane extends + CompareViewerSwitchingPane { + + private CompareEditorInput fCompareEditorInput; + + private ViewerDescriptor fSelectedViewerDescriptor; + + private ToolBar toolBar; + + public CompareStructureViewerSwitchingPane(Composite parent, int style, + boolean visibility, CompareEditorInput cei) { + super(parent, style, visibility); + fCompareEditorInput = cei; + } + + private CompareConfiguration getCompareConfiguration() { + return fCompareEditorInput.getCompareConfiguration(); + } + + protected Viewer getViewer(Viewer oldViewer, Object input) { + if (input instanceof ICompareInput) { + if (fSelectedViewerDescriptor != null) { + ViewerDescriptor[] array = CompareUIPlugin.getDefault().findStructureViewerDescriptor( + oldViewer, (ICompareInput)input, getCompareConfiguration()); + List list = array != null ? Arrays.asList(array) + : Collections.EMPTY_LIST; + if (list.contains(fSelectedViewerDescriptor)) { + // use selected viewer only when appropriate for the new input + fCompareEditorInput + .setStructureViewerDescriptor(fSelectedViewerDescriptor); + Viewer viewer = fCompareEditorInput.findStructureViewer( + oldViewer, (ICompareInput) input, this); + return viewer; + } + // fallback to default otherwise + fSelectedViewerDescriptor = null; + } + + fCompareEditorInput.setStructureViewerDescriptor(null); + Viewer viewer = fCompareEditorInput.findStructureViewer(oldViewer, + (ICompareInput) input, this); + fCompareEditorInput.setStructureViewerDescriptor(fSelectedViewerDescriptor); + return viewer; + } + return null; + } + + protected Control createTopLeft(Composite p) { + final Composite composite = new Composite(p, SWT.NONE) { + public Point computeSize(int wHint, int hHint, boolean changed) { + return super.computeSize(wHint, Math.max(24, hHint), changed); + } + }; + + RowLayout layout = new RowLayout(); + layout.marginTop = 0; + composite.setLayout(layout); + + CLabel cl = new CLabel(composite, SWT.NONE); + cl.setText(null); + + toolBar = new ToolBar(composite, SWT.FLAT); + toolBar.setVisible(false); // hide by default + final ToolItem toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + toolItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage( + /* IWorkbenchGraphicConstants */"IMG_LCL_VIEW_MENU")); //$NON-NLS-1$ + toolItem + .setToolTipText(CompareMessages.CompareStructureViewerSwitchingPane_switchButtonTooltip); + toolItem.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + showMenu(); + } + }); + toolBar.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent e) { + showMenu(); + } + }); + return composite; + } + + protected boolean inputChanged(Object input) { + return getInput() != input + || fCompareEditorInput.getStructureViewerDescriptor() != fSelectedViewerDescriptor; + } + + public void setInput(Object input) { + super.setInput(input); + if (getViewer() == null || !Utilities.okToUse(getViewer().getControl())) + return; + ViewerDescriptor[] vd = null; + if (getInput() instanceof ICompareInput) { + vd = CompareUIPlugin.getDefault().findStructureViewerDescriptor( + getViewer(), (ICompareInput) getInput(), + getCompareConfiguration()); + } + toolBar.setVisible(vd != null && vd.length > 1); + } + + private void showMenu() { + if (!(getInput() instanceof ICompareInput)) + return; + + ViewerDescriptor[] vd = CompareUIPlugin.getDefault() + .findStructureViewerDescriptor(getViewer(), + (ICompareInput) getInput(), getCompareConfiguration()); + + // 1. create + final Menu menu = new Menu(getShell(), SWT.POP_UP); + + // add default + String label = CompareMessages.CompareStructureViewerSwitchingPane_defaultViewer; + MenuItem defaultItem = new MenuItem(menu, SWT.RADIO); + defaultItem.setText(label); + defaultItem.addSelectionListener(createSelectionListener(null)); + defaultItem.setSelection(fSelectedViewerDescriptor == null); + + new MenuItem(menu, SWT.SEPARATOR); + + // add others + for (int i = 0; i < vd.length; i++) { + final ViewerDescriptor vdi = vd[i]; + label = vdi.getLabel(); + if (label == null || label.equals("")) { //$NON-NLS-1$ + String l = CompareUIPlugin.getDefault().findStructureTypeNameOrType((ICompareInput) getInput(), vdi, getCompareConfiguration()); + if (l == null) + // couldn't figure out the label, skip the viewer + continue; + label = NLS.bind(CompareMessages.CompareStructureViewerSwitchingPane_discoveredLabel, new Object[] {l}); + } + MenuItem item = new MenuItem(menu, SWT.RADIO); + item.setText(label); + item.addSelectionListener(createSelectionListener(vdi)); + item.setSelection(vdi == fSelectedViewerDescriptor); + } + + // 2. show + Rectangle bounds = toolBar.getItem(0).getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + + // 3. dispose on close + menu.addMenuListener(new MenuAdapter() { + public void menuHidden(MenuEvent e) { + e.display.asyncExec(new Runnable() { + public void run() { + menu.dispose(); + } + }); + } + }); + } + + private SelectionListener createSelectionListener(final ViewerDescriptor vd) { + return new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + MenuItem mi = (MenuItem) e.widget; + if (mi.getSelection()) { + Viewer oldViewer = getViewer(); + fSelectedViewerDescriptor = vd; + CompareStructureViewerSwitchingPane.this.setInput(oldViewer + .getInput()); + } + } + + public void widgetDefaultSelected(SelectionEvent e) { + // nothing to do + } + }; + } + + public void setText(String label) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) { + cl.setText(label); + c.layout(); + } + return; + } + } + } + + public void setImage(Image image) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + if (cl != null && !cl.isDisposed()) + cl.setImage(image); + return; + } + } + } + + public void addMouseListener(MouseListener listener) { + Composite c = (Composite) getTopLeft(); + Control[] children = c.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof CLabel) { + CLabel cl = (CLabel) children[i]; + cl.addMouseListener(listener); + } + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java new file mode 100644 index 000000000..15815f375 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareUIPlugin.java @@ -0,0 +1,1421 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.StringTokenizer; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.IStreamMerger; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.ComparePlugin; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IStructureCreator; +import org.eclipse.compare.structuremergeviewer.StructureDiffViewer; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorRegistry; +import org.eclipse.ui.IReusableEditor; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.IWorkbenchAdapter; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The Compare UI plug-in defines the entry point to initiate a configurable + * compare operation on arbitrary resources. The result of the compare + * is opened into a compare editor where the details can be browsed and + * edited in dynamically selected structure and content viewers. + * <p> + * The Compare UI provides a registry for content and structure compare viewers, + * which is initialized from extensions contributed to extension points + * declared by this plug-in. + * <p> + * This class is the plug-in runtime class for the + * <code>"org.eclipse.compare"</code> plug-in. + * </p> + */ +public final class CompareUIPlugin extends AbstractUIPlugin { + + static class CompareRegistry { + + private final static String ID_ATTRIBUTE= "id"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE= "extensions"; //$NON-NLS-1$ + private final static String CONTENT_TYPE_ID_ATTRIBUTE= "contentTypeId"; //$NON-NLS-1$ + + private HashMap fIdMap; // maps ids to data + private HashMap fExtensionMap; // multimap: maps extensions to list of data + private HashMap fContentTypeBindings; // multimap: maps content type bindings to list of data + + + void register(IConfigurationElement element, Object data) { + String id= element.getAttribute(ID_ATTRIBUTE); + if (id != null) { + if (fIdMap == null) + fIdMap= new HashMap(); + fIdMap.put(id, data); + } + + String types= element.getAttribute(EXTENSIONS_ATTRIBUTE); + if (types != null) { + if (fExtensionMap == null) + fExtensionMap= new HashMap(); + StringTokenizer tokenizer= new StringTokenizer(types, ","); //$NON-NLS-1$ + while (tokenizer.hasMoreElements()) { + String extension= tokenizer.nextToken().trim(); + List l = (List) fExtensionMap.get(normalizeCase(extension)); + if (l == null) + fExtensionMap.put(normalizeCase(extension), l = new ArrayList()); + l.add(data); + } + } + } + + void createBinding(IConfigurationElement element, String idAttributeName) { + String type= element.getAttribute(CONTENT_TYPE_ID_ATTRIBUTE); + String id= element.getAttribute(idAttributeName); + if (id == null) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.targetIdAttributeMissing", idAttributeName)); //$NON-NLS-1$ + if (type != null && id != null && fIdMap != null) { + Object o= fIdMap.get(id); + if (o != null) { + IContentType ct= fgContentTypeManager.getContentType(type); + if (ct != null) { + if (fContentTypeBindings == null) + fContentTypeBindings= new HashMap(); + List l = (List) fContentTypeBindings.get(ct); + if (l == null) + fContentTypeBindings.put(ct, l = new ArrayList()); + l.add(o); + } else { + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.contentTypeNotFound", type)); //$NON-NLS-1$ + } + } else { + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.targetNotFound", id)); //$NON-NLS-1$ + } + } + } + + Object search(IContentType type) { + List list = searchAll(type); + return list != null ? list.get(0) : null; + } + + List searchAll(IContentType type) { + if (fContentTypeBindings != null) { + for (; type != null; type= type.getBaseType()) { + List data= (List) fContentTypeBindings.get(type); + if (data != null) + return data; + } + } + return null; + } + + Object search(String extension) { + List list = searchAll(extension); + return list != null ? list.get(0) : null; + } + + List searchAll(String extension) { + if (fExtensionMap != null) + return (List) fExtensionMap.get(normalizeCase(extension)); + return null; + } + } + + /** Status code describing an internal error */ + public static final int INTERNAL_ERROR= 1; + + private static boolean NORMALIZE_CASE= true; + + public static final String PLUGIN_ID= "org.eclipse.compare"; //$NON-NLS-1$ + + private static final String BINARY_TYPE= "binary"; //$NON-NLS-1$ + + private static final String STREAM_MERGER_EXTENSION_POINT= "streamMergers"; //$NON-NLS-1$ + private static final String STREAM_MERGER= "streamMerger"; //$NON-NLS-1$ + private static final String STREAM_MERGER_ID_ATTRIBUTE= "streamMergerId"; //$NON-NLS-1$ + private static final String STRUCTURE_CREATOR_EXTENSION_POINT= "structureCreators"; //$NON-NLS-1$ + private static final String STRUCTURE_CREATOR= "structureCreator"; //$NON-NLS-1$ + private static final String STRUCTURE_CREATOR_ID_ATTRIBUTE= "structureCreatorId"; //$NON-NLS-1$ + + private static final String VIEWER_TAG= "viewer"; //$NON-NLS-1$ + private static final String STRUCTURE_MERGE_VIEWER_EXTENSION_POINT= "structureMergeViewers"; //$NON-NLS-1$ + private static final String STRUCTURE_MERGE_VIEWER_ID_ATTRIBUTE= "structureMergeViewerId"; //$NON-NLS-1$ + private static final String CONTENT_MERGE_VIEWER_EXTENSION_POINT= "contentMergeViewers"; //$NON-NLS-1$ + private static final String CONTENT_MERGE_VIEWER_ID_ATTRIBUTE= "contentMergeViewerId"; //$NON-NLS-1$ + private static final String CONTENT_VIEWER_EXTENSION_POINT= "contentViewers"; //$NON-NLS-1$ + private static final String CONTENT_VIEWER_ID_ATTRIBUTE= "contentViewerId"; //$NON-NLS-1$ + + private static final String CONTENT_TYPE_BINDING= "contentTypeBinding"; //$NON-NLS-1$ + + + private static final String COMPARE_EDITOR= PLUGIN_ID + ".CompareEditor"; //$NON-NLS-1$ + + private static final String STRUCTUREVIEWER_ALIASES_PREFERENCE_NAME= "StructureViewerAliases"; //$NON-NLS-1$ + + // content type + private static final IContentTypeManager fgContentTypeManager= Platform.getContentTypeManager(); + + public static final int NO_DIFFERENCE = 10000; + + /** + * The plugin singleton. + */ + private static CompareUIPlugin fgComparePlugin; + + /** Maps type to icons */ + private static Map fgImages= new Hashtable(10); + /** Maps type to ImageDescriptors */ + private static Map fgImageDescriptors= new Hashtable(10); + /** Maps ImageDescriptors to Images */ + private static Map fgImages2= new Hashtable(10); + + private static List fgDisposeOnShutdownImages= new ArrayList(); + + private ResourceBundle fResourceBundle; + + private boolean fRegistriesInitialized; + private CompareRegistry fStreamMergers= new CompareRegistry(); + private CompareRegistry fStructureCreators= new CompareRegistry(); + private CompareRegistry fStructureMergeViewers= new CompareRegistry(); + private CompareRegistry fContentViewers= new CompareRegistry(); + private CompareRegistry fContentMergeViewers= new CompareRegistry(); + + private Map fStructureViewerAliases; + private CompareFilter fFilter; + private IPropertyChangeListener fPropertyChangeListener; + + /** + * Creates the <code>CompareUIPlugin</code> object and registers all + * structure creators, content merge viewers, and structure merge viewers + * contributed to this plug-in's extension points. + * <p> + * Note that instances of plug-in runtime classes are automatically created + * by the platform in the course of plug-in activation. + */ + public CompareUIPlugin() { + super(); + Assert.isTrue(fgComparePlugin == null); + fgComparePlugin= this; + } + + public void start(BundleContext context) throws Exception { + super.start(context); + + ComparePlugin.getDefault().setCappingDisabled( + getPreferenceStore().getBoolean( + ComparePreferencePage.CAPPING_DISABLED)); + } + + public void stop(BundleContext context) throws Exception { + + IPreferenceStore ps= getPreferenceStore(); + rememberAliases(ps); + if (fPropertyChangeListener != null) { + ps.removePropertyChangeListener(fPropertyChangeListener); + fPropertyChangeListener= null; + } + + super.stop(context); + + if (fgDisposeOnShutdownImages != null) { + Iterator i= fgDisposeOnShutdownImages.iterator(); + while (i.hasNext()) { + Image img= (Image) i.next(); + if (!img.isDisposed()) + img.dispose(); + } + fgImages= null; + } + } + + /** + * Returns the singleton instance of this plug-in runtime class. + * + * @return the compare plug-in instance + */ + public static CompareUIPlugin getDefault() { + return fgComparePlugin; + } + + /** + * Returns this plug-in's resource bundle. + * + * @return the plugin's resource bundle + */ + public ResourceBundle getResourceBundle() { + if (fResourceBundle == null) + fResourceBundle= Platform.getResourceBundle(getBundle()); + return fResourceBundle; + } + + /** + * Returns this plug-in's unique identifier. + * + * @return the plugin's unique identifier + */ + public static String getPluginId() { + return getDefault().getBundle().getSymbolicName(); + } + + private void initializeRegistries() { + if (!fRegistriesInitialized) { + registerExtensions(); + fRegistriesInitialized= true; + } + } + + /** + * Registers all stream mergers, structure creators, content merge viewers, and structure merge viewers + * that are found in the XML plugin files. + */ + private void registerExtensions() { + IExtensionRegistry registry= Platform.getExtensionRegistry(); + + // collect all IStreamMergers + IConfigurationElement[] elements= registry.getConfigurationElementsFor(PLUGIN_ID, STREAM_MERGER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (STREAM_MERGER.equals(element.getName())) + fStreamMergers.register(element, new StreamMergerDescriptor(element)); + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fStreamMergers.createBinding(element, STREAM_MERGER_ID_ATTRIBUTE); + } + + // collect all IStructureCreators + elements= registry.getConfigurationElementsFor(PLUGIN_ID, STRUCTURE_CREATOR_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!STRUCTURE_CREATOR.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, STRUCTURE_CREATOR)); //$NON-NLS-1$ + fStructureCreators.register(element, new StructureCreatorDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fStructureCreators.createBinding(element, STRUCTURE_CREATOR_ID_ATTRIBUTE); + } + + // collect all viewers which define the structure merge viewer extension point + elements= registry.getConfigurationElementsFor(PLUGIN_ID, STRUCTURE_MERGE_VIEWER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + fStructureMergeViewers.register(element, new ViewerDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fStructureMergeViewers.createBinding(element, STRUCTURE_MERGE_VIEWER_ID_ATTRIBUTE); + } + + // collect all viewers which define the content merge viewer extension point + elements= registry.getConfigurationElementsFor(PLUGIN_ID, CONTENT_MERGE_VIEWER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + fContentMergeViewers.register(element, new ViewerDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fContentMergeViewers.createBinding(element, CONTENT_MERGE_VIEWER_ID_ATTRIBUTE); + } + + // collect all viewers which define the content viewer extension point + elements= registry.getConfigurationElementsFor(PLUGIN_ID, CONTENT_VIEWER_EXTENSION_POINT); + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + String name= element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + logErrorMessage(Utilities.getFormattedString("CompareUIPlugin.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + fContentViewers.register(element, new ViewerDescriptor(element)); + } + } + for (int i= 0; i < elements.length; i++) { + IConfigurationElement element= elements[i]; + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fContentViewers.createBinding(element, CONTENT_VIEWER_ID_ATTRIBUTE); + } + } + + public static IWorkbench getActiveWorkbench() { + CompareUIPlugin plugin= getDefault(); + if (plugin == null) + return null; + return plugin.getWorkbench(); + } + + public static IWorkbenchWindow getActiveWorkbenchWindow() { + IWorkbench workbench= getActiveWorkbench(); + if (workbench == null) + return null; + return workbench.getActiveWorkbenchWindow(); + } + + /** + * Returns the active workbench page or <code>null</code> if + * no active workbench page can be determined. + * + * @return the active workbench page or <code>null</code> if + * no active workbench page can be determined + */ + private static IWorkbenchPage getActivePage() { + IWorkbenchWindow window= getActiveWorkbenchWindow(); + if (window == null) + return null; + return window.getActivePage(); + } + + /** + * Returns the SWT Shell of the active workbench window or <code>null</code> if + * no workbench window is active. + * + * @return the SWT Shell of the active workbench window, or <code>null</code> if + * no workbench window is active + */ + public static Shell getShell() { + IWorkbenchWindow window= getActiveWorkbenchWindow(); + if (window == null) + return null; + return window.getShell(); + } + + /** + * Registers the given image for being disposed when this plug-in is shutdown. + * + * @param image the image to register for disposal + */ + public static void disposeOnShutdown(Image image) { + if (image != null) + fgDisposeOnShutdownImages.add(image); + } + + /** + * Performs the comparison described by the given input and opens a compare + * editor on the result. + * + * @param input + * the input on which to open the compare editor + * @param page + * the workbench page on which to create a new compare editor + * @param editor + * if not null the input is opened in this editor + * @param activate + * if <code>true</code> the editor will be activated + * @see IWorkbenchPage#openEditor(org.eclipse.ui.IEditorInput, String, + * boolean) + * @see CompareEditorInput + */ + public void openCompareEditor(final CompareEditorInput input, + final IWorkbenchPage page, final IReusableEditor editor, + final boolean activate) { + CompareConfiguration configuration = input.getCompareConfiguration(); + if (configuration != null) { + IPreferenceStore ps= configuration.getPreferenceStore(); + if (ps != null) + configuration.setProperty( + CompareConfiguration.USE_OUTLINE_VIEW, + Boolean.valueOf(ps.getBoolean(ComparePreferencePage.USE_OUTLINE_VIEW))); + } + if (input.canRunAsJob()) { + openEditorInBackground(input, page, editor, activate); + } else { + if (compareResultOK(input, null)) { + internalOpenEditor(input, page, editor, activate); + } + } + } + + private void openEditorInBackground(final CompareEditorInput input, + final IWorkbenchPage page, final IReusableEditor editor, + final boolean activate) { + internalOpenEditor(input, page, editor, activate); + } + + private void internalOpenEditor(final CompareEditorInput input, + final IWorkbenchPage wp, final IReusableEditor editor, + final boolean activate) { + Runnable runnable = new Runnable() { + public void run() { + if (editor != null && !editor.getSite().getShell().isDisposed()) { // reuse the given editor + editor.setInput(input); + return; + } + + IWorkbenchPage page = wp; + if (page == null) + page= getActivePage(); + if (page != null) { + // open new CompareEditor on page + try { + page.openEditor(input, COMPARE_EDITOR, activate); + } catch (PartInitException e) { + MessageDialog.openError(getShell(), Utilities.getString("CompareUIPlugin.openEditorError"), e.getMessage()); //$NON-NLS-1$ + } + } else { + MessageDialog.openError(getShell(), + Utilities.getString("CompareUIPlugin.openEditorError"), //$NON-NLS-1$ + Utilities.getString("CompareUIPlugin.noActiveWorkbenchPage")); //$NON-NLS-1$ + } + } + }; + syncExec(runnable); + } + + /** + * Performs the comparison described by the given input and opens a + * compare dialog on the result. + * + * @param input the input on which to open the compare editor + * @see CompareEditorInput + */ + public void openCompareDialog(final CompareEditorInput input) { + // We don't ever open dialogs in the background + if (compareResultOK(input, null)) { + internalOpenDialog(input); + } + } + + public IStatus prepareInput(CompareEditorInput input, IProgressMonitor monitor) { + try { + input.run(monitor); + String message= input.getMessage(); + if (message != null) { + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, message, null); + } + if (input.getCompareResult() == null) { + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, NO_DIFFERENCE, Utilities.getString("CompareUIPlugin.noDifferences"), null); //$NON-NLS-1$ + } + return Status.OK_STATUS; + } catch (InterruptedException e) { + throw new OperationCanceledException(); + } catch (InvocationTargetException e) { + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, Utilities.getString("CompareUIPlugin.compareFailed"), e.getTargetException()); //$NON-NLS-1$ + } + } + + /* + * @return <code>true</code> if compare result is OK to show, <code>false</code> otherwise + */ + public boolean compareResultOK(CompareEditorInput input, IRunnableContext context) { + final Shell shell= getShell(); + try { + + // run operation in separate thread and make it cancelable + if (context == null) + context = PlatformUI.getWorkbench().getProgressService(); + context.run(true, true, input); + + String message= input.getMessage(); + if (message != null) { + MessageDialog.openError(shell, Utilities.getString("CompareUIPlugin.compareFailed"), message); //$NON-NLS-1$ + return false; + } + + if (input.getCompareResult() == null) { + MessageDialog.openInformation(shell, Utilities.getString("CompareUIPlugin.dialogTitle"), Utilities.getString("CompareUIPlugin.noDifferences")); //$NON-NLS-2$ //$NON-NLS-1$ + return false; + } + + return true; + + } catch (InterruptedException x) { + // canceled by user + } catch (InvocationTargetException x) { + MessageDialog.openError(shell, Utilities.getString("CompareUIPlugin.compareFailed"), x.getTargetException().getMessage()); //$NON-NLS-1$ + } + return false; + } + + /* + * Registers an image for the given type. + */ + private static void registerImage(String type, Image image, boolean dispose) { + fgImages.put(normalizeCase(type), image); + if (image != null && dispose) { + fgDisposeOnShutdownImages.add(image); + } + } + + /** + * Registers an image descriptor for the given type. + * + * @param type the type + * @param descriptor the image descriptor + */ + public static void registerImageDescriptor(String type, ImageDescriptor descriptor) { + fgImageDescriptors.put(normalizeCase(type), descriptor); + } + + public static ImageDescriptor getImageDescriptor(String relativePath) { + if (fgComparePlugin == null) + return null; + IPath path= Utilities.getIconPath(null).append(relativePath); + URL url= FileLocator.find(fgComparePlugin.getBundle(), path, null); + if (url == null) + return null; + return ImageDescriptor.createFromURL(url); + } + + /** + * Returns a shared image for the given type, or a generic image if none + * has been registered for the given type. + * <p> + * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + * </p> + * + * @param type the type + * @return the image + */ + public static Image getImage(String type) { + + type= normalizeCase(type); + + boolean dispose= false; + Image image= null; + if (type != null) + image= (Image) fgImages.get(type); + if (image == null) { + ImageDescriptor id= (ImageDescriptor) fgImageDescriptors.get(type); + if (id != null) { + image= id.createImage(); + dispose= true; + } + + if (image == null) { + if (fgComparePlugin != null) { + if (ITypedElement.FOLDER_TYPE.equals(type)) { + image= getDefault().getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); + //image= SharedImages.getImage(ISharedImages.IMG_OBJ_FOLDER); + } else { + image= createWorkbenchImage(type); + dispose= true; + } + } else { + id= (ImageDescriptor) fgImageDescriptors.get(normalizeCase("file")); //$NON-NLS-1$ + image= id.createImage(); + dispose= true; + } + } + if (image != null) + registerImage(type, image, dispose); + } + return image; + } + + /** + * Returns a shared image for the given adaptable. + * This convenience method queries the given adaptable + * for its <code>IWorkbenchAdapter.getImageDescriptor</code>, which it + * uses to create an image if it does not already have one. + * <p> + * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + * </p> + * + * @param adaptable the adaptable for which to find an image + * @return an image + */ + public static Image getImage(IAdaptable adaptable) { + if (adaptable != null) { + Object o= adaptable.getAdapter(IWorkbenchAdapter.class); + if (o instanceof IWorkbenchAdapter) { + ImageDescriptor id= ((IWorkbenchAdapter) o).getImageDescriptor(adaptable); + if (id != null) { + Image image= (Image)fgImages2.get(id); + if (image == null) { + image= id.createImage(); + try { + fgImages2.put(id, image); + } catch (NullPointerException ex) { + // NeedWork + } + fgDisposeOnShutdownImages.add(image); + + } + return image; + } + } + } + return null; + } + + private static Image createWorkbenchImage(String type) { + IEditorRegistry er= getDefault().getWorkbench().getEditorRegistry(); + ImageDescriptor id= er.getImageDescriptor("foo." + type); //$NON-NLS-1$ + return id.createImage(); + } + + /** + * Returns an structure creator descriptor for the given type. + * + * @param type the type for which to find a descriptor + * @return a descriptor for the given type, or <code>null</code> if no + * descriptor has been registered + */ + public StructureCreatorDescriptor getStructureCreator(String type) { + initializeRegistries(); + return (StructureCreatorDescriptor) fStructureCreators.search(type); + } + + /** + * Returns a stream merger for the given type. + * + * @param type the type for which to find a stream merger + * @return a stream merger for the given type, or <code>null</code> if no + * stream merger has been registered + */ + public IStreamMerger createStreamMerger(String type) { + initializeRegistries(); + StreamMergerDescriptor descriptor= (StreamMergerDescriptor) fStreamMergers.search(type); + if (descriptor != null) + return descriptor.createStreamMerger(); + return null; + } + + /** + * Returns a stream merger for the given content type. + * + * @param type the type for which to find a stream merger + * @return a stream merger for the given type, or <code>null</code> if no + * stream merger has been registered + */ + public IStreamMerger createStreamMerger(IContentType type) { + initializeRegistries(); + StreamMergerDescriptor descriptor= (StreamMergerDescriptor) fStreamMergers.search(type); + if (descriptor != null) + return descriptor.createStreamMerger(); + return null; + } + + public ViewerDescriptor[] findStructureViewerDescriptor(Viewer oldViewer, + ICompareInput input, CompareConfiguration configuration) { + if (input == null) + return null; + // we don't show the structure of additions or deletions + if (input == null || input.getLeft() == null || input.getRight() == null) + return null; + + Set result = new LinkedHashSet(); + + // content type search + IContentType ctype= getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(ctype); + if (list != null) + result.addAll(list); + } + + // old style search + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) { + type= normalizeCase(types[0]); + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(type); + if (list != null) + result.addAll(list); + String alias= getStructureViewerAlias(type); + if (alias != null) { + list = fStructureMergeViewers.searchAll(alias); + if (list != null) + result.addAll(list); + } + } + + return result.size() > 0 ? (ViewerDescriptor[]) result + .toArray(new ViewerDescriptor[0]) : null; + } + + /** + * Returns a structure compare viewer based on an old viewer and an input object. + * If the old viewer is suitable for showing the input, the old viewer + * is returned. Otherwise, the input's type is used to find a viewer descriptor in the registry + * which in turn is used to create a structure compare viewer under the given parent composite. + * If no viewer descriptor can be found <code>null</code> is returned. + * + * @param oldViewer a new viewer is only created if this old viewer cannot show the given input + * @param input the input object for which to find a structure viewer + * @param parent the SWT parent composite under which the new viewer is created + * @param configuration a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent, + CompareConfiguration configuration) { + ViewerDescriptor[] descriptors = findStructureViewerDescriptor(oldViewer, input, configuration); + if (descriptors == null || descriptors.length == 0) { + // we didn't found any viewer so far. + // now we try to find a structure creator for the generic StructureDiffViewer + IContentType ctype= getCommonType(input); + + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) { + type= normalizeCase(types[0]); + } + + StructureCreatorDescriptor scc= null; + initializeRegistries(); + Object desc= fStructureCreators.search(ctype); // search for content type + if (desc instanceof StructureCreatorDescriptor) + scc= (StructureCreatorDescriptor) desc; + if (scc == null && type != null) + scc= getStructureCreator(type); // search for old-style type scheme + if (scc != null) { + IStructureCreator sc= scc.createStructureCreator(); + if (sc != null) { + StructureDiffViewer sdv= new StructureDiffViewer(parent, configuration); + sdv.setStructureCreator(sc); + return sdv; + } + } + return null; + } + return getViewer(descriptors[0], oldViewer, parent, configuration); + } + + public ViewerDescriptor[] findContentViewerDescriptor(Viewer oldViewer, Object in, CompareConfiguration cc) { + Set result = new LinkedHashSet(); + if (in instanceof IStreamContentAccessor) { + String type= ITypedElement.TEXT_TYPE; + + if (in instanceof ITypedElement) { + ITypedElement tin= (ITypedElement) in; + + IContentType ct= getContentType(tin); + if (ct != null) { + initializeRegistries(); + List list = fContentViewers.searchAll(ct); + if (list != null) + result.addAll(list); + } + + String ty= tin.getType(); + if (ty != null) + type= ty; + } + + initializeRegistries(); + List list = fContentViewers.searchAll(type); + if (list != null) + result.addAll(list); + // fallback + result.add(fContentViewers.search(Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT))); + return (ViewerDescriptor[]) result.toArray(new ViewerDescriptor[0]); + } + + if (!(in instanceof ICompareInput)) + return null; + + ICompareInput input= (ICompareInput) in; + + IContentType ctype = getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(ctype); + if (list != null) + result.addAll(list); + } + + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) + type= types[0]; + + if (ITypedElement.FOLDER_TYPE.equals(type)) + return null; + + if (type == null) { + int n= 0; + for (int i= 0; i < types.length; i++) + if (!ITypedElement.UNKNOWN_TYPE.equals(types[i])) { + n++; + if (type == null) + type= types[i]; // remember the first known type + } + if (n > 1) // don't use the type if there were more than one + type= null; + } + + if (type != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(type); + if (list != null) + result.addAll(list); + } + + // fallback + String leftType= guessType(input.getLeft()); + String rightType= guessType(input.getRight()); + + if (leftType != null || rightType != null) { + boolean right_text = rightType != null + && ITypedElement.TEXT_TYPE.equals(rightType); + boolean left_text = leftType != null + && ITypedElement.TEXT_TYPE.equals(leftType); + initializeRegistries(); + if ((rightType != null && !right_text) + || (leftType != null && !left_text)) { + List list = fContentMergeViewers.searchAll(BINARY_TYPE); + if (list != null) + result.addAll(list); + } + List list = fContentMergeViewers.searchAll(ITypedElement.TEXT_TYPE); + if (list != null) + result.addAll(list); + + return (ViewerDescriptor[]) result.toArray(new ViewerDescriptor[0]); + } + return result.size() > 0 ? (ViewerDescriptor[])result.toArray(new ViewerDescriptor[0]) : null; + } + + /** + * Returns a content compare viewer based on an old viewer and an input object. + * If the old viewer is suitable for showing the input the old viewer + * is returned. Otherwise the input's type is used to find a viewer descriptor in the registry + * which in turn is used to create a content compare viewer under the given parent composite. + * If no viewer descriptor can be found <code>null</code> is returned. + * + * @param oldViewer a new viewer is only created if this old viewer cannot show the given input + * @param in the input object for which to find a content viewer + * @param parent the SWT parent composite under which the new viewer is created + * @param cc a configuration which is passed to a newly created viewer + * @return the compare viewer which is suitable for the given input object or <code>null</code> + */ + public Viewer findContentViewer(Viewer oldViewer, Object in, + Composite parent, CompareConfiguration cc) { + ViewerDescriptor[] descriptors = findContentViewerDescriptor(oldViewer, in, cc); + return getViewer(descriptors != null ? descriptors[0] : null, oldViewer, parent, cc); + } + + private static Viewer getViewer(Object descriptor, Viewer oldViewer, Composite parent, CompareConfiguration cc) { + if (descriptor instanceof IViewerDescriptor) + return ((IViewerDescriptor)descriptor).createViewer(oldViewer, parent, cc); + return null; + } + + private static String[] getTypes(ICompareInput input) { + ITypedElement ancestor= input.getAncestor(); + ITypedElement left= input.getLeft(); + ITypedElement right= input.getRight(); + + ArrayList tmp= new ArrayList(); + if (ancestor != null) { + String type= ancestor.getType(); + if (type != null) + tmp.add(normalizeCase(type)); + } + if (left != null) { + String type= left.getType(); + if (type != null) + tmp.add(normalizeCase(type)); + } + if (right != null) { + String type= right.getType(); + if (type != null) + tmp.add(normalizeCase(type)); + } + return (String[]) tmp.toArray(new String[tmp.size()]); + } + + private static IContentType getContentType(ITypedElement element) { + if (element == null) + return null; + String name= element.getName(); + IContentType ct= null; + if (element instanceof IStreamContentAccessor) { + IStreamContentAccessor isa= (IStreamContentAccessor) element; + try { + InputStream is= isa.getContents(); + if (is != null) { + InputStream bis= new BufferedInputStream(is); + try { + ct= fgContentTypeManager.findContentTypeFor(is, name); + } catch (IOException e) { + // silently ignored + } finally { + try { + bis.close(); + } catch (IOException e2) { + // silently ignored + } + } + } + } catch (CoreException e1) { + // silently ignored + } + } + if (ct == null) + ct= fgContentTypeManager.findContentTypeFor(name); + return ct; + } + + /* + * Returns true if the given types are homogeneous. + */ + private static boolean isHomogenous(String[] types) { + switch (types.length) { + case 1: + return true; + case 2: + return types[0].equals(types[1]); + case 3: + return types[0].equals(types[1]) && types[1].equals(types[2]); + } + return false; + } + + /* + * Returns the most specific content type that is common to the given inputs or null. + */ + private static IContentType getCommonType(ICompareInput input) { + + ITypedElement ancestor= input.getAncestor(); + ITypedElement left= input.getLeft(); + ITypedElement right= input.getRight(); + + int n= 0; + IContentType[] types= new IContentType[3]; + IContentType type= null; + + if (ancestor != null) { + type= getContentType(ancestor); + if (type != null) + types[n++]= type; + } + type= getContentType(left); + if (type != null) + types[n++]= type; + else + return null; + type= getContentType(right); + if (type != null) + types[n++]= type; + else + return null; + + IContentType result= null; + IContentType[] s0, s1, s2; + switch (n) { + case 0: + return null; + case 1: + return types[0]; + case 2: + if (types[0].equals(types[1])) + return types[0]; + s0= toFullPath(types[0]); + s1= toFullPath(types[1]); + for (int i= 0; i < Math.min(s0.length, s1.length); i++) { + if (!s0[i].equals(s1[i])) + break; + result= s0[i]; + } + return result; + case 3: + if (types[0].equals(types[1]) && types[1].equals(types[2])) + return types[0]; + s0= toFullPath(types[0]); + s1= toFullPath(types[1]); + s2= toFullPath(types[2]); + for (int i= 0; i < Math.min(Math.min(s0.length, s1.length), s2.length); i++) { + if (!s0[i].equals(s1[i]) || !s1[i].equals(s2[i])) + break; + result= s0[i]; + } + return result; + } + return null; + } + + private static IContentType[] toFullPath(IContentType ct) { + List l= new ArrayList(); + for (; ct != null; ct= ct.getBaseType()) + l.add(0, ct); + return (IContentType[]) l.toArray(new IContentType[l.size()]); + } + + /* + * Guesses the file type of the given input. + * Returns ITypedElement.TEXT_TYPE if none of the first 10 lines is longer than 1000 bytes. + * Returns ITypedElement.UNKNOWN_TYPE otherwise. + * Returns <code>null</code> if the input isn't an <code>IStreamContentAccessor</code>. + */ + private static String guessType(ITypedElement input) { + if (input instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) input; + InputStream is= null; + try { + is= sca.getContents(); + if (is == null) + return null; + int lineLength= 0; + int lines= 0; + while (lines < 10) { + int c= is.read(); + if (c == -1) // EOF + break; + if (c == '\n' || c == '\r') { // reset line length + lineLength= 0; + lines++; + } else + lineLength++; + if (lineLength > 1000) + return ITypedElement.UNKNOWN_TYPE; + } + return ITypedElement.TEXT_TYPE; + } catch (CoreException ex) { + // be silent and return UNKNOWN_TYPE + } catch (IOException ex) { + // be silent and return UNKNOWN_TYPE + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + return ITypedElement.UNKNOWN_TYPE; + } + return null; + } + + private static String normalizeCase(String s) { + if (NORMALIZE_CASE && s != null) + return s.toUpperCase(); + return s; + } + + //---- alias management + + private String getStructureViewerAlias(String type) { + return (String) getStructureViewerAliases().get(type); + } + + public void addStructureViewerAlias(String type, String alias) { + getStructureViewerAliases().put(normalizeCase(alias), normalizeCase(type)); + } + + private Map getStructureViewerAliases() { + if (fStructureViewerAliases == null) { + fStructureViewerAliases= new Hashtable(10); + String aliases= getPreferenceStore().getString(STRUCTUREVIEWER_ALIASES_PREFERENCE_NAME); + if (aliases != null && aliases.length() > 0) { + StringTokenizer st= new StringTokenizer(aliases, " "); //$NON-NLS-1$ + while (st.hasMoreTokens()) { + String pair= st.nextToken(); + int pos= pair.indexOf('.'); + if (pos > 0) { + String key= pair.substring(0, pos); + String alias= pair.substring(pos+1); + fStructureViewerAliases.put(key, alias); + } + } + } + } + return fStructureViewerAliases; + } + + public void removeAllStructureViewerAliases(String type) { + if (fStructureViewerAliases == null) + return; + String t= normalizeCase(type); + Set entrySet= fStructureViewerAliases.entrySet(); + for (Iterator iter= entrySet.iterator(); iter.hasNext(); ) { + Map.Entry entry= (Map.Entry)iter.next(); + if (entry.getValue().equals(t)) + iter.remove(); + } + } + + /* + * Converts the aliases into a single string before they are stored + * in the preference store. + * The format is: + * <key> '.' <alias> ' ' <key> '.' <alias> ... + */ + private void rememberAliases(IPreferenceStore ps) { + if (fStructureViewerAliases == null) + return; + StringBuffer buffer= new StringBuffer(); + Iterator iter= fStructureViewerAliases.keySet().iterator(); + while (iter.hasNext()) { + String key= (String) iter.next(); + String alias= (String) fStructureViewerAliases.get(key); + buffer.append(key); + buffer.append('.'); + buffer.append(alias); + buffer.append(' '); + } + ps.setValue(STRUCTUREVIEWER_ALIASES_PREFERENCE_NAME, buffer.toString()); + } + + //---- filters + + public boolean filter(String name, boolean isFolder, boolean isArchive) { + if (fFilter == null) { + fFilter= new CompareFilter(); + final IPreferenceStore ps= getPreferenceStore(); + fFilter.setFilters(ps.getString(ComparePreferencePage.PATH_FILTER)); + fPropertyChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + if (ComparePreferencePage.PATH_FILTER.equals(event.getProperty())) + fFilter.setFilters(ps.getString(ComparePreferencePage.PATH_FILTER)); + } + }; + ps.addPropertyChangeListener(fPropertyChangeListener); + } + return fFilter.filter(name, isFolder, isArchive); + } + + private void internalOpenDialog(final CompareEditorInput input) { + Runnable runnable = new Runnable() { + public void run() { + CompareDialog dialog= new CompareDialog(getShell(), input); + dialog.open(); + } + }; + syncExec(runnable); + } + + private void syncExec(Runnable runnable) { + if (Display.getCurrent() == null) { + Display.getDefault().syncExec(runnable); + } else { + runnable.run(); + } + } + + //---- more utilities + + protected void handleNoDifference() { + Runnable runnable = new Runnable() { + public void run() { + MessageDialog.openInformation(getShell(), Utilities.getString("CompareUIPlugin.dialogTitle"), Utilities.getString("CompareUIPlugin.noDifferences")); //$NON-NLS-1$//$NON-NLS-2$ + } + }; + syncExec(runnable); + } + + /** + * Returns an array of all editors that have an unsaved content. If the identical content is + * presented in more than one editor, only one of those editor parts is part of the result. + * + * @return an array of all dirty editor parts. + */ + public static IEditorPart[] getDirtyEditors() { + Set inputs= new HashSet(); + List result= new ArrayList(0); + IWorkbench workbench= getDefault().getWorkbench(); + IWorkbenchWindow[] windows= workbench.getWorkbenchWindows(); + for (int i= 0; i < windows.length; i++) { + IWorkbenchPage[] pages= windows[i].getPages(); + for (int x= 0; x < pages.length; x++) { + IEditorPart[] editors= pages[x].getDirtyEditors(); + for (int z= 0; z < editors.length; z++) { + IEditorPart ep= editors[z]; + IEditorInput input= ep.getEditorInput(); + if (!inputs.contains(input)) { + inputs.add(input); + result.add(ep); + } + } + } + } + return (IEditorPart[])result.toArray(new IEditorPart[result.size()]); + } + + public static void logErrorMessage(String message) { + if (message == null) + message= ""; //$NON-NLS-1$ + log(new Status(IStatus.ERROR, getPluginId(), INTERNAL_ERROR, message, null)); + } + + public static void log(Throwable e) { + log(new Status(IStatus.ERROR, getPluginId(), INTERNAL_ERROR, CompareMessages.ComparePlugin_internal_error, e)); + } + + public static void log(IStatus status) { + getDefault().getLog().log(status); + } + + String findContentTypeNameOrType(ICompareInput input, ViewerDescriptor vd, CompareConfiguration cc) { + IContentType ctype= getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(ctype); + if (list != null) + if (list.contains(vd)) + return ctype.getName(); + } + + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) + type= types[0]; + + if (ITypedElement.FOLDER_TYPE.equals(type)) + return null; + + if (type == null) { + int n= 0; + for (int i= 0; i < types.length; i++) + if (!ITypedElement.UNKNOWN_TYPE.equals(types[i])) { + n++; + if (type == null) + type= types[i]; // remember the first known type + } + if (n > 1) // don't use the type if there were more than one + type= null; + } + + if (type != null) { + initializeRegistries(); + List list = fContentMergeViewers.searchAll(type); + if (list != null) + if (list.contains(vd)) + return type; + } + + // fallback + String leftType= guessType(input.getLeft()); + String rightType= guessType(input.getRight()); + + if (leftType != null || rightType != null) { + boolean right_text = rightType != null + && ITypedElement.TEXT_TYPE.equals(rightType); + boolean left_text = leftType != null + && ITypedElement.TEXT_TYPE.equals(leftType); + initializeRegistries(); + if ((rightType != null && !right_text) + || (leftType != null && !left_text)) { + List list = fContentMergeViewers.searchAll(BINARY_TYPE); + if (list != null) + if (list.contains(vd)) + return type; + } + List list = fContentMergeViewers.searchAll(ITypedElement.TEXT_TYPE); + if (list != null) + if (list.contains(vd)) + return type; + } + return null; + } + + String findStructureTypeNameOrType(ICompareInput input, ViewerDescriptor vd, CompareConfiguration cc) { + if (input == null) + return null; + // we don't show the structure of additions or deletions + if (input == null || input.getLeft() == null || input.getRight() == null) + return null; + + // content type search + IContentType ctype= getCommonType(input); + if (ctype != null) { + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(ctype); + if (list != null) + if (list.contains(vd)) + return ctype.getName(); + } + + // old style search + String[] types= getTypes(input); + String type= null; + if (isHomogenous(types)) { + type= normalizeCase(types[0]); + initializeRegistries(); + List list = fStructureMergeViewers.searchAll(type); + if (list != null) + if (list.contains(vd)) + return type; + String alias= getStructureViewerAlias(type); + if (alias != null) { + list = fStructureMergeViewers.searchAll(alias); + if (list != null) + if (list.contains(vd)) + return alias; + } + } + + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java new file mode 100644 index 000000000..771aef90e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +public class CompareWithEditionAction extends EditionAction { + + public CompareWithEditionAction() { + super(false, "org.eclipse.compare.internal.CompareWithEditionAction"); //$NON-NLS-1$ + this.fHelpContextId= ICompareContextIds.COMPARE_WITH_EDITION_DIALOG; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties new file mode 100644 index 000000000..ad6aec79f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithEditionAction.properties @@ -0,0 +1,38 @@ +############################################################################### +# Copyright (c) 2000, 2006 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +# @(#)CompareWithEditionAction.properties +# +# Resources for CompareWithEditionAction.java + +title= Compare with Local History + +treeTitleFormat= Local History of ''{0}'' +dateIcon= obj16/day_obj.gif +timeIcon= obj16/resource_obj.gif + +treeFormat= {0} +workspaceTreeFormat= {0} (Workspace File) +parseErrorFormat= {0} (Parse Error) + +editionLabel= Local History ({0}) +workspaceEditionLabel= Workspace File + +targetLabel= Editor Buffer +workspaceTargetLabel= Workspace File + +todayFormat= Today ({0}) +yesterdayFormat= Yesterday ({0}) +dayFormat= {0} + +closeButton.label=Close + +noLocalHistoryError= No local history available for selected resource. diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java new file mode 100644 index 000000000..e6899c1bf --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceAction.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Aleksandra Wozniak and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - initial implementation + * IBM Corporation - maintenance + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; + +/** + * The "Compare with other resource" action. + * + * @deprecated Temporarily replaced by CompareWithOtherResourceHandler. See bug + * 264498. + */ +public class CompareWithOtherResourceAction extends CompareAction { + + public void run(ISelection selection) { + // Show CompareWithOtherResourceDialog which return resources to compare + // and ancestor if specified. Don't need to display the other dialog + showSelectAncestorDialog = false; + super.run(selection); + } + + protected boolean isEnabled(ISelection selection) { + int selectionSize = 0; + if (selection instanceof IStructuredSelection) { + selectionSize = ((IStructuredSelection) selection).toArray().length; + } + // enable for a single selection + return super.isEnabled(selection) || selectionSize == 1; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java new file mode 100644 index 000000000..b5debc26f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceDialog.java @@ -0,0 +1,838 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Aleksandra Wozniak and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - initial implementation + * IBM Corporation - Bug 73923 (major refactoring and adjustments) + * IBM Corporation - Bug 241649 - [Dialogs] Resizing of the "compare with other" dialog + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.FileOutputStream; +import java.io.IOException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DragSource; +import org.eclipse.swt.dnd.DragSourceEvent; +import org.eclipse.swt.dnd.DragSourceListener; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.DropTargetListener; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.DirectoryDialog; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.events.ExpansionAdapter; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.widgets.ExpandableComposite; +import org.eclipse.ui.part.ResourceTransfer; + +/** + * This is a dialog that can invoke the compare editor on chosen files. + */ +public class CompareWithOtherResourceDialog extends TitleAreaDialog { + + private int MIN_WIDTH = 320; + private int MIN_HEIGHT_WITH_ANCESTOR = 320; + private int MIN_HEIGHT_WITHOUT_ANCESTOR = 238; + + private class FileTextDragListener implements DragSourceListener { + + private ContentTypeElement element; + + public FileTextDragListener(ContentTypeElement element) { + this.element = element; + } + + public void dragFinished(DragSourceEvent event) { + element.setText(""); //$NON-NLS-1$ + } + + public void dragSetData(DragSourceEvent event) { + event.data = element.getText(); + } + + public void dragStart(DragSourceEvent event) { + if (element.getText() == null) + event.doit = false; + } + } + + private class FileTextDropListener implements DropTargetListener { + + private ContentTypeElement element; + private ResourceTransfer resourceTransfer; + private TextTransfer textTransfer; + + public FileTextDropListener(ContentTypeElement element) { + this.element = element; + resourceTransfer = ResourceTransfer.getInstance(); + textTransfer = TextTransfer.getInstance(); + } + + public void dragEnter(DropTargetEvent event) { + + if (event.detail == DND.DROP_DEFAULT) { + if ((event.operations & DND.DROP_COPY) != 0) + event.detail = DND.DROP_COPY; + else + event.detail = DND.DROP_NONE; + } + + for (int i = 0; i < event.dataTypes.length; i++) { + if (resourceTransfer.isSupportedType(event.dataTypes[i]) + || textTransfer.isSupportedType(event.dataTypes[i])) { + event.currentDataType = event.dataTypes[i]; + if (event.detail != DND.DROP_COPY) + event.detail = DND.DROP_NONE; + break; + } + } + } + + public void dragLeave(DropTargetEvent event) { + // intentionally empty + } + + public void dragOperationChanged(DropTargetEvent event) { + + if (event.detail == DND.DROP_DEFAULT) { + if ((event.operations & DND.DROP_COPY) != 0) + event.detail = DND.DROP_COPY; + else + event.detail = DND.DROP_NONE; + } else if (resourceTransfer.isSupportedType(event.currentDataType)) { + if (event.detail != DND.DROP_COPY) + event.detail = DND.DROP_NONE; + } + } + + public void dragOver(DropTargetEvent event) { + // intentionally empty + } + + public void drop(DropTargetEvent event) { + + if (textTransfer.isSupportedType(event.currentDataType)) { + String txt = (String) event.data; + IResource r = ResourcesPlugin.getWorkspace().getRoot().findMember(txt); + if (r != null) + element.setResource(r); + } else if (resourceTransfer.isSupportedType(event.currentDataType)) { + IResource[] files = (IResource[]) event.data; + if (files.length > 0) + element.setResource(files[0]); + } + + } + + public void dropAccept(DropTargetEvent event) { + // intentionally empty + } + + } + + private abstract class ContentTypeElement { + + private Button radioButton; + protected Button mainButton; + protected Text text; + private String type; + protected InternalSection section; + private IResource resource; + + public ContentTypeElement(Composite parent, String type, InternalSection section) { + this.type = type; + this.section = section; + createContents(parent); + } + + private void createContents(Composite parent) { + createRadioButton(parent); + createText(parent); + createMainButton(parent); + } + + private void createRadioButton(Composite parent) { + radioButton = new Button(parent, SWT.RADIO); + radioButton.setText(type); + } + + protected void createText(Composite parent) { + text = new Text(parent, SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.setEditable(false); + } + + protected void createMainButton(Composite parent) { + mainButton = new Button(parent, SWT.PUSH); + mainButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + } + + protected Button getRadioButton() { + return radioButton; + } + + protected String getText() { + return text.getText(); + } + + protected void setText(String string) { + text.setText(string); + } + + protected void setEnabled(boolean enabled) { + radioButton.setSelection(enabled); + mainButton.setEnabled(enabled); + text.setEnabled(enabled); + } + + protected void setResource(IResource resource) { + this.resource = resource; + section.setResource(resource); + } + + public IResource getResource() { + return resource; + } + + void clearResource() { + resource = null; + text.setText(""); //$NON-NLS-1$ + } + + } + + private class WorkspaceContent extends ContentTypeElement { + + public WorkspaceContent(Composite parent, InternalSection section) { + super(parent, CompareMessages.CompareWithOtherResourceDialog_workspaceRadioButton, section); + } + + protected void createMainButton(Composite parent) { + super.createMainButton(parent); + mainButton.setText(CompareMessages.CompareWithOtherResourceDialog_workspaceMainButton); + // temporarily hide this button. For more information about supporting for browsing workspace see bug 243744. + mainButton.setVisible(false); + } + + protected void createText(Composite parent) { + + super.createText(parent); + text.setEditable(true); + + text.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + section.setResource(text.getText()); + updateErrorInfo(); + } + }); + + text.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + section.setResource(text.getText()); + updateErrorInfo(); + } + }); + + initDrag(); + initDrop(); + } + + protected void setResource(IResource resource) { + super.setResource(resource); + text.setText(resource.getFullPath().toOSString()); + } + + protected void initDrag() { + DragSource source = new DragSource(text, DND.DROP_MOVE + | DND.DROP_COPY | DND.DROP_DEFAULT); + Transfer[] types = new Transfer[] { TextTransfer.getInstance(), + ResourceTransfer.getInstance() }; + source.setTransfer(types); + source.addDragListener(new FileTextDragListener(this)); + } + + protected void initDrop() { + DropTarget target = new DropTarget(text, DND.DROP_MOVE + | DND.DROP_COPY | DND.DROP_DEFAULT); + Transfer[] types = new Transfer[] { TextTransfer.getInstance(), + ResourceTransfer.getInstance() }; + target.setTransfer(types); + target.addDropListener(new FileTextDropListener(this)); + } + + } + + private class ExternalFileContent extends ContentTypeElement { + + public ExternalFileContent(Composite parent, InternalSection section) { + super(parent, CompareMessages.CompareWithOtherResourceDialog_externalFileRadioButton, section); + } + + protected void createMainButton(Composite parent) { + super.createMainButton(parent); + mainButton.setText(CompareMessages.CompareWithOtherResourceDialog_externalFileMainButton); + mainButton.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + IResource r = tmpProject.getExternalFile(); + if (r == null) + return; + setResource(r); + } + }); + } + + protected void setResource(IResource resource) { + super.setResource(resource); + text.setText(resource.getLocation().toOSString()); + } + + } + + private class ExternalFolderContent extends ContentTypeElement { + + public ExternalFolderContent(Composite parent, InternalSection section) { + super(parent, CompareMessages.CompareWithOtherResourceDialog_externalFolderRadioButton, section); + } + + protected void createMainButton(Composite parent) { + super.createMainButton(parent); + mainButton.setText(CompareMessages.CompareWithOtherResourceDialog_externalFolderMainButton); + mainButton.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + IResource r = tmpProject.getExternalFolder(); + if (r == null) + return; + setResource(r); + } + }); + } + + protected void setResource(IResource resource) { + super.setResource(resource); + text.setText(resource.getLocation().toOSString()); + } + + } + + private abstract class InternalSection { + + // there is no "enum" support in Java 1.4. Sigh... + public static final int WORKSPACE = 0; + public static final int EXTERNAL_FILE = 1; + public static final int EXTERNAL_FOLDER = 2; + + protected Group group; + private IResource resource; + + ExternalFileContent externalFileContent; + ExternalFolderContent externalFolderContent; + WorkspaceContent workspaceContent; + + private InternalSection() { + // not to instantiate + } + + protected void createContents(Composite parent) { + + group = new Group(parent, SWT.NONE); + group.setLayout(new GridLayout(3, false)); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + workspaceContent = new WorkspaceContent(group, this); + externalFileContent = new ExternalFileContent(group, this); + externalFolderContent = new ExternalFolderContent(group, this); + + addListenersToRadioButtons(); + } + + private void addListenersToRadioButtons() { + final ContentTypeElement[] elements = new ContentTypeElement[] { workspaceContent, + externalFileContent, externalFolderContent }; + for (int i = 0; i < elements.length; i++) { + elements[i].getRadioButton().addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + for (int j = 0; j < elements.length; j++) { + if (event.widget != elements[j].getRadioButton()) + elements[j].setEnabled(false); + else { + elements[j].setEnabled(true); + setResource(elements[j].getResource()); + } + } + } + }); + } + } + + protected IResource getResource() { + return resource; + } + + protected void setResource(IResource resource) { + this.resource = resource; + updateErrorInfo(); + } + + protected void setResource(String s) { + IResource tmp = ResourcesPlugin.getWorkspace().getRoot() + .findMember(s); + if (tmp instanceof IWorkspaceRoot) + resource = null; + else + resource = tmp; + updateErrorInfo(); + } + + protected void clearResource() { + resource = null; + workspaceContent.clearResource(); + externalFileContent.clearResource(); + externalFolderContent.clearResource(); + updateErrorInfo(); + } + + protected void setContentType(int type) { + switch(type) { + case WORKSPACE: + workspaceContent.setEnabled(true); + externalFileContent.setEnabled(false); + externalFolderContent.setEnabled(false); + break; + case EXTERNAL_FILE: + workspaceContent.setEnabled(false); + externalFileContent.setEnabled(true); + externalFolderContent.setEnabled(false); + break; + case EXTERNAL_FOLDER: + workspaceContent.setEnabled(false); + externalFileContent.setEnabled(false); + externalFolderContent.setEnabled(true); + } + } + } + + private class InternalGroup extends InternalSection { + + public InternalGroup(Composite parent) { + createContents(parent); + } + + public void setText(String text) { + group.setText(text); + } + + public void setLayoutData(GridData layoutData) { + group.setLayoutData(layoutData); + } + } + + private class InternalExpandable extends InternalSection { + + private ExpandableComposite expandable; + private Button clearButton; + + public InternalExpandable(Composite parent) { + createContents(parent); + } + + protected void createContents(Composite parent) { + final Composite p = parent; + expandable = new ExpandableComposite(parent, SWT.NONE, + ExpandableComposite.TREE_NODE | ExpandableComposite.TWISTIE); + super.createContents(expandable); + createClearButton(group); + expandable.setClient(group); + expandable.addExpansionListener(new ExpansionAdapter() { + public void expansionStateChanged(ExpansionEvent e) { + p.layout(); + adjustSize(e.getState()); + } + }); + } + + private void createClearButton(Composite parent) { + clearButton = new Button(parent, SWT.PUSH); + clearButton.setText(CompareMessages.CompareWithOtherResourceDialog_clear); + clearButton.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + public void widgetSelected(SelectionEvent e) { + clearResource(); + } + }); + } + + public void setText(String text) { + expandable.setText(text); + group.setText(text); + } + + public void setLayoutData(GridData layoutData) { + expandable.setLayoutData(layoutData); + } + } + + private class ExternalResourcesProject { + + // Implementation based on org.eclipse.jdt.internal.core.ExternalFoldersManager + + private int counter = 0; + + private static final String TMP_PROJECT_NAME = ".org.eclipse.compare.tmp"; //$NON-NLS-1$ + + private final static String TMP_PROJECT_FILE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" //$NON-NLS-1$ + + "<projectDescription>\n" //$NON-NLS-1$ + + "\t<name>" + TMP_PROJECT_NAME + "\t</name>\n" //$NON-NLS-1$ //$NON-NLS-2$ + + "\t<comment></comment>\n" //$NON-NLS-1$ + + "\t<projects>\n" //$NON-NLS-1$ + + "\t</projects>\n" //$NON-NLS-1$ + + "\t<buildSpec>\n" //$NON-NLS-1$ + + "\t</buildSpec>\n" //$NON-NLS-1$ + + "\t<natures>\n" + "\t</natures>\n" //$NON-NLS-1$//$NON-NLS-2$ + + "</projectDescription>"; //$NON-NLS-1$ + + private final static String TMP_FOLDER_NAME = "tmpFolder"; //$NON-NLS-1$ + + private ExternalResourcesProject() { + // nothing to do here + } + + private IProject createTmpProject() throws CoreException { + IProject project = getTmpProject(); + if (!project.isAccessible()) { + try { + IPath stateLocation = CompareUI.getPlugin().getStateLocation(); + if (!project.exists()) { + IProjectDescription desc = project.getWorkspace() + .newProjectDescription(project.getName()); + desc.setLocation(stateLocation.append(TMP_PROJECT_NAME)); + project.create(desc, null); + } + try { + project.open(null); + } catch (CoreException e) { // in case .project file or folder has been deleted + IPath projectPath = stateLocation.append(TMP_PROJECT_NAME); + projectPath.toFile().mkdirs(); + FileOutputStream output = new FileOutputStream( + projectPath.append(".project").toOSString()); //$NON-NLS-1$ + try { + output.write(TMP_PROJECT_FILE.getBytes()); + } finally { + output.close(); + } + project.open(null); + } + getTmpFolder(project); + } catch (IOException ioe) { + return project; + } catch (CoreException ce) { + throw new CoreException(ce.getStatus()); + } + } + project.setHidden(true); + return project; + } + + private IFolder getTmpFolder(IProject project) throws CoreException { + IFolder folder = project.getFolder(TMP_FOLDER_NAME); + if (!folder.exists()) + folder.create(IResource.NONE, true, null); + return folder; + } + + private IFile getExternalFile() { + FileDialog dialog = new FileDialog(getShell()); + String path = dialog.open(); + if (path != null) + return (IFile) linkResource(new Path(path)); + return null; + } + + private IFolder getExternalFolder() { + DirectoryDialog dialog = new DirectoryDialog(getShell()); + String path = dialog.open(); + if (path != null) + return (IFolder) linkResource(new Path(path)); + return null; + } + + private IResource linkResource(IPath path) { + IResource r = null; + String resourceName = path.lastSegment(); + try { + IProject project = createTmpProject(); + if (!project.isOpen()) + project.open(null); + if (path.toFile().isFile()) { + r = getTmpFolder(project).getFile(resourceName); + if (r.exists()) { // add a number to file's name when there already is a file with that name in a folder + String extension = path.getFileExtension(); + String fileName = path.removeFileExtension().lastSegment(); + r = getTmpFolder(project).getFile(getName(fileName, extension)); + } + ((IFile)r).createLink(path, IResource.REPLACE, null); + } else { // isDirectory + r = getTmpFolder(project).getFolder(resourceName); + if (r.exists()) { + r = getTmpFolder(project).getFolder(getName(resourceName, null)); + } + ((IFolder)r).createLink(path, IResource.REPLACE, null); + } + } catch (CoreException e) { + CompareUIPlugin.log(e); + MessageDialog.openError(getShell(), + CompareMessages.CompareWithOtherResourceDialog_externalFile_errorTitle, + CompareMessages.CompareWithOtherResourceDialog_externalFile_errorMessage); + } + return r; + } + + /** + * This method is used to prevent duplicating names of linked resources. + * It adds a suffix based on the <code>counter</code> value. + * + * @param name + * @param extension optional + * @return + */ + private String getName(String name, String extension) { + if (counter != 0) { + name = name + "-" + counter; //$NON-NLS-1$ + } + // at most 3 resources at the same time with the same name: + // left, right, ancestor + counter = (counter + 1) % 3; + if (extension != null) { + name += "." + extension; //$NON-NLS-1$ + } + // don't change the name if counter equals 0 + return name; + } + + private IProject getTmpProject() { + return ResourcesPlugin.getWorkspace().getRoot().getProject( + TMP_PROJECT_NAME); + } + } + + private Button okButton; + private InternalGroup rightPanel, leftPanel; + private InternalExpandable ancestorPanel; + private ISelection selection; + private ExternalResourcesProject tmpProject = new ExternalResourcesProject(); + + /** + * Creates the dialog. + * + * @param shell + * a shell + * @param selection + * if the selection is not null, it will be set as initial files + * for comparison + * @since 3.4 + */ + protected CompareWithOtherResourceDialog(Shell shell, ISelection selection) { + super(shell); + setShellStyle(SWT.MODELESS | SWT.RESIZE | SWT.MAX); + this.selection = selection; + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets + * .Composite) + */ + protected Control createDialogArea(Composite parent) { + + Composite mainPanel = new Composite(parent, SWT.NULL); + mainPanel.setLayout(new GridLayout(1, true)); + mainPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + ancestorPanel = new InternalExpandable(mainPanel); + ancestorPanel.setText(CompareMessages.CompareWithOtherResourceDialog_ancestor); + GridData ancestorGD = new GridData(SWT.FILL, SWT.FILL, true, false); + ancestorPanel.setLayoutData(ancestorGD); + + leftPanel = new InternalGroup(mainPanel); + leftPanel.setText(CompareMessages.CompareWithOtherResourceDialog_leftPanel); + leftPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + rightPanel = new InternalGroup(mainPanel); + rightPanel.setText(CompareMessages.CompareWithOtherResourceDialog_rightPanel); + rightPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + setSelection(selection); + getShell().setText(CompareMessages.CompareWithOtherResourceDialog_dialogTitle); + setTitle(CompareMessages.CompareWithOtherResourceDialog_dialogMessage); + adjustSize(ancestorPanel.expandable.isExpanded()); + + return mainPanel; + } + + private void adjustSize(boolean expanded) { + int minWidth = convertHorizontalDLUsToPixels(MIN_WIDTH); + int minHeight = convertVerticalDLUsToPixels(expanded ? MIN_HEIGHT_WITH_ANCESTOR + : MIN_HEIGHT_WITHOUT_ANCESTOR); + getShell().setMinimumSize(minWidth, minHeight); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse + * .swt.widgets.Composite) + */ + protected void createButtonsForButtonBar(Composite parent) { + super.createButtonsForButtonBar(parent); + okButton = getButton(IDialogConstants.OK_ID); + updateErrorInfo(); + setMessage(CompareMessages.CompareWithOtherResourceDialog_info); + } + + private void setSelection(ISelection selection) { + IResource[] selectedResources = Utilities.getResources(selection); + switch (selectedResources.length) { + case 1: + leftPanel.workspaceContent.setResource(selectedResources[0]); + break; + case 2: + leftPanel.workspaceContent.setResource(selectedResources[0]); + rightPanel.workspaceContent.setResource(selectedResources[1]); + break; + case 3: + ancestorPanel.workspaceContent.setResource(selectedResources[0]); + ancestorPanel.expandable.setExpanded(true); + leftPanel.workspaceContent.setResource(selectedResources[1]); + rightPanel.workspaceContent.setResource(selectedResources[2]); + break; + } + setInitialContentTypes(); + } + + private void setInitialContentTypes() { + ancestorPanel.setContentType(InternalSection.WORKSPACE); + leftPanel.setContentType(InternalSection.WORKSPACE); + rightPanel.setContentType(InternalSection.WORKSPACE); + } + + private boolean isComparePossible() { + IResource[] resources; + if (ancestorPanel.getResource() == null) + resources = new IResource[] { leftPanel.getResource(), + rightPanel.getResource() }; + else + resources = new IResource[] { ancestorPanel.getResource(), + leftPanel.getResource(), rightPanel.getResource() }; + + ResourceCompareInput r = new ResourceCompareInput( + new CompareConfiguration()); + return r.isEnabled(new StructuredSelection(resources)); + } + + private void updateErrorInfo() { + if (okButton != null) { + if (leftPanel.getResource() == null + || rightPanel.getResource() == null) { + setMessage(CompareMessages.CompareWithOtherResourceDialog_error_empty, + IMessageProvider.ERROR); + okButton.setEnabled(false); + } else if (!isComparePossible()) { + setMessage( + CompareMessages.CompareWithOtherResourceDialog_error_not_comparable, + IMessageProvider.ERROR); + okButton.setEnabled(false); + } else { + setMessage(CompareMessages.CompareWithOtherResourceDialog_info); + okButton.setEnabled(true); + } + } + } + + /** + * Returns table with selected resources. If any resource wasn't chosen in + * the ancestor panel, table has only two elements -- resources chosen in + * left and right panel. In the other case table contains all three + * resources. + * + * @return table with selected resources + */ + public IResource[] getResult() { + IResource[] resources; + IResource rightResource = rightPanel.getResource(); + IResource leftResource = leftPanel.getResource(); + IResource ancestorResource = ancestorPanel.getResource(); + if (ancestorResource == null) + resources = new IResource[] { leftResource, rightResource }; + else + resources = new IResource[] { ancestorResource, leftResource, + rightResource }; + return resources; + } + + /* + * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings() + */ + protected IDialogSettings getDialogBoundsSettings() { + String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ + IDialogSettings settings = CompareUIPlugin.getDefault() + .getDialogSettings(); + IDialogSettings section = settings.getSection(sectionName); + if (section == null) + section = settings.addNewSection(sectionName); + return section; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java new file mode 100644 index 000000000..43a55ee83 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/CompareWithOtherResourceHandler.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * This is a temporary replacement for CompareWithOtherResourceAction which was + * available from "Compare With > Other Resource...". See bug 264498. + */ +public class CompareWithOtherResourceHandler extends AbstractHandler { + + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + IWorkbenchPage workbenchPage = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + + // CompareAction#isEnabled(ISelection) + CompareConfiguration cc = new CompareConfiguration(); + cc.setProperty(CompareEditor.CONFIRM_SAVE_PROPERTY, new Boolean(false)); + ResourceCompareInput input = new ResourceCompareInput(cc); + + int selectionSize = 0; + if (selection instanceof IStructuredSelection) { + selectionSize = ((IStructuredSelection) selection).toArray().length; + } + if (input.isEnabled(selection) || selectionSize == 1) { + + // CompareAction#run(ISelection) + if (!input.setSelection(selection, workbenchPage.getWorkbenchWindow().getShell(), false)) + return null; + input.initializeCompareConfiguration(); + CompareUI.openCompareEditorOnPage(input, workbenchPage); + } + return null; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java new file mode 100644 index 000000000..9aee9c259 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ContentChangeNotifier.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.swt.widgets.Display; + +/** + * A helper class for managing content change notification. + */ +public class ContentChangeNotifier implements IContentChangeNotifier { + + private ListenerList fListenerList; + private final IContentChangeNotifier element; + + public ContentChangeNotifier(IContentChangeNotifier element) { + this.element = element; + } + + /* (non-Javadoc) + * see IContentChangeNotifier.addChangeListener + */ + public void addContentChangeListener(IContentChangeListener listener) { + if (fListenerList == null) + fListenerList= new ListenerList(); + fListenerList.add(listener); + } + + /* (non-Javadoc) + * see IContentChangeNotifier.removeChangeListener + */ + public void removeContentChangeListener(IContentChangeListener listener) { + if (fListenerList != null) { + fListenerList.remove(listener); + if (fListenerList.isEmpty()) + fListenerList= null; + } + } + + /** + * Notifies all registered <code>IContentChangeListener</code>s of a content change. + */ + public void fireContentChanged() { + if (isEmpty()) { + return; + } + // Legacy listeners may expect to be notified in the UI thread. + Runnable runnable = new Runnable() { + public void run() { + Object[] listeners= fListenerList.getListeners(); + for (int i= 0; i < listeners.length; i++) { + final IContentChangeListener contentChangeListener = (IContentChangeListener)listeners[i]; + SafeRunner.run(new ISafeRunnable() { + public void run() throws Exception { + contentChangeListener.contentChanged(element); + } + public void handleException(Throwable exception) { + // Logged by safe runner + } + }); + } + } + }; + if (Display.getCurrent() == null) { + Display.getDefault().syncExec(runnable); + } else { + runnable.run(); + } + } + + /** + * Return whether this notifier is empty (i.e. has no listeners). + * @return whether this notifier is empty + */ + public boolean isEmpty() { + return fListenerList == null || fListenerList.isEmpty(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java new file mode 100644 index 000000000..a0ac41e04 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DiffImageDescriptor.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.graphics.*; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; + +/** + * Combines an image with an overlay. + */ +public class DiffImageDescriptor extends CompositeImageDescriptor { + + static final int HEIGHT= 16; + + private final ImageData fBaseImageData; + private final ImageDescriptor fOverlayImage; + private final int fWidth; + private final boolean fLeft; + private final int hashCode; + + public DiffImageDescriptor(Image base, ImageDescriptor overlay, int w, boolean onLeft) { + ImageData data = null; + if (base != null) { + data = base.getImageData(); + if (data == null) + data = DEFAULT_IMAGE_DATA; + } + fBaseImageData = data; + fOverlayImage= overlay; + fWidth= w; + fLeft= onLeft; + hashCode = calculateHashCode(); + } + + private int calculateHashCode() { + int h1 = 0; + int h2 = 0; + if (fBaseImageData != null) { + h1 = calculateHash(fBaseImageData); + } + if (fOverlayImage != null) { + h2 = fOverlayImage.hashCode(); + } + return h1 + h2 + fWidth; + } + + private int calculateHash(ImageData baseImageData) { + byte[] data = baseImageData.data; + int hash = baseImageData.width + baseImageData.height; + for (int i = 0; i < data.length; i++) { + byte b = data[i]; + hash >>>= 1; + hash ^= b; + } + return hash; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.resource.CompositeImageDescriptor#getSize() + */ + protected Point getSize() { + return new Point(fWidth, HEIGHT); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.resource.CompositeImageDescriptor#drawCompositeImage(int, int) + */ + protected void drawCompositeImage(int width, int height) { + if (fLeft) { + if (fBaseImageData != null) { + drawImage(fBaseImageData, fWidth - fBaseImageData.width, 0); + } + + if (fOverlayImage != null) { + ImageData overlay= fOverlayImage.getImageData(); + if (overlay == null) + overlay= DEFAULT_IMAGE_DATA; + drawImage(overlay, 0, (HEIGHT - overlay.height) / 2); + } + } else { + if (fBaseImageData != null) { + drawImage(fBaseImageData, 0, 0); + } + + if (fOverlayImage != null) { + ImageData overlay= fOverlayImage.getImageData(); + if (overlay == null) + overlay= DEFAULT_IMAGE_DATA; + drawImage(overlay, fWidth - overlay.width, (HEIGHT - overlay.height) / 2); + } + } + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return hashCode; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof DiffImageDescriptor) { + DiffImageDescriptor other = (DiffImageDescriptor) obj; + return (other.hashCode == hashCode + && isEqual(other.fOverlayImage, fOverlayImage) + && other.fWidth == fWidth + && other.fLeft == fLeft + && isEqual(other.fBaseImageData, fBaseImageData)); + } + return false; + } + + private boolean isEqual(ImageData i1, ImageData i2) { + if (isEqual((Object) i1, (Object) i2)) { + return true; + } + if (i1 == null || i2 == null) + return false; + return (i1.width == i2.width && i1.height == i2.height + && i1.depth == i2.depth && i1.scanlinePad == i2.scanlinePad + && i1.bytesPerLine == i2.bytesPerLine + /* && i1.palette == i2.palette */ + && i1.transparentPixel == i2.transparentPixel + && i1.maskPad == i2.maskPad + && i1.alpha == i2.alpha + && i1.type == i2.type && i1.x == i2.x && i1.y == i2.y + && i1.disposalMethod == i2.disposalMethod && i1.delayTime == i2.delayTime + && equals(i1.data,i2.data) && equals(i1.maskData, i2.maskData) + && equals(i1.alphaData, i2.alphaData)); + } + + private boolean equals(byte[] data, byte[] data2) { + if (isEqual(data, data2)) + return true; + if (data == null || data2 == null) + return false; + if (data.length != data2.length) + return false; + for (int i = 0; i < data2.length; i++) { + if (data[i] != data2[i]) + return false; + } + return true; + } + + private boolean isEqual(Object o1, Object o2) { + if (o1 == o2) + return true; + if (o1 == null || o2 == null) + return false; + return o1.equals(o2); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java new file mode 100644 index 000000000..8686bbbb3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocLineComparator.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.text.*; +import org.eclipse.compare.contentmergeviewer.ITokenComparator; +import org.eclipse.compare.rangedifferencer.IRangeComparator; + +/** + * Implements the <code>IRangeComparator</code> interface for lines in a document. + * A <code>DocLineComparator</code> is used as the input for the <code>RangeDifferencer</code> + * engine to perform a line oriented compare on documents. + * <p> + * A <code>DocLineComparator</code> doesn't know anything about line separators because + * its notion of lines is solely defined in the underlying <code>IDocument</code>. + */ +public class DocLineComparator implements ITokenComparator { + + private IDocument fDocument; + private int fLineOffset; + private int fLineCount; + private int fLength; + private boolean fIgnoreWhiteSpace; + + /** + * Creates a <code>DocLineComparator</code> for the given document range. + * ignoreWhiteSpace controls whether comparing lines (in method + * <code>rangesEqual<code>) should ignore whitespace. + * + * @param document the document from which the lines are taken + * @param region if non-<code>null</code> only lines within this range are taken + * @param ignoreWhiteSpace if <code>true</code> white space is ignored when comparing lines + */ + public DocLineComparator(IDocument document, IRegion region, + boolean ignoreWhiteSpace) { + fDocument = document; + fIgnoreWhiteSpace = ignoreWhiteSpace; + + fLineOffset = 0; + if (region != null) { + fLength = region.getLength(); + int start = region.getOffset(); + try { + fLineOffset = fDocument.getLineOfOffset(start); + } catch (BadLocationException ex) { + // silently ignored + } + + if (fLength == 0) { + // optimization, empty documents have one line + fLineCount = 1; + } else { + int endLine = fDocument.getNumberOfLines(); + try { + endLine = fDocument.getLineOfOffset(start + fLength); + } catch (BadLocationException ex) { + // silently ignored + } + fLineCount = endLine - fLineOffset + 1; + } + } else { + fLength = document.getLength(); + fLineCount = fDocument.getNumberOfLines(); + } + } + + /** + * Returns the number of lines in the document. + * + * @return number of lines + */ + public int getRangeCount() { + return fLineCount; + } + + /* (non Javadoc) + * see ITokenComparator.getTokenStart + */ + public int getTokenStart(int line) { + try { + IRegion r= fDocument.getLineInformation(fLineOffset + line); + return r.getOffset(); + } catch (BadLocationException ex) { + return fDocument.getLength(); + } + } + + /* (non Javadoc) + * Returns the length of the given line. + * see ITokenComparator.getTokenLength + */ + public int getTokenLength(int line) { + return getTokenStart(line+1) - getTokenStart(line); + } + + /** + * Returns <code>true</code> if a line given by the first index + * matches a line specified by the other <code>IRangeComparator</code> and index. + * + * @param thisIndex the number of the line within this range comparator + * @param otherComparator the range comparator to compare this with + * @param otherIndex the number of the line within the other comparator + * @return <code>true</code> if the lines are equal + */ + public boolean rangesEqual(int thisIndex, IRangeComparator otherComparator, int otherIndex) { + + if (otherComparator != null && otherComparator.getClass() == getClass()) { + DocLineComparator other= (DocLineComparator) otherComparator; + + if (fIgnoreWhiteSpace) { + String s1= extract(thisIndex); + String s2= other.extract(otherIndex); + //return s1.trim().equals(s2.trim()); + return compare(s1, s2); + } + + int tlen= getTokenLength(thisIndex); + int olen= other.getTokenLength(otherIndex); + if (tlen == olen) { + String s1= extract(thisIndex); + String s2= other.extract(otherIndex); + return s1.equals(s2); + } + } + return false; + } + + /** + * Aborts the comparison if the number of tokens is too large. + * + * @param length a number on which to base the decision whether to return + * <code>true</code> or <code>false</code> + * @param maxLength another number on which to base the decision whether to return + * <code>true</code> or <code>false</code> + * @param other the other <code>IRangeComparator</code> to compare with + * @return <code>true</code> to avoid a too lengthy range comparison + */ + public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) { + return false; + } + + //---- private methods + + /** + * Extract a single line from the underlying document without the line separator. + * + * @param line the number of the line to extract + * @return the contents of the line as a String + */ + private String extract(int line) { + if (line < fLineCount) { + try { + IRegion r= fDocument.getLineInformation(fLineOffset + line); + return fDocument.get(r.getOffset(), r.getLength()); + } catch(BadLocationException e) { + // silently ignored + } + } + return ""; //$NON-NLS-1$ + } + + private boolean compare(String s1, String s2) { + int l1= s1.length(); + int l2= s2.length(); + int c1= 0, c2= 0; + int i1= 0, i2= 0; + + while (c1 != -1) { + + c1= -1; + while (i1 < l1) { + char c= s1.charAt(i1++); + if (! Character.isWhitespace(c)) { + c1= c; + break; + } + } + + c2= -1; + while (i2 < l2) { + char c= s2.charAt(i2++); + if (! Character.isWhitespace(c)) { + c2= c; + break; + } + } + + if (c1 != c2) + return false; + } + return true; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java new file mode 100644 index 000000000..7f2dd3e9e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/DocumentManager.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ArrayList; + +import org.eclipse.jface.text.IDocument; + +/** + * No API yet. + */ +public class DocumentManager { + + private static final boolean DEBUG= false; + + private static ArrayList fgKeys= new ArrayList(); + private static ArrayList fgValues= new ArrayList(); + + public static IDocument get(Object o) { + + for (int i= 0; i < fgKeys.size(); i++) { + if (fgKeys.get(i) == o) + return (IDocument) fgValues.get(i); + } + return null; + } + + public static void put(Object o, IDocument document) { + if (DEBUG) System.out.println("DocumentManager.put: " + document); //$NON-NLS-1$ + for (int i= 0; i < fgKeys.size(); i++) { + if (fgKeys.get(i) == o) { + fgValues.set(i, document); + return; + } + } + fgKeys.add(o); + fgValues.add(document); + } + + public static void remove(IDocument document) { + if (document != null) { + if (DEBUG) System.out.println("DocumentManager.remove: " + document); //$NON-NLS-1$ + for (int i= 0; i < fgValues.size(); i++) { + if (fgValues.get(i) == document) { + fgKeys.remove(i); + fgValues.remove(i); + return; + } + } + if (DEBUG) System.out.println("DocumentManager.remove: not found"); //$NON-NLS-1$ + } + } + + public static void dump() { + if (DEBUG) System.out.println("DocumentManager: managed docs:" + fgValues.size()); //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java new file mode 100644 index 000000000..f0adb2cb8 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/EditionAction.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ResourceBundle; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; + +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.graphics.Image; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.BadLocationException; + +import org.eclipse.ui.*; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.actions.WorkspaceModifyOperation; + +import org.eclipse.compare.*; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.IStreamContentAccessor; + + +public class EditionAction extends BaseCompareAction { + + /** + * Implements the IStreamContentAccessor and ITypedElement protocols + * for a Document. + */ + class DocumentBufferNode implements ITypedElement, IEncodedStreamContentAccessor { + private static final String UTF_16= "UTF-16"; //$NON-NLS-1$ + private IDocument fDocument; + private IFile fFile; + + DocumentBufferNode(IDocument document, IFile file) { + fDocument= document; + fFile= file; + } + + public String getName() { + return fFile.getName(); + } + + public String getType() { + return fFile.getFileExtension(); + } + + public Image getImage() { + return null; + } + + public InputStream getContents() { + return new ByteArrayInputStream(Utilities.getBytes(fDocument.get(), UTF_16)); + } + + public String getCharset() { + return UTF_16; + } + } + + private String fBundleName; + private boolean fReplaceMode; + protected boolean fPrevious= false; + protected String fHelpContextId; + + EditionAction(boolean replaceMode, String bundleName) { + fReplaceMode= replaceMode; + fBundleName= bundleName; + } + + protected boolean isEnabled(ISelection selection) { + return Utilities.getFiles(selection).length == 1; // we don't support multiple selection for now + } + + protected void run(ISelection selection) { + IFile[] files= Utilities.getFiles(selection); + for (int i= 0; i < files.length; i++) + doFromHistory(files[i]); + } + + private void doFromHistory(final IFile file) { + + ResourceBundle bundle= ResourceBundle.getBundle(fBundleName); + String title= Utilities.getString(bundle, "title"); //$NON-NLS-1$ + + Shell parentShell= CompareUIPlugin.getShell(); + + IFileState states[]= null; + try { + states= file.getHistory(null); + } catch (CoreException ex) { + MessageDialog.openError(parentShell, title, ex.getMessage()); + return; + } + + if (states == null || states.length <= 0) { + String msg= Utilities.getString(bundle, "noLocalHistoryError"); //$NON-NLS-1$ + MessageDialog.openInformation(parentShell, title, msg); + return; + } + + ITypedElement base= new ResourceNode(file); + + IDocument document= getDocument(file); + ITypedElement target= base; + if (document != null) + target= new DocumentBufferNode(document, file); + + ITypedElement[] editions= new ITypedElement[states.length+1]; + editions[0]= base; + for (int i= 0; i < states.length; i++) + editions[i+1]= new HistoryItem(base, states[i]); + + EditionSelectionDialog d= new EditionSelectionDialog(parentShell, bundle); + d.setEditionTitleArgument(file.getName()); + d.setEditionTitleImage(CompareUIPlugin.getImage(file)); + //d.setHideIdenticalEntries(false); + if (fHelpContextId != null) + d.setHelpContextId(fHelpContextId); + + if (fReplaceMode) { + + ITypedElement ti= null; + if (fPrevious) + ti= d.selectPreviousEdition(target, editions, null); + else + ti= d.selectEdition(target, editions, null); + + if (ti instanceof IStreamContentAccessor) { + IStreamContentAccessor sa= (IStreamContentAccessor)ti; + + if (Utilities.validateResource(file, parentShell, title)) { + try { + + if (document != null) + updateDocument(document, sa); + else + updateWorkspace(bundle, parentShell, sa, file); + + } catch (InterruptedException x) { + // Do nothing. Operation has been canceled by user. + + } catch (InvocationTargetException x) { + String reason= x.getTargetException().getMessage(); + MessageDialog.openError(parentShell, title, Utilities.getFormattedString(bundle, "replaceError", reason)); //$NON-NLS-1$ + } + } + } + } else { + d.setCompareMode(true); + + d.selectEdition(target, editions, null); + } + } + + private void updateWorkspace(final ResourceBundle bundle, Shell shell, + final IStreamContentAccessor sa, final IFile file) + throws InvocationTargetException, InterruptedException { + WorkspaceModifyOperation operation= new WorkspaceModifyOperation() { + public void execute(IProgressMonitor pm) throws InvocationTargetException { + try { + String taskName= Utilities.getString(bundle, "taskName"); //$NON-NLS-1$ + pm.beginTask(taskName, IProgressMonitor.UNKNOWN); + file.setContents(sa.getContents(), false, true, pm); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } finally { + pm.done(); + } + } + }; + + ProgressMonitorDialog pmdialog= new ProgressMonitorDialog(shell); + pmdialog.run(false, true, operation); + } + + private void updateDocument(IDocument document, IStreamContentAccessor sa) throws InvocationTargetException { + try { + String text= Utilities.readString(sa); + document.replace(0, document.getLength(), text); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } catch (BadLocationException e) { + throw new InvocationTargetException(e); + } + } + + private IDocument getDocument(IFile file) { + IWorkbench wb= PlatformUI.getWorkbench(); + if (wb == null) + return null; + IWorkbenchWindow[] ws= wb.getWorkbenchWindows(); + if (ws == null) + return null; + + FileEditorInput test= new FileEditorInput(file); + + for (int i= 0; i < ws.length; i++) { + IWorkbenchWindow w= ws[i]; + IWorkbenchPage[] wps= w.getPages(); + if (wps != null) { + for (int j= 0; j < wps.length; j++) { + IWorkbenchPage wp= wps[j]; + IEditorPart ep= wp.findEditor(test); + if (ep instanceof ITextEditor) { + ITextEditor te= (ITextEditor) ep; + IDocumentProvider dp= te.getDocumentProvider(); + if (dp != null) { + IDocument doc= dp.getDocument(ep); + if (doc != null) + return doc; + } + } + } + } + } + return null; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java new file mode 100644 index 000000000..723d8cfbb --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ExceptionHandler.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; + +/** + * The default exception handler shows an error dialog when one of its handle methods + * is called. If the passed exception is a <code>CoreException</code> an error dialog + * pops up showing the exception's status information. For a <code>InvocationTargetException</code> + * a normal message dialog pops up showing the exception's message. Additionally the exception + * is written to the platform log. + */ +public class ExceptionHandler { + + private static ExceptionHandler fgInstance= new ExceptionHandler(); + + /* + * Logs the given exception using the platform's logging mechanism. The exception is + * logged as an error with the error code <code>JavaStatusConstants.INTERNAL_ERROR</code>. + */ + public static void log(Throwable t, String message) { + CompareUIPlugin.log(new Status(IStatus.ERROR, CompareUIPlugin.getPluginId(), + CompareUIPlugin.INTERNAL_ERROR, message, t)); + } + + /** + * Handles the given <code>CoreException</code>. The workbench shell is used as a parent + * for the dialog window. + * + * @param e the <code>CoreException</code> to be handled + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(CoreException e, String title, String message) { + handle(e, CompareUIPlugin.getShell(), title, message); + } + + /** + * Handles the given <code>CoreException</code>. + * + * @param e the <code>CoreException</code> to be handled + * @param parent the dialog window's parent shell + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(CoreException e, Shell parent, String title, String message) { + fgInstance.perform(e, parent, title, message); + } + + /** + * Handles the given <code>InvocationTargetException</code>. The workbench shell is used + * as a parent for the dialog window. + * + * @param e the <code>InvocationTargetException</code> to be handled + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(InvocationTargetException e, String title, String message) { + handle(e, CompareUIPlugin.getShell(), title, message); + } + + /** + * Handles the given <code>InvocationTargetException</code>. + * + * @param e the <code>InvocationTargetException</code> to be handled + * @param parent the dialog window's parent shell + * @param title the dialog window's window title + * @param message message to be displayed by the dialog window + */ + public static void handle(InvocationTargetException e, Shell parent, String title, String message) { + fgInstance.perform(e, parent, title, message); + } + + //---- Hooks for subclasses to control exception handling ------------------------------------ + + protected void perform(CoreException e, Shell shell, String title, String message) { + CompareUIPlugin.log(e); + IStatus status= e.getStatus(); + if (status != null) { + ErrorDialog.openError(shell, title, message, status); + } else { + displayMessageDialog(e, e.getMessage(), shell, title, message); + } + } + + protected void perform(InvocationTargetException e, Shell shell, String title, String message) { + Throwable target= e.getTargetException(); + if (target instanceof CoreException) { + perform((CoreException)target, shell, title, message); + } else { + CompareUIPlugin.log(e); + if (e.getMessage() != null && e.getMessage().length() > 0) { + displayMessageDialog(e, e.getMessage(), shell, title, message); + } else { + displayMessageDialog(e, target.getMessage(), shell, title, message); + } + } + } + + //---- Helper methods ----------------------------------------------------------------------- + + private void displayMessageDialog(Throwable t, String exceptionMessage, Shell shell, String title, String message) { + StringWriter msg= new StringWriter(); + if (message != null) { + msg.write(message); + msg.write("\n\n"); //$NON-NLS-1$ + } + if (exceptionMessage == null || exceptionMessage.length() == 0) + msg.write(CompareMessages.ExceptionDialog_seeErrorLogMessage); + else + msg.write(exceptionMessage); + MessageDialog.openError(shell, title, msg.toString()); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java new file mode 100644 index 000000000..e2b36a5bb --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareContextIds.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareUI; + +/** + * Help context ids for the Compare UI. + * <p> + * This interface contains constants only; it is not intended to be implemented + * or extended. + * </p> + * + */ +public interface ICompareContextIds { + + public static final String PREFIX= CompareUI.PLUGIN_ID + '.'; + + // Dialogs + public static final String EDITION_DIALOG= PREFIX + "edition_dialog_context"; //$NON-NLS-1$ + + public static final String COMPARE_EDITOR= PREFIX + "compare_editor_context"; //$NON-NLS-1$ + public static final String PATCH_INPUT_WIZARD_PAGE= PREFIX + "patch_input_wizard_page_context"; //$NON-NLS-1$ + public static final String PATCH_PREVIEW_WIZARD_PAGE= PREFIX + "patch_preview_wizard_page_context"; //$NON-NLS-1$ + public static final String ADD_FROM_HISTORY_DIALOG= PREFIX + "add_from_history_dialog_context"; //$NON-NLS-1$ + public static final String COMPARE_DIALOG= PREFIX + "compare_dialog_context"; //$NON-NLS-1$ + public static final String COMPARE_WITH_EDITION_DIALOG= PREFIX + "compare_with_edition_dialog_context"; //$NON-NLS-1$ + public static final String REPLACE_WITH_EDITION_DIALOG= PREFIX + "replace_with_edition_dialog_context"; //$NON-NLS-1$ + + // Viewer + public static final String TEXT_MERGE_VIEW= PREFIX + "text_merge_view_context"; //$NON-NLS-1$ + public static final String IMAGE_COMPARE_VIEW= PREFIX + "image_compare_view_context"; //$NON-NLS-1$ + public static final String BINARY_COMPARE_VIEW= PREFIX + "binary_compare_view_context"; //$NON-NLS-1$ + public static final String DIFF_VIEW= PREFIX + "diff_view_context"; //$NON-NLS-1$ + + // Actions + public static final String GLOBAL_NEXT_DIFF_ACTION= PREFIX + "global_next_diff_action_context"; //$NON-NLS-1$ + public static final String GLOBAL_PREVIOUS_DIFF_ACTION= PREFIX + "global_previous_diff_action_context"; //$NON-NLS-1$ + public static final String NEXT_DIFF_ACTION= PREFIX + "next_diff_action_context"; //$NON-NLS-1$ + public static final String PREVIOUS_DIFF_ACTION= PREFIX + "previous_diff_action_context"; //$NON-NLS-1$ + public static final String IGNORE_WHITESPACE_ACTION= PREFIX + "ignore_whitespace_action_context"; //$NON-NLS-1$ + + // Preference page + public static final String COMPARE_PREFERENCE_PAGE= PREFIX + "compare_preference_page_context"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java new file mode 100644 index 000000000..e36def063 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ICompareUIConstants.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + + +public interface ICompareUIConstants { + public final String PREFIX = CompareUIPlugin.getPluginId() + "."; //$NON-NLS-1$ + + public static final String DTOOL_NEXT= "dlcl16/next_nav.gif"; //$NON-NLS-1$ + public static final String ETOOL_NEXT= "elcl16/next_nav.gif"; //$NON-NLS-1$ + public static final String CTOOL_NEXT= ETOOL_NEXT; + + public static final String DTOOL_PREV= "dlcl16/prev_nav.gif"; //$NON-NLS-1$ + public static final String ETOOL_PREV= "elcl16/prev_nav.gif"; //$NON-NLS-1$ + public static final String CTOOL_PREV= ETOOL_PREV; + + public static final String HUNK_OBJ = "obj16/hunk_obj.gif"; //$NON-NLS-1$ + + public static final String ERROR_OVERLAY= "ovr16/error_ov.gif"; //$NON-NLS-1$ + public static final String IS_MERGED_OVERLAY= "ovr16/merged_ov.gif"; //$NON-NLS-1$ + public static final String REMOVED_OVERLAY= "ovr16/removed_ov.gif"; //$NON-NLS-1$ + public static final String WARNING_OVERLAY= "ovr16/warning_ov.gif"; //$NON-NLS-1$ + + public static final String RETARGET_PROJECT= "eview16/compare_view.gif"; //$NON-NLS-1$ + + public static final String IGNORE_WHITESPACE_ENABLED= "etool16/ignorews_edit.gif"; //$NON-NLS-1$ + public static final String IGNORE_WHITESPACE_DISABLED= "dtool16/ignorews_edit.gif"; //$NON-NLS-1$ + + public static final String PROP_ANCESTOR_VISIBLE = PREFIX + "AncestorVisible"; //$NON-NLS-1$ + public static final String PROP_IGNORE_ANCESTOR = PREFIX + "IgnoreAncestor"; //$NON-NLS-1$ + public static final String PROP_TITLE = PREFIX + "Title"; //$NON-NLS-1$ + public static final String PROP_TITLE_IMAGE = PREFIX + "TitleImage"; //$NON-NLS-1$ + public static final String PROP_SELECTED_EDITION = PREFIX + "SelectedEdition"; //$NON-NLS-1$ + + public static final int COMPARE_IMAGE_WIDTH= 22; + + public static final String PREF_NAVIGATION_END_ACTION= PREFIX + "NavigationEndAction"; //$NON-NLS-1$ + public static final String PREF_NAVIGATION_END_ACTION_LOCAL= PREFIX + "NavigationEndActionLocal"; //$NON-NLS-1$ + public static final String PREF_VALUE_PROMPT = "prompt"; //$NON-NLS-1$ + public static final String PREF_VALUE_LOOP = "loop"; //$NON-NLS-1$ + public static final String PREF_VALUE_NEXT = "next"; //$NON-NLS-1$ + public static final String PREF_VALUE_DO_NOTHING = "doNothing"; //$NON-NLS-1$ + + public static final String COMMAND_IGNORE_WHITESPACE = PREFIX + "ignoreWhiteSpace"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java new file mode 100644 index 000000000..47a5b735a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IFlushable2.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.contentmergeviewer.IFlushable; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Interface which provides the ability to flush the contents from the specified + * side of the viewer. + * + * @see IFlushable + * + * @since 3.7 + */ +public interface IFlushable2 { + void flushLeft(IProgressMonitor monitor); + + void flushRight(IProgressMonitor monitor); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java new file mode 100644 index 000000000..a790eaae6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IMergeViewerTestAdapter.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.text.IDocument; + +/** + * An interface that provides access to the internals of a merge viewer for the purposes of testing. + * NOTE: This interface is not to be used for any other purpose. + */ +public interface IMergeViewerTestAdapter { + + /** + * Return the document for the given leg + * @param leg the leg (or side) + * @return the document for that leg of the comparison + */ + public IDocument getDocument(char leg); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java new file mode 100644 index 000000000..287a78c33 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ISavingSaveable.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.ui.Saveable; + +/** + * Interface defines API for checking if an object, preferably an instance of + * {@link Saveable}, is being saved. + * + * @since 3.7 + */ +public interface ISavingSaveable { + public boolean isSaving(); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java new file mode 100644 index 000000000..69cbbbc77 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/IViewerDescriptor.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.compare.CompareConfiguration; + +/** + * A factory object for creating a <code>Viewer</code>s from a descriptor. + * <p> + * It is used when registering a viewer for a specific type + * in <code>CompareUIPlugin.registerContentViewerDescriptor</code> and + * in <code>CompareUIPlugin.registerStructureViewerDescriptor</code>. + * + * @see org.eclipse.compare.structuremergeviewer.IStructureCreator + * @see CompareUIPlugin + */ +public interface IViewerDescriptor { + + /** + * Creates a new viewer from this descriptor under the given STW parent control. + * If the current viewer has the same type as a new viewer + * the implementation of this method is free to return the current viewer instead. + * + * @param currentViewer the current viewer which is going to be replaced with a new viewer. + * @param parent the SWT parent control under which the new viewer has to be created. + * @param config a compare configuration the new viewer might be interested in. + * @return a new viewer or the current viewer. + */ + Viewer createViewer(Viewer currentViewer, Composite parent, CompareConfiguration config); +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java new file mode 100644 index 000000000..53d4165ac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageCanvas.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A <code>Canvas</code> showing a single centered SWT <code>Image</code>. + * If the <code>Image</code> is larger than the <code>Canvas<code>, + * <code>Scrollbars</code> will appear. + */ +class ImageCanvas extends Canvas { + + private Image fImage; + + /* + * Create a new ImageCanvas with the given SWT stylebits. + * (SWT.H_SCROLL and SWT.V_SCROLL are automtically added). + */ + public ImageCanvas(Composite parent, int style) { + super(parent, style | SWT.H_SCROLL | SWT.V_SCROLL); + + ScrollBar sb= getHorizontalBar(); + sb.setIncrement(20); + sb.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + repaint(); + } + }); + + sb= getVerticalBar(); + sb.setIncrement(20); + sb.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + repaint(); + } + }); + + addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + updateScrollbars(); + } + }); + + addListener(SWT.Paint, new Listener() { + public void handleEvent(Event event) { + paint(event.gc); + } + }); + } + + /* + * Set the SWT Image to use as the ImageCanvas contents. + */ + public void setImage(Image img) { + fImage= img; + + if (!isDisposed()) { + getHorizontalBar().setSelection(0); + getVerticalBar().setSelection(0); + updateScrollbars(); + getParent().layout(); + redraw(); + } + } + + public void repaint() { + if (!isDisposed()) { + GC gc= new GC(this); + paint(gc); + gc.dispose(); + } + } + + void paint(GC gc) { + if (fImage != null) { + Rectangle bounds= fImage.getBounds(); + Rectangle clientArea= getClientArea(); + + int x; + if (bounds.width < clientArea.width) + x= (clientArea.width - bounds.width) / 2; + else + x= -getHorizontalBar().getSelection(); + + int y; + if (bounds.height < clientArea.height) + y= (clientArea.height - bounds.height) / 2; + else + y= -getVerticalBar().getSelection(); + + gc.drawImage(fImage, x, y); + } + } + + /** + * @private + */ + void updateScrollbars() { + Rectangle bounds= fImage != null ? fImage.getBounds() : new Rectangle(0, 0, 0, 0); + Point size= getSize(); + Rectangle clientArea= getClientArea(); + + ScrollBar horizontal= getHorizontalBar(); + if (bounds.width <= clientArea.width) { + horizontal.setVisible(false); + horizontal.setSelection(0); + } else { + horizontal.setPageIncrement(clientArea.width - horizontal.getIncrement()); + int max= bounds.width + (size.x - clientArea.width); + horizontal.setMaximum(max); + horizontal.setThumb(size.x > max ? max : size.x); + horizontal.setVisible(true); + } + + ScrollBar vertical= getVerticalBar(); + if (bounds.height <= clientArea.height) { + vertical.setVisible(false); + vertical.setSelection(0); + } else { + vertical.setPageIncrement(clientArea.height - vertical.getIncrement()); + int max= bounds.height + (size.y - clientArea.height); + vertical.setMaximum(max); + vertical.setThumb(size.y > max ? max : size.y); + vertical.setVisible(true); + } + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java new file mode 100644 index 000000000..c9cb53956 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewer.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.InputStream; +import java.io.IOException; +import java.util.ResourceBundle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.PlatformUI; +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.ContentMergeViewer; + +/** + */ +public class ImageMergeViewer extends ContentMergeViewer { + + private static final String BUNDLE_NAME= "org.eclipse.compare.internal.ImageMergeViewerResources"; //$NON-NLS-1$ + + private Object fLeftImage; + private Object fRightImage; + + private ImageCanvas fAncestor; + private ImageCanvas fLeft; + private ImageCanvas fRight; + + + public ImageMergeViewer(Composite parent, int styles, CompareConfiguration mp) { + super(styles, ResourceBundle.getBundle(BUNDLE_NAME), mp); + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICompareContextIds.IMAGE_COMPARE_VIEW); + + buildControl(parent); + String title= Utilities.getString(getResourceBundle(), "title"); //$NON-NLS-1$ + getControl().setData(CompareUI.COMPARE_VIEWER_TITLE, title); + } + + protected void updateContent(Object ancestor, Object left, Object right) { + + setInput(fAncestor, ancestor); + + fLeftImage= left; + setInput(fLeft, left); + + fRightImage= right; + setInput(fRight, right); + } + + /* + * We can't modify the contents of either side we just return null. + */ + protected byte[] getContents(boolean left) { + return null; + } + + public void createControls(Composite composite) { + fAncestor= new ImageCanvas(composite, SWT.NO_FOCUS); + fLeft= new ImageCanvas(composite, SWT.NO_FOCUS); + fRight= new ImageCanvas(composite, SWT.NO_FOCUS); + } + + private static void setInput(ImageCanvas canvas, Object input) { + if (canvas != null) { + + InputStream stream= null; + if (input instanceof IStreamContentAccessor) { + IStreamContentAccessor sca= (IStreamContentAccessor) input; + if (sca != null) { + try { + stream= sca.getContents(); + } catch (CoreException ex) { + // NeedWork + } + } + } + + Image image= null; + Display display= canvas.getDisplay(); + if (stream != null) { + try { + image= new Image(display, stream); + } catch (SWTException ex) { + // silently ignored + } + } + + canvas.setImage(image); + if (image != null) { + canvas.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); + } else { + canvas.setBackground(null); + } + + if (stream != null) { + try { + stream.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + } + + protected void handleResizeAncestor(int x, int y, int width, int height) { + if (width > 0) { + fAncestor.setVisible(true); + fAncestor.setBounds(x, y, width, height); + } else { + fAncestor.setVisible(false); + } + } + + protected void handleResizeLeftRight(int x, int y, int width1, int centerWidth, int width2, int height) { + fLeft.setBounds(x, y, width1, height); + fRight.setBounds(x+width1+centerWidth, y, width2, height); + } + + protected void copy(boolean leftToRight) { + if (leftToRight) { + fRightImage= fLeftImage; + setInput(fRight, fRightImage); + setRightDirty(true); + } else { + fLeftImage= fRightImage; + setInput(fLeft, fLeftImage); + setLeftDirty(true); + } + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java new file mode 100644 index 000000000..d73a961c1 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerCreator.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.compare.*; +import org.eclipse.jface.viewers.Viewer; + +/** + * A factory object for the <code>ImageMergeViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class ImageMergeViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new ImageMergeViewer(parent, SWT.NULL, mp); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties new file mode 100644 index 000000000..7388f9c7a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties @@ -0,0 +1,32 @@ +############################################################################### +# Copyright (c) 2000, 2006 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +# @(#)ImageMergeViewerResources.properties +# +# Resource strings for ImageMergeViewer.java + +title= Image Compare + +##################################################### +# Actions +##################################################### + +action.CopyLeftToRight.label= Copy Left to Right +action.CopyLeftToRight.tooltip= Copy Image from Left to Right +action.CopyLeftToRight.image= elcl16/copy_r_co.gif + +action.CopyRightToLeft.label= Copy Right to Left +action.CopyRightToLeft.tooltip= Copy Image from Right to Left +action.CopyRightToLeft.image= elcl16/copy_l_co.gif + +action.EnableAncestor.label= Enable Ancestor Pane +action.EnableAncestor.tooltip= Control Visibility of Ancestor Pane +action.EnableAncestor.image= elcl16/ancestorpane_co.gif diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java new file mode 100644 index 000000000..86a0d6d45 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ListContentProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.List; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * A specialized content provider to show a list of editor parts. + */ +public class ListContentProvider implements IStructuredContentProvider { + List fContents; + + public ListContentProvider() { + // nothing to do + } + + public Object[] getElements(Object input) { + if (fContents != null && fContents == input) + return fContents.toArray(); + return new Object[0]; + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof List) + fContents= (List)newInput; + else + fContents= null; + // we use a fixed set. + } + + public void dispose() { + // empty default implementation + } + + public boolean isDeleted(Object o) { + return fContents != null && !fContents.contains(o); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java new file mode 100644 index 000000000..ec99bd716 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeSourceViewer.java @@ -0,0 +1,1039 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Max Weninger (max.weninger@windriver.com) - Bug 131895 [Edit] Undo in compare + * Max Weninger (max.weninger@windriver.com) - Bug 72936 [Viewers] Show line numbers in comparision + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ResourceBundle; + +import org.eclipse.compare.ICompareContainer; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.commands.operations.IOperationHistory; +import org.eclipse.core.commands.operations.IOperationHistoryListener; +import org.eclipse.core.commands.operations.IUndoContext; +import org.eclipse.core.commands.operations.OperationHistoryEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.action.GroupMarker; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextListener; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension5; +import org.eclipse.jface.text.IUndoManager; +import org.eclipse.jface.text.IUndoManagerExtension; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextEvent; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.ISharedTextColors; +import org.eclipse.jface.text.source.LineNumberRulerColumn; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IPropertyListener; +import org.eclipse.ui.IWorkbenchActionConstants; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.menus.CommandContributionItem; +import org.eclipse.ui.menus.CommandContributionItemParameter; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; +import org.eclipse.ui.texteditor.ChangeEncodingAction; +import org.eclipse.ui.texteditor.FindReplaceAction; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.IElementStateListener; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * Wraps a JFace SourceViewer and add some convenience methods. + */ +public class MergeSourceViewer implements ISelectionChangedListener, + ITextListener, IMenuListener, IOperationHistoryListener, IAdaptable { + + public static final String UNDO_ID= "undo"; //$NON-NLS-1$ + public static final String REDO_ID= "redo"; //$NON-NLS-1$ + public static final String CUT_ID= "cut"; //$NON-NLS-1$ + public static final String COPY_ID= "copy"; //$NON-NLS-1$ + public static final String PASTE_ID= "paste"; //$NON-NLS-1$ + public static final String DELETE_ID= "delete"; //$NON-NLS-1$ + public static final String SELECT_ALL_ID= "selectAll"; //$NON-NLS-1$ + public static final String FIND_ID= "find"; //$NON-NLS-1$ + public static final String GOTO_LINE_ID= "gotoLine"; //$NON-NLS-1$ + public static final String CHANGE_ENCODING_ID= "changeEncoding"; //$NON-NLS-1$ + + class TextOperationAction extends MergeViewerAction { + + private int fOperationCode; + + TextOperationAction(int operationCode, boolean mutable, boolean selection, boolean content) { + this(operationCode, null, mutable, selection, content); + } + + public TextOperationAction(int operationCode, String actionDefinitionId, boolean mutable, boolean selection, boolean content) { + super(mutable, selection, content); + if (actionDefinitionId != null) + setActionDefinitionId(actionDefinitionId); + fOperationCode= operationCode; + update(); + } + + public void run() { + if (isEnabled()) + getSourceViewer().doOperation(fOperationCode); + } + + public boolean isEnabled() { + return fOperationCode != -1 && getSourceViewer().canDoOperation(fOperationCode); + } + + public void update() { + this.setEnabled(isEnabled()); + } + } + + /** + * TODO: The only purpose of this class is to provide "Go to Line" action in + * TextMergeViewer. The adapter should be removed as soon as we implement + * embedded TextEditor in a similar way JDT has it done for Java compare. + */ + class TextEditorAdapter implements ITextEditor { + + public void close(boolean save) { + // defining interface method + } + + public void doRevertToSaved() { + // defining interface method + } + + public IAction getAction(String actionId) { + // defining interface method + return null; + } + + public IDocumentProvider getDocumentProvider() { + return new IDocumentProvider(){ + + public void aboutToChange(Object element) { + // defining interface method + } + + public void addElementStateListener( + IElementStateListener listener) { + // defining interface method + } + + public boolean canSaveDocument(Object element) { + // defining interface method + return false; + } + + public void changed(Object element) { + // defining interface method + } + + public void connect(Object element) throws CoreException { + // defining interface method + } + + public void disconnect(Object element) { + // defining interface method + } + + public IAnnotationModel getAnnotationModel(Object element) { + // defining interface method + return null; + } + + public IDocument getDocument(Object element) { + return MergeSourceViewer.this.getSourceViewer().getDocument(); + } + + public long getModificationStamp(Object element) { + // defining interface method + return 0; + } + + public long getSynchronizationStamp(Object element) { + // defining interface method + return 0; + } + + public boolean isDeleted(Object element) { + // defining interface method + return false; + } + + public boolean mustSaveDocument(Object element) { + // defining interface method + return false; + } + + public void removeElementStateListener( + IElementStateListener listener) { + // defining interface method + } + + public void resetDocument(Object element) throws CoreException { + // defining interface method + } + + public void saveDocument(IProgressMonitor monitor, + Object element, IDocument document, boolean overwrite) + throws CoreException { + // defining interface method + }}; + } + + public IRegion getHighlightRange() { + // defining interface method + return null; + } + + public ISelectionProvider getSelectionProvider() { + return MergeSourceViewer.this.getSourceViewer().getSelectionProvider(); + } + + public boolean isEditable() { + // defining interface method + return false; + } + + public void removeActionActivationCode(String actionId) { + // defining interface method + } + + public void resetHighlightRange() { + // defining interface method + } + + public void selectAndReveal(int start, int length) { + selectAndReveal(start, length, start, length); + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#selectAndReveal(int, int, int, int) + */ + private void selectAndReveal(int selectionStart, int selectionLength, int revealStart, int revealLength) { + + ISelection selection = getSelectionProvider().getSelection(); + if (selection instanceof ITextSelection) { + ITextSelection textSelection = (ITextSelection) selection; + if (textSelection.getOffset() != 0 || textSelection.getLength() != 0) + markInNavigationHistory(); + } + + StyledText widget= MergeSourceViewer.this.getSourceViewer().getTextWidget(); + widget.setRedraw(false); + { + adjustHighlightRange(revealStart, revealLength); + MergeSourceViewer.this.getSourceViewer().revealRange(revealStart, revealLength); + + MergeSourceViewer.this.getSourceViewer().setSelectedRange(selectionStart, selectionLength); + + markInNavigationHistory(); + } + widget.setRedraw(true); + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#markInNavigationHistory() + */ + private void markInNavigationHistory() { + getSite().getPage().getNavigationHistory().markLocation(this); + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#adjustHighlightRange(int, int) + */ + private void adjustHighlightRange(int offset, int length) { + + if (MergeSourceViewer.this instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5) MergeSourceViewer.this; + extension.exposeModelRange(new Region(offset, length)); + } else if (!isVisible(MergeSourceViewer.this.getSourceViewer(), offset, length)) { + MergeSourceViewer.this.getSourceViewer().resetVisibleRegion(); + } + } + + /* + * @see org.eclipse.ui.texteditor.AbstractTextEditor#isVisible(ISourceViewer, int, int) + */ + private /*static*/ final boolean isVisible(ITextViewer viewer, int offset, int length) { + if (viewer instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5) viewer; + IRegion overlap= extension.modelRange2WidgetRange(new Region(offset, length)); + return overlap != null; + } + return viewer.overlapsWithVisibleRegion(offset, length); + } + + public void setAction(String actionID, IAction action) { + // defining interface method + } + + public void setActionActivationCode(String actionId, + char activationCharacter, int activationKeyCode, + int activationStateMask) { + // defining interface method + } + + public void setHighlightRange(int offset, int length, boolean moveCursor) { + // defining interface method + } + + public void showHighlightRangeOnly(boolean showHighlightRangeOnly) { + // defining interface method + } + + public boolean showsHighlightRangeOnly() { + // defining interface method + return false; + } + + public IEditorInput getEditorInput() { + if (MergeSourceViewer.this.fContainer.getWorkbenchPart() instanceof IEditorPart) + return ((IEditorPart) MergeSourceViewer.this.fContainer.getWorkbenchPart()).getEditorInput(); + return null; + } + + public IEditorSite getEditorSite() { + // defining interface method + return null; + } + + public void init(IEditorSite site, IEditorInput input) + throws PartInitException { + // defining interface method + } + + public void addPropertyListener(IPropertyListener listener) { + // defining interface method + } + + public void createPartControl(Composite parent) { + // defining interface method + } + + public void dispose() { + // defining interface method + } + + public IWorkbenchPartSite getSite() { + return MergeSourceViewer.this.fContainer.getWorkbenchPart().getSite(); + } + + public String getTitle() { + // defining interface method + return null; + } + + public Image getTitleImage() { + // defining interface method + return null; + } + + public String getTitleToolTip() { + // defining interface method + return null; + } + + public void removePropertyListener(IPropertyListener listener) { + // defining interface method + } + + public void setFocus() { + // defining interface method + } + + public Object getAdapter(Class adapter) { + // defining interface method + return null; + } + + public void doSave(IProgressMonitor monitor) { + // defining interface method + } + + public void doSaveAs() { + // defining interface method + } + + public boolean isDirty() { + // defining interface method + return false; + } + + public boolean isSaveAsAllowed() { + // defining interface method + return false; + } + + public boolean isSaveOnCloseNeeded() { + // defining interface method + return false; + } + } + + private ResourceBundle fResourceBundle; + private ICompareContainer fContainer; + private SourceViewer fSourceViewer; + private Position fRegion; + private boolean fEnabled= true; + private HashMap fActions= new HashMap(); + private IDocument fRememberedDocument; + + private boolean fAddSaveAction= true; + private boolean isConfigured = false; + + // line number ruler support + private IPropertyChangeListener fPreferenceChangeListener; + private boolean fShowLineNumber=false; + private LineNumberRulerColumn fLineNumberColumn; + private List textActions = new ArrayList(); + private CommandContributionItem fSaveContributionItem; + + public MergeSourceViewer(SourceViewer sourceViewer, ResourceBundle bundle, ICompareContainer container) { + Assert.isNotNull(sourceViewer); + fSourceViewer= sourceViewer; + fResourceBundle= bundle; + fContainer = container; + + MenuManager menu= new MenuManager(); + menu.setRemoveAllWhenShown(true); + menu.addMenuListener(this); + StyledText te= getSourceViewer().getTextWidget(); + te.setMenu(menu.createContextMenu(te)); + fContainer.registerContextMenu(menu, getSourceViewer()); + + // for listening to editor show/hide line number preference value + fPreferenceChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + MergeSourceViewer.this.handlePropertyChangeEvent(event); + } + }; + EditorsUI.getPreferenceStore().addPropertyChangeListener(fPreferenceChangeListener); + fShowLineNumber= EditorsUI.getPreferenceStore().getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); + if(fShowLineNumber){ + updateLineNumberRuler(); + } + + IOperationHistory history = getHistory(); + if (history != null) + history.addOperationHistoryListener(this); + + // don't add save when in a dialog, IWorkbenchPart is null in dialog containers + fAddSaveAction = fContainer.getWorkbenchPart() != null; + } + + public void rememberDocument(IDocument doc) { +// if (doc != null && fRememberedDocument != null) { +// System.err.println("MergeSourceViewer.rememberDocument: fRememberedDocument != null: shouldn't happen"); //$NON-NLS-1$ +// } + fRememberedDocument= doc; + } + + public IDocument getRememberedDocument() { + return fRememberedDocument; + } + + public void hideSaveAction() { + fAddSaveAction= false; + } + + public void setFont(Font font) { + StyledText te= getSourceViewer().getTextWidget(); + if (te != null) + te.setFont(font); + if (fLineNumberColumn != null) { + fLineNumberColumn.setFont(font); + layoutViewer(); + } + } + + public void setBackgroundColor(Color color) { + StyledText te= getSourceViewer().getTextWidget(); + if (te != null) + te.setBackground(color); + if (fLineNumberColumn != null) + fLineNumberColumn.setBackground(color); + } + + public void setForegroundColor(Color color) { + StyledText te= getSourceViewer().getTextWidget(); + if (te != null) + te.setForeground(color); + } + + public void setEnabled(boolean enabled) { + if (enabled != fEnabled) { + fEnabled= enabled; + StyledText c= getSourceViewer().getTextWidget(); + if (c != null) { + c.setEnabled(enabled); + Display d= c.getDisplay(); + c.setBackground(enabled ? d.getSystemColor(SWT.COLOR_LIST_BACKGROUND) : null); + } + } + } + + public boolean getEnabled() { + return fEnabled; + } + + public void setRegion(Position region) { + fRegion= region; + } + + public Position getRegion() { + return fRegion; + } + + public boolean isControlOkToUse() { + StyledText t= getSourceViewer().getTextWidget(); + return t != null && !t.isDisposed(); + } + + public void setSelection(Position position) { + if (position != null) + getSourceViewer().setSelectedRange(position.getOffset(), position.getLength()); + } + + public void setLineBackground(Position position, Color c) { + StyledText t= getSourceViewer().getTextWidget(); + if (t != null && !t.isDisposed()) { + Point region= new Point(0, 0); + getLineRange(position, region); + + region.x-= getDocumentRegionOffset(); + + try { + t.setLineBackground(region.x, region.y, c); + } catch (IllegalArgumentException ex) { + // silently ignored + } + } + } + + public void resetLineBackground() { + StyledText t= getSourceViewer().getTextWidget(); + if (t != null && !t.isDisposed()) { + int lines= getLineCount(); + t.setLineBackground(0, lines, null); + } + } + + /* + * Returns number of lines in document region. + */ + public int getLineCount() { + IRegion region= getSourceViewer().getVisibleRegion(); + + int length= region.getLength(); + if (length == 0) + return 0; + + IDocument doc= getSourceViewer().getDocument(); + int startLine= 0; + int endLine= 0; + + int start= region.getOffset(); + try { + startLine= doc.getLineOfOffset(start); + } catch(BadLocationException ex) { + // silently ignored + } + try { + endLine= doc.getLineOfOffset(start+length); + } catch(BadLocationException ex) { + // silently ignored + } + + return endLine-startLine+1; + } + + public int getViewportLines() { + StyledText te= getSourceViewer().getTextWidget(); + Rectangle clArea= te.getClientArea(); + if (!clArea.isEmpty()) + return clArea.height / te.getLineHeight(); + return 0; + } + + public int getViewportHeight() { + StyledText te= getSourceViewer().getTextWidget(); + Rectangle clArea= te.getClientArea(); + if (!clArea.isEmpty()) + return clArea.height; + return 0; + } + + /* + * Returns lines + */ + public int getDocumentRegionOffset() { + int start= getSourceViewer().getVisibleRegion().getOffset(); + IDocument doc= getSourceViewer().getDocument(); + if (doc != null) { + try { + return doc.getLineOfOffset(start); + } catch(BadLocationException ex) { + // silently ignored + } + } + return 0; + } + + public int getVerticalScrollOffset() { + StyledText st= getSourceViewer().getTextWidget(); + int lineHeight= st.getLineHeight(); + return getSourceViewer().getTopInset() - ((getDocumentRegionOffset()*lineHeight) + st.getTopPixel()); + } + + /* + * Returns the start line and the number of lines which correspond to the given position. + * Starting line number is 0 based. + */ + public Point getLineRange(Position p, Point region) { + + IDocument doc= getSourceViewer().getDocument(); + + if (p == null || doc == null) { + region.x= 0; + region.y= 0; + return region; + } + + int start= p.getOffset(); + int length= p.getLength(); + + int startLine= 0; + try { + startLine= doc.getLineOfOffset(start); + } catch (BadLocationException e) { + // silently ignored + } + + int lineCount= 0; + + if (length == 0) { +// // if range length is 0 and if range starts a new line +// try { +// if (start == doc.getLineStartOffset(startLine)) { +// lines--; +// } +// } catch (BadLocationException e) { +// lines--; +// } + + } else { + int endLine= 0; + try { + endLine= doc.getLineOfOffset(start + length - 1); // why -1? + } catch (BadLocationException e) { + // silently ignored + } + lineCount= endLine-startLine+1; + } + + region.x= startLine; + region.y= lineCount; + return region; + } + + /* + * Scroll TextPart to the given line. + */ + public void vscroll(int line) { + + int srcViewSize= getLineCount(); + int srcExtentSize= getViewportLines(); + + if (srcViewSize > srcExtentSize) { + + if (line < 0) + line= 0; + + int cp= getSourceViewer().getTopIndex(); + if (cp != line) + getSourceViewer().setTopIndex(line + getDocumentRegionOffset()); + } + } + + public void addAction(String actionId, MergeViewerAction action) { + fActions.put(actionId, action); + } + + public IAction getAction(String actionId) { + IAction action= (IAction) fActions.get(actionId); + if (action == null) { + action= createAction(actionId); + if (action == null) + return null; + if (action instanceof MergeViewerAction) { + MergeViewerAction mva = (MergeViewerAction) action; + if (mva.isContentDependent()) + getSourceViewer().addTextListener(this); + if (mva.isSelectionDependent()) + getSourceViewer().addSelectionChangedListener(this); + + Utilities.initAction(action, fResourceBundle, "action." + actionId + "."); //$NON-NLS-1$ //$NON-NLS-2$ + } + addAction(actionId, action); + + } + if (action instanceof MergeViewerAction) { + MergeViewerAction mva = (MergeViewerAction) action; + if (mva.isEditableDependent() && !getSourceViewer().isEditable()) + return null; + } + return action; + } + + protected IAction createAction(String actionId) { + if (UNDO_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.UNDO, IWorkbenchCommandConstants.EDIT_UNDO, true, false, true); + if (REDO_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.REDO, IWorkbenchCommandConstants.EDIT_REDO, true, false, true); + if (CUT_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.CUT, IWorkbenchCommandConstants.EDIT_CUT, true, true, false); + if (COPY_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.COPY, IWorkbenchCommandConstants.EDIT_COPY, false, true, false); + if (PASTE_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.PASTE, IWorkbenchCommandConstants.EDIT_PASTE, true, false, false); + if (DELETE_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.DELETE, IWorkbenchCommandConstants.EDIT_DELETE, true, false, false); + if (SELECT_ALL_ID.equals(actionId)) + return new TextOperationAction(ITextOperationTarget.SELECT_ALL, IWorkbenchCommandConstants.EDIT_SELECT_ALL, false, false, false); + return null; + } + + public void selectionChanged(SelectionChangedEvent event) { + Iterator e= fActions.values().iterator(); + while (e.hasNext()) { + Object next = e.next(); + if (next instanceof MergeViewerAction) { + MergeViewerAction action = (MergeViewerAction) next; + if (action.isSelectionDependent()) + action.update(); + } + } + } + + public void textChanged(TextEvent event) { + updateContentDependantActions(); + } + + void updateContentDependantActions() { + Iterator e= fActions.values().iterator(); + while (e.hasNext()) { + Object next = e.next(); + if (next instanceof MergeViewerAction) { + MergeViewerAction action = (MergeViewerAction) next; + if (action.isContentDependent()) + action.update(); + } + } + } + + /* + * Allows the viewer to add menus and/or tools to the context menu. + */ + public void menuAboutToShow(IMenuManager menu) { + + menu.add(new Separator("undo")); //$NON-NLS-1$ + addMenu(menu, UNDO_ID); + addMenu(menu, REDO_ID); + menu.add(new GroupMarker("save")); //$NON-NLS-1$ + if (fAddSaveAction) + addSave(menu); + menu.add(new Separator("file")); //$NON-NLS-1$ + + menu.add(new Separator("ccp")); //$NON-NLS-1$ + addMenu(menu, CUT_ID); + addMenu(menu, COPY_ID); + addMenu(menu, PASTE_ID); + addMenu(menu, DELETE_ID); + addMenu(menu, SELECT_ALL_ID); + + menu.add(new Separator("edit")); //$NON-NLS-1$ + addMenu(menu, CHANGE_ENCODING_ID); + menu.add(new Separator("find")); //$NON-NLS-1$ + addMenu(menu, FIND_ID); + + menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); + + menu.add(new Separator("text")); //$NON-NLS-1$ + for (Iterator iterator = textActions.iterator(); iterator.hasNext();) { + IAction action = (IAction) iterator.next(); + menu.add(action); + } + + menu.add(new Separator("rest")); //$NON-NLS-1$ + + // update all actions + // to get undo redo right + updateActions(); + } + + private void addMenu(IMenuManager menu, String actionId) { + IAction action= getAction(actionId); + if (action != null) + menu.add(action); + } + + private void addSave(IMenuManager menu) { + ICommandService commandService = (ICommandService) fContainer.getWorkbenchPart().getSite().getService(ICommandService.class); + final Command command= commandService.getCommand(IWorkbenchCommandConstants.FILE_SAVE); + + final IHandler handler = command.getHandler(); + if (handler != null) { + if (fSaveContributionItem == null) { + fSaveContributionItem = new CommandContributionItem( + new CommandContributionItemParameter(fContainer + .getWorkbenchPart().getSite(), null, command + .getId(), CommandContributionItem.STYLE_PUSH)); + } + // save is an editable dependent action, ie add only when edit + // is possible + if (handler.isHandled() && getSourceViewer().isEditable()) + menu.add(fSaveContributionItem); + } + } + + /** + * The viewer is no longer part of the UI, it's a wrapper only. The disposal + * doesn't take place while releasing the editor pane's children. The method + * have to be called it manually. The wrapped viewer is disposed as a + * regular viewer, while disposing the UI. + */ + public void dispose() { + getSourceViewer().removeTextListener(this); + getSourceViewer().removeSelectionChangedListener(this); + EditorsUI.getPreferenceStore().removePropertyChangeListener(fPreferenceChangeListener); + + IOperationHistory history = getHistory(); + if (history != null) + history.removeOperationHistoryListener(this); + } + + /** + * update all actions independent of their type + * + */ + public void updateActions() { + Iterator e= fActions.values().iterator(); + while (e.hasNext()) { + Object next = e.next(); + if (next instanceof MergeViewerAction) { + MergeViewerAction action = (MergeViewerAction) next; + action.update(); + } else if (next instanceof FindReplaceAction) { + FindReplaceAction action = (FindReplaceAction) next; + action.update(); + } else if (next instanceof ChangeEncodingAction) { + ChangeEncodingAction action = (ChangeEncodingAction) next; + action.update(); + } + } + } + + public void configure(SourceViewerConfiguration configuration) { + if (isConfigured ) + getSourceViewer().unconfigure(); + isConfigured = true; + getSourceViewer().configure(configuration); + } + + /** + * specific implementation to support a vertical ruler + * @param x + * @param y + * @param width + * @param height + */ + public void setBounds (int x, int y, int width, int height) { + if(getSourceViewer().getControl() instanceof Composite){ + ((Composite)getSourceViewer().getControl()).setBounds(x, y, width, height); + } else { + getSourceViewer().getTextWidget().setBounds(x, y, width, height); + } + } + + /** + * handle show/hide line numbers from editor preferences + * @param event + */ + protected void handlePropertyChangeEvent(PropertyChangeEvent event) { + + String key= event.getProperty(); + + if(key.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER)){ + boolean b= EditorsUI.getPreferenceStore().getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER); + if (b != fShowLineNumber){ + toggleLineNumberRuler(); + } + } else if (key.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR)) { + updateLineNumberColumnPresentation(true); + } + } + + /** + * Hides or shows line number ruler column based of preference setting + */ + private void updateLineNumberRuler() { + if (!fShowLineNumber) { + if (fLineNumberColumn != null) { + getSourceViewer().removeVerticalRulerColumn(fLineNumberColumn); + } + } else { + if (fLineNumberColumn == null) { + fLineNumberColumn = new LineNumberRulerColumn(); + updateLineNumberColumnPresentation(false); + } + getSourceViewer().addVerticalRulerColumn(fLineNumberColumn); + } + } + + private void updateLineNumberColumnPresentation(boolean refresh) { + if (fLineNumberColumn == null) + return; + RGB rgb= getColorFromStore(EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR); + if (rgb == null) + rgb= new RGB(0, 0, 0); + ISharedTextColors sharedColors= getSharedColors(); + fLineNumberColumn.setForeground(sharedColors.getColor(rgb)); + if (refresh) { + fLineNumberColumn.redraw(); + } + } + + private void layoutViewer() { + Control parent= getSourceViewer().getControl(); + if (parent instanceof Composite && !parent.isDisposed()) + ((Composite) parent).layout(true); + } + + private ISharedTextColors getSharedColors() { + return EditorsUI.getSharedTextColors(); + } + + private RGB getColorFromStore(IPreferenceStore store, String key) { + RGB rgb= null; + if (store.contains(key)) { + if (store.isDefault(key)) + rgb= PreferenceConverter.getDefaultColor(store, key); + else + rgb= PreferenceConverter.getColor(store, key); + } + return rgb; + } + + /** + * Toggles line number ruler column. + */ + private void toggleLineNumberRuler() + { + fShowLineNumber=!fShowLineNumber; + + updateLineNumberRuler(); + } + + public void addTextAction(IAction textEditorPropertyAction) { + textActions.add(textEditorPropertyAction); + } + + public void addAction(String id, IAction action) { + fActions.put(id, action); + } + + private IOperationHistory getHistory() { + if (PlatformUI.getWorkbench() == null) { + return null; + } + return PlatformUI.getWorkbench().getOperationSupport() + .getOperationHistory(); + } + + public void historyNotification(OperationHistoryEvent event) { + // This method updates the enablement of all content operations + // when the undo history changes. It could be localized to UNDO and REDO. + IUndoContext context = getUndoContext(); + if (context != null && event.getOperation().hasContext(context)) { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + updateContentDependantActions(); + } + }); + } + } + + private IUndoContext getUndoContext() { + IUndoManager undoManager = getSourceViewer().getUndoManager(); + if (undoManager instanceof IUndoManagerExtension) + return ((IUndoManagerExtension)undoManager).getUndoContext(); + return null; + } + + /** + * @return the wrapped viewer + */ + public SourceViewer getSourceViewer() { + return fSourceViewer; + } + + public Object getAdapter(Class adapter) { + if (adapter == ITextEditor.class) { + return new TextEditorAdapter(); + } + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java new file mode 100644 index 000000000..b1b579c8e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerAction.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.ui.texteditor.IUpdate; +import org.eclipse.jface.action.Action; + + +public abstract class MergeViewerAction extends Action implements IUpdate { + + private boolean fMutable; + private boolean fSelection; + private boolean fContent; + + public MergeViewerAction(boolean mutable, boolean selection, boolean content) { + fMutable= mutable; + fSelection= selection; + fContent= content; + } + + public boolean isSelectionDependent() { + return fSelection; + } + + public boolean isContentDependent() { + return fContent; + } + + public boolean isEditableDependent() { + return fMutable; + } + + public void update() { + // empty default implementation + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java new file mode 100644 index 000000000..a46445a28 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MergeViewerContentProvider.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider; +import org.eclipse.compare.structuremergeviewer.*; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; + +/** + * Adapts any <code>ContentMergeViewer</code> to work on an <code>ICompareInput</code> + * e.g. a <code>DiffNode</code>. + */ +public class MergeViewerContentProvider implements IMergeViewerContentProvider { + + public static final char ANCESTOR_CONTRIBUTOR = 'A'; + public static final char RIGHT_CONTRIBUTOR = 'R'; + public static final char LEFT_CONTRIBUTOR = 'L'; + + private CompareConfiguration fCompareConfiguration; + private String fAncestorError; + private String fLeftError; + private String fRightError; + + public MergeViewerContentProvider(CompareConfiguration cc) { + fCompareConfiguration= cc; + } + + private boolean hasError() { + return fAncestorError != null || fLeftError != null || fRightError != null; + } + + public void dispose() { + // empty default implementation + } + + public void inputChanged(Viewer v, Object o1, Object o2) { + // we are not interested since we have no state + } + + //---- ancestor + + public void setAncestorError(String errorMessage) { + fAncestorError= errorMessage; + } + + public String getAncestorLabel(Object element) { + if (fAncestorError != null) + return fAncestorError; + return fCompareConfiguration.getAncestorLabel(element); + } + + public Image getAncestorImage(Object element) { + if (fAncestorError != null) + return null; + return fCompareConfiguration.getAncestorImage(element); + } + + public Object getAncestorContent(Object element) { + if (element instanceof ICompareInput) + return ((ICompareInput) element).getAncestor(); + return null; + } + + public boolean showAncestor(Object element) { + if (element instanceof ICompareInput) + return true; // fix for #45239: Show ancestor for incoming and outgoing changes + //return (((ICompareInput)element).getKind() & Differencer.DIRECTION_MASK) == Differencer.CONFLICTING; + return false; + } + + //---- left + + public void setLeftError(String errorMessage) { + fLeftError= errorMessage; + } + + public String getLeftLabel(Object element) { + if (fLeftError != null) + return fLeftError; + return fCompareConfiguration.getLeftLabel(element); + } + + public Image getLeftImage(Object element) { + if (fLeftError != null) + return null; + return fCompareConfiguration.getLeftImage(element); + } + + public Object getLeftContent(Object element) { + if (element instanceof ICompareInput) + return ((ICompareInput) element).getLeft(); + return null; + } + + public boolean isLeftEditable(Object element) { + if (hasError()) + return false; + if (element instanceof ICompareInput) { + Object left= ((ICompareInput) element).getLeft(); + if (left == null && element instanceof IDiffElement) { + IDiffElement parent= ((IDiffElement)element).getParent(); + if (parent instanceof ICompareInput) + left= ((ICompareInput) parent).getLeft(); + } + if (left instanceof IEditableContent) + return ((IEditableContent)left).isEditable(); + } + return false; + } + + public void saveLeftContent(Object element, byte[] bytes) { + if (element instanceof ICompareInput) { + ICompareInput node= (ICompareInput) element; + if (bytes != null) { + ITypedElement left= node.getLeft(); + // #9869: problem if left is null (because no resource exists yet) nothing is done! + if (left == null) { + node.copy(false); + left= node.getLeft(); + } + if (left instanceof IEditableContent) + ((IEditableContent)left).setContent(bytes); + if (node instanceof ResourceCompareInput.MyDiffNode) + ((ResourceCompareInput.MyDiffNode)node).fireChange(); + } else { + node.copy(false); + } + } + } + + //---- right + + public void setRightError(String errorMessage) { + fRightError= errorMessage; + } + + public String getRightLabel(Object element) { + if (fRightError != null) + return fRightError; + return fCompareConfiguration.getRightLabel(element); + } + + public Image getRightImage(Object element) { + if (fRightError != null) + return null; + return fCompareConfiguration.getRightImage(element); + } + + public Object getRightContent(Object element) { + if (element instanceof ICompareInput) + return ((ICompareInput) element).getRight(); + return null; + } + + public boolean isRightEditable(Object element) { + if (hasError()) + return false; + if (element instanceof ICompareInput) { + Object right= ((ICompareInput) element).getRight(); + if (right == null && element instanceof IDiffElement) { + IDiffContainer parent= ((IDiffElement)element).getParent(); + if (parent instanceof ICompareInput) + right= ((ICompareInput) parent).getRight(); + } + if (right instanceof IEditableContent) + return ((IEditableContent)right).isEditable(); + } + return false; + } + + public void saveRightContent(Object element, byte[] bytes) { + if (element instanceof ICompareInput) { + ICompareInput node= (ICompareInput) element; + if (bytes != null) { + ITypedElement right= node.getRight(); + // #9869: problem if right is null (because no resource exists yet) nothing is done! + if (right == null) { + node.copy(true); + right= node.getRight(); + } + if (right instanceof IEditableContent) + ((IEditableContent)right).setContent(bytes); + if (node instanceof ResourceCompareInput.MyDiffNode) + ((ResourceCompareInput.MyDiffNode)node).fireChange(); + } else { + node.copy(true); + } + } + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java new file mode 100644 index 000000000..3ca58fe91 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NavigationEndDialog.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.dialogs.*; +import org.eclipse.jface.preference.RadioGroupFieldEditor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.*; + +public class NavigationEndDialog extends MessageDialogWithToggle { + + private final String[][] labelsAndValues; + private RadioGroupFieldEditor editor; + + public NavigationEndDialog(Shell parentShell, String dialogTitle, + Image dialogTitleImage, String dialogMessage, String[][] labelsAndValues) { + super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, + QUESTION, new String[] { IDialogConstants.OK_LABEL , IDialogConstants.CANCEL_LABEL}, 0, + CompareMessages.NavigationEndDialog_0, false); + this.labelsAndValues = labelsAndValues; + } + + protected Control createCustomArea(Composite parent) { + editor = new RadioGroupFieldEditor(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, CompareMessages.NavigationEndDialog_1, 1, + labelsAndValues, + parent, true); + editor.setPreferenceStore(CompareUIPlugin.getDefault().getPreferenceStore()); + editor.fillIntoGrid(parent, 1); + editor.load(); + return parent; + } + + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + editor.store(); + } + super.buttonPressed(buttonId); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java new file mode 100644 index 000000000..f9fce2b90 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/NullViewer.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.*; + +import org.eclipse.compare.CompareViewerPane; + +/** + * Used whenever the input is null or no viewer can be found. + */ +public class NullViewer extends AbstractViewer { + + private Control fDummy; + + public NullViewer(Composite parent) { + + fDummy= new Tree(parent, SWT.NULL); + + CompareViewerPane.clearToolBar(parent); + } + + public Control getControl() { + return fDummy; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java new file mode 100644 index 000000000..ee303531c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OutlineViewerCreator.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.runtime.*; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.widgets.Composite; + +/** + * Class which allows content merge viewer to provide a structure viewer that can be used in the outline + * view. + */ +public abstract class OutlineViewerCreator { + + /** + * Property constant that identifies the input of the outline view. + */ + public static final String PROP_INPUT = "org.eclipse.compare.OutlineInput"; //$NON-NLS-1$ + + private ListenerList listeners = new ListenerList(ListenerList.IDENTITY); + + /** + * Method called by the editor to create a structure viewer for the current content merge viewer. + * @param oldViewer the current viewer that is being used to show the structure + * @param input the input + * @param parent the parent composite + * @param configuration the compare configuration + * @return a viewer to be placed in the outline viewer or <code>null</code> + */ + public abstract Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, + Composite parent, CompareConfiguration configuration); + + public abstract boolean hasViewerFor(Object input); + + public void addPropertyChangeListener(IPropertyChangeListener listener) { + listeners.add(listener); + } + + public void removePropertyChangeListener(IPropertyChangeListener listener) { + listeners.remove(listener); + } + + public void fireInputChange(Object oldInput, Object newInput) { + Object[] list = listeners.getListeners(); + final PropertyChangeEvent event = new PropertyChangeEvent(this, PROP_INPUT, oldInput, newInput); + for (int i = 0; i < list.length; i++) { + final IPropertyChangeListener listener = (IPropertyChangeListener)list[i]; + SafeRunner.run(new ISafeRunnable() { + public void run() throws Exception { + listener.propertyChange(event); + } + public void handleException(Throwable exception) { + // Logged by SafeRunner + } + }); + } + } + + public abstract Object getInput(); + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java new file mode 100644 index 000000000..de7ba253a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/OverlayPreferenceStore.java @@ -0,0 +1,452 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +/** + * An overlaying preference store. + */ +public class OverlayPreferenceStore implements IPreferenceStore { + + + public static final class TypeDescriptor { + private TypeDescriptor() { + // nothing to do + } + } + + public static final TypeDescriptor BOOLEAN= new TypeDescriptor(); + public static final TypeDescriptor DOUBLE= new TypeDescriptor(); + public static final TypeDescriptor FLOAT= new TypeDescriptor(); + public static final TypeDescriptor INT= new TypeDescriptor(); + public static final TypeDescriptor LONG= new TypeDescriptor(); + public static final TypeDescriptor STRING= new TypeDescriptor(); + + public static class OverlayKey { + + TypeDescriptor fDescriptor; + String fKey; + + public OverlayKey(TypeDescriptor descriptor, String key) { + fDescriptor= descriptor; + fKey= key; + } + } + + private class PropertyListener implements IPropertyChangeListener { + + /* + * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + OverlayKey key= findOverlayKey(event.getProperty()); + if (key != null) + propagateProperty(fParent, key, fStore); + } + } + + + private IPreferenceStore fParent; + private IPreferenceStore fStore; + private OverlayKey[] fOverlayKeys; + + private PropertyListener fPropertyListener; + + + public OverlayPreferenceStore(IPreferenceStore parent, OverlayKey[] overlayKeys) { + fParent= parent; + fOverlayKeys= overlayKeys; + fStore= new PreferenceStore(); + } + + private OverlayKey findOverlayKey(String key) { + for (int i= 0; i < fOverlayKeys.length; i++) { + if (fOverlayKeys[i].fKey.equals(key)) + return fOverlayKeys[i]; + } + return null; + } + + private boolean covers(String key) { + return (findOverlayKey(key) != null); + } + + private void propagateProperty(IPreferenceStore orgin, OverlayKey key, IPreferenceStore target) { + + if (orgin.isDefault(key.fKey)) { + if (!target.isDefault(key.fKey)) + target.setToDefault(key.fKey); + return; + } + + TypeDescriptor d= key.fDescriptor; + if (BOOLEAN == d) { + + boolean originValue= orgin.getBoolean(key.fKey); + boolean targetValue= target.getBoolean(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (DOUBLE == d) { + + double originValue= orgin.getDouble(key.fKey); + double targetValue= target.getDouble(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (FLOAT == d) { + + float originValue= orgin.getFloat(key.fKey); + float targetValue= target.getFloat(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (INT == d) { + + int originValue= orgin.getInt(key.fKey); + int targetValue= target.getInt(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (LONG == d) { + + long originValue= orgin.getLong(key.fKey); + long targetValue= target.getLong(key.fKey); + if (targetValue != originValue) + target.setValue(key.fKey, originValue); + + } else if (STRING == d) { + + String originValue= orgin.getString(key.fKey); + String targetValue= target.getString(key.fKey); + if (targetValue != null && originValue != null && !targetValue.equals(originValue)) + target.setValue(key.fKey, originValue); + + } + } + + public void propagate() { + for (int i= 0; i < fOverlayKeys.length; i++) + propagateProperty(fStore, fOverlayKeys[i], fParent); + } + + private void loadProperty(IPreferenceStore orgin, OverlayKey key, IPreferenceStore target, boolean forceInitialization) { + TypeDescriptor d= key.fDescriptor; + if (BOOLEAN == d) { + + if (forceInitialization) + target.setValue(key.fKey, true); + target.setValue(key.fKey, orgin.getBoolean(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultBoolean(key.fKey)); + + } else if (DOUBLE == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1.0D); + target.setValue(key.fKey, orgin.getDouble(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultDouble(key.fKey)); + + } else if (FLOAT == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1.0F); + target.setValue(key.fKey, orgin.getFloat(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultFloat(key.fKey)); + + } else if (INT == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1); + target.setValue(key.fKey, orgin.getInt(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultInt(key.fKey)); + + } else if (LONG == d) { + + if (forceInitialization) + target.setValue(key.fKey, 1L); + target.setValue(key.fKey, orgin.getLong(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultLong(key.fKey)); + + } else if (STRING == d) { + + if (forceInitialization) + target.setValue(key.fKey, "1"); //$NON-NLS-1$ + target.setValue(key.fKey, orgin.getString(key.fKey)); + target.setDefault(key.fKey, orgin.getDefaultString(key.fKey)); + + } + } + + public void load() { + for (int i= 0; i < fOverlayKeys.length; i++) + loadProperty(fParent, fOverlayKeys[i], fStore, true); + } + + public void loadDefaults() { + for (int i= 0; i < fOverlayKeys.length; i++) + setToDefault(fOverlayKeys[i].fKey); + } + + public void start() { + if (fPropertyListener == null) { + fPropertyListener= new PropertyListener(); + fParent.addPropertyChangeListener(fPropertyListener); + } + } + + public void stop() { + if (fPropertyListener != null) { + fParent.removePropertyChangeListener(fPropertyListener); + fPropertyListener= null; + } + } + + /* + * @see IPreferenceStore#addPropertyChangeListener(IPropertyChangeListener) + */ + public void addPropertyChangeListener(IPropertyChangeListener listener) { + fStore.addPropertyChangeListener(listener); + } + + /* + * @see IPreferenceStore#removePropertyChangeListener(IPropertyChangeListener) + */ + public void removePropertyChangeListener(IPropertyChangeListener listener) { + fStore.removePropertyChangeListener(listener); + } + + /* + * @see IPreferenceStore#firePropertyChangeEvent(String, Object, Object) + */ + public void firePropertyChangeEvent(String name, Object oldValue, Object newValue) { + fStore.firePropertyChangeEvent(name, oldValue, newValue); + } + + /* + * @see IPreferenceStore#contains(String) + */ + public boolean contains(String name) { + return fStore.contains(name); + } + + /* + * @see IPreferenceStore#getBoolean(String) + */ + public boolean getBoolean(String name) { + return fStore.getBoolean(name); + } + + /* + * @see IPreferenceStore#getDefaultBoolean(String) + */ + public boolean getDefaultBoolean(String name) { + return fStore.getDefaultBoolean(name); + } + + /* + * @see IPreferenceStore#getDefaultDouble(String) + */ + public double getDefaultDouble(String name) { + return fStore.getDefaultDouble(name); + } + + /* + * @see IPreferenceStore#getDefaultFloat(String) + */ + public float getDefaultFloat(String name) { + return fStore.getDefaultFloat(name); + } + + /* + * @see IPreferenceStore#getDefaultInt(String) + */ + public int getDefaultInt(String name) { + return fStore.getDefaultInt(name); + } + + /* + * @see IPreferenceStore#getDefaultLong(String) + */ + public long getDefaultLong(String name) { + return fStore.getDefaultLong(name); + } + + /* + * @see IPreferenceStore#getDefaultString(String) + */ + public String getDefaultString(String name) { + return fStore.getDefaultString(name); + } + + /* + * @see IPreferenceStore#getDouble(String) + */ + public double getDouble(String name) { + return fStore.getDouble(name); + } + + /* + * @see IPreferenceStore#getFloat(String) + */ + public float getFloat(String name) { + return fStore.getFloat(name); + } + + /* + * @see IPreferenceStore#getInt(String) + */ + public int getInt(String name) { + return fStore.getInt(name); + } + + /* + * @see IPreferenceStore#getLong(String) + */ + public long getLong(String name) { + return fStore.getLong(name); + } + + /* + * @see IPreferenceStore#getString(String) + */ + public String getString(String name) { + return fStore.getString(name); + } + + /* + * @see IPreferenceStore#isDefault(String) + */ + public boolean isDefault(String name) { + return fStore.isDefault(name); + } + + /* + * @see IPreferenceStore#needsSaving() + */ + public boolean needsSaving() { + return fStore.needsSaving(); + } + + /* + * @see IPreferenceStore#putValue(String, String) + */ + public void putValue(String name, String value) { + if (covers(name)) + fStore.putValue(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, double) + */ + public void setDefault(String name, double value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, float) + */ + public void setDefault(String name, float value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, int) + */ + public void setDefault(String name, int value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, long) + */ + public void setDefault(String name, long value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, String) + */ + public void setDefault(String name, String value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setDefault(String, boolean) + */ + public void setDefault(String name, boolean value) { + if (covers(name)) + fStore.setDefault(name, value); + } + + /* + * @see IPreferenceStore#setToDefault(String) + */ + public void setToDefault(String name) { + fStore.setToDefault(name); + } + + /* + * @see IPreferenceStore#setValue(String, double) + */ + public void setValue(String name, double value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, float) + */ + public void setValue(String name, float value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, int) + */ + public void setValue(String name, int value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, long) + */ + public void setValue(String name, long value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, String) + */ + public void setValue(String name, String value) { + if (covers(name)) + fStore.setValue(name, value); + } + + /* + * @see IPreferenceStore#setValue(String, boolean) + */ + public void setValue(String name, boolean value) { + if (covers(name)) + fStore.setValue(name, value); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java new file mode 100644 index 000000000..4d5eca397 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + + +public class ReplaceWithEditionAction extends EditionAction { + + public ReplaceWithEditionAction() { + super(true, "org.eclipse.compare.internal.ReplaceWithEditionAction"); //$NON-NLS-1$ + fHelpContextId= ICompareContextIds.REPLACE_WITH_EDITION_DIALOG; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties new file mode 100644 index 000000000..94d650eac --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithEditionAction.properties @@ -0,0 +1,40 @@ +############################################################################### +# Copyright (c) 2000, 2006 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +# @(#)ReplaceWithEditionAction.properties +# +# Resources for ReplaceWithEditionAction.java + +title= Replace from Local History + +treeTitleFormat= Local History of ''{0}'' +dateIcon= obj16/day_obj.gif +timeIcon= obj16/resource_obj.gif + +treeFormat= {0} +workspaceTreeFormat= {0} (Workspace File) +parseErrorFormat= {0} (Parse Error) + +editionLabel= Local History ({0}) +workspaceEditionLabel= Workspace File + +targetLabel= {0} + +todayFormat= Today ({0}) +yesterdayFormat= Yesterday ({0}) +dayFormat= {0} + +buttonLabel= Replace + +noLocalHistoryError= No local history available for selected resource. +replaceError=Cannot replace resource (reason: {0}). + +taskName=Replacing diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java new file mode 100644 index 000000000..ecd4e87be --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ReplaceWithPreviousEditionAction.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + + +public class ReplaceWithPreviousEditionAction extends EditionAction { + + public ReplaceWithPreviousEditionAction() { + super(true, + "org.eclipse.compare.internal.ReplaceWithEditionAction"); //$NON-NLS-1$ + fPrevious= true; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java new file mode 100644 index 000000000..d18bf6c7c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResizableDialog.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ResourceBundle; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.DialogSettings; + + +/** + * Base class for resizable Dialogs with persistent window bounds. + */ +public abstract class ResizableDialog extends Dialog { + + // dialog store id constants + private final static String DIALOG_BOUNDS_KEY= "ResizableDialogBounds"; //$NON-NLS-1$ + private static final String X= "x"; //$NON-NLS-1$ + private static final String Y= "y"; //$NON-NLS-1$ + private static final String WIDTH= "width"; //$NON-NLS-1$ + private static final String HEIGHT= "height"; //$NON-NLS-1$ + + protected ResourceBundle fBundle; + private Rectangle fNewBounds; + private IDialogSettings fSettings; + private String fContextId; + + + public ResizableDialog(Shell parent, ResourceBundle bundle) { + super(parent); + setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX); + + fBundle= bundle; + + fSettings= CompareUIPlugin.getDefault().getDialogSettings(); + } + + public void setHelpContextId(String contextId) { + fContextId= contextId; + } + + /* + * @see org.eclipse.jface.window.Window#configureShell(Shell) + */ + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + if (fContextId != null) + PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, fContextId); + } + + protected Point getInitialSize() { + + int width= 0; + int height= 0; + + final Shell s= getShell(); + if (s != null) { + s.addControlListener( + new ControlListener() { + public void controlMoved(ControlEvent arg0) { + fNewBounds= s.getBounds(); + } + public void controlResized(ControlEvent arg0) { + fNewBounds= s.getBounds(); + } + } + ); + } + + IDialogSettings bounds= fSettings.getSection(DIALOG_BOUNDS_KEY); + if (bounds == null) { + if (fBundle != null) { + width= Utilities.getInteger(fBundle, WIDTH, 0); + height= Utilities.getInteger(fBundle, HEIGHT, 0); + Shell shell= getParentShell(); + if (shell != null) { + Point parentSize= shell.getSize(); + if (width <= 0) + width= parentSize.x-300; + if (height <= 0) + height= parentSize.y-200; + } + } else { + Shell shell= getParentShell(); + if (shell != null) { + Point parentSize= shell.getSize(); + width= parentSize.x-100; + height= parentSize.y-100; + } + } + if (width < 700) + width= 700; + if (height < 500) + height= 500; + } else { + try { + width= bounds.getInt(WIDTH); + } catch (NumberFormatException e) { + width= 700; + } + try { + height= bounds.getInt(HEIGHT); + } catch (NumberFormatException e) { + height= 500; + } + } + + return new Point(width, height); + } + + protected Point getInitialLocation(Point initialSize) { + Point loc= super.getInitialLocation(initialSize); + + IDialogSettings bounds= fSettings.getSection(DIALOG_BOUNDS_KEY); + if (bounds != null) { + try { + loc.x= bounds.getInt(X); + } catch (NumberFormatException e) { + // silently ignored + } + try { + loc.y= bounds.getInt(Y); + } catch (NumberFormatException e) { + // silently ignored + } + } + return loc; + } + + public boolean close() { + boolean closed= super.close(); + if (closed && fNewBounds != null) + saveBounds(fNewBounds); + return closed; + } + + private void saveBounds(Rectangle bounds) { + IDialogSettings dialogBounds= fSettings.getSection(DIALOG_BOUNDS_KEY); + if (dialogBounds == null) { + dialogBounds= new DialogSettings(DIALOG_BOUNDS_KEY); + fSettings.addSection(dialogBounds); + } + dialogBounds.put(X, bounds.x); + dialogBounds.put(Y, bounds.y); + dialogBounds.put(WIDTH, bounds.width); + dialogBounds.put(HEIGHT, bounds.height); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java new file mode 100644 index 000000000..fe72e6b24 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ResourceCompareInput.java @@ -0,0 +1,553 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matt McCutchen (hashproduct+eclipse@gmail.com) - Bug 35390 Three-way compare cannot select (mis-selects) )ancestor resource + * Aleksandra Wozniak (aleksandra.k.wozniak@gmail.com) - Bug 239959 + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.ZipFileStructureCreator; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DiffTreeViewer; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +import com.ibm.icu.text.MessageFormat; + + +/** + * A two-way or three-way compare for arbitrary IResources. + */ +class ResourceCompareInput extends CompareEditorInput { + + private static final boolean NORMALIZE_CASE= true; + + private boolean fThreeWay= false; + private Object fRoot; + private IStructureComparator fAncestor; + private IStructureComparator fLeft; + private IStructureComparator fRight; + private IResource fAncestorResource; + private IResource fLeftResource; + private IResource fRightResource; + private DiffTreeViewer fDiffViewer; + private IAction fOpenAction; + + class MyDiffNode extends DiffNode { + + private boolean fDirty= false; + private ITypedElement fLastId; + private String fLastName; + + + public MyDiffNode(IDiffContainer parent, int description, ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(parent, description, ancestor, left, right); + } + public void fireChange() { + super.fireChange(); + setDirty(true); + fDirty= true; + if (fDiffViewer != null) + fDiffViewer.refresh(this); + } + void clearDirty() { + fDirty= false; + } + public String getName() { + if (fLastName == null) + fLastName= super.getName(); + if (fDirty) + return '<' + fLastName + '>'; + return fLastName; + } + + public ITypedElement getId() { + ITypedElement id= super.getId(); + if (id == null) + return fLastId; + fLastId= id; + return id; + } + } + + static class FilteredBufferedResourceNode extends BufferedResourceNode { + FilteredBufferedResourceNode(IResource resource) { + super(resource); + } + protected IStructureComparator createChild(IResource child) { + String name= child.getName(); + if (CompareUIPlugin.getDefault().filter(name, child instanceof IContainer, false)) + return null; + return new FilteredBufferedResourceNode(child); + } + } + + /* + * Creates an compare editor input for the given selection. + */ + ResourceCompareInput(CompareConfiguration config) { + super(config); + } + + public Viewer createDiffViewer(Composite parent) { + fDiffViewer= new DiffTreeViewer(parent, getCompareConfiguration()) { + protected void fillContextMenu(IMenuManager manager) { + + if (fOpenAction == null) { + fOpenAction= new Action() { + public void run() { + handleOpen(null); + } + }; + Utilities.initAction(fOpenAction, getBundle(), "action.CompareContents."); //$NON-NLS-1$ + } + + boolean enable= false; + ISelection selection= getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection ss= (IStructuredSelection)selection; + if (ss.size() == 1) { + Object element= ss.getFirstElement(); + if (element instanceof MyDiffNode) { + ITypedElement te= ((MyDiffNode) element).getId(); + if (te != null) + enable= !ITypedElement.FOLDER_TYPE.equals(te.getType()); + } else + enable= true; + } + } + fOpenAction.setEnabled(enable); + + manager.add(fOpenAction); + + super.fillContextMenu(manager); + } + }; + return fDiffViewer; + } + + class SelectAncestorDialog extends MessageDialog { + private IResource[] theResources; + IResource ancestorResource; + IResource leftResource; + IResource rightResource; + + private Button[] buttons; + + public SelectAncestorDialog(Shell parentShell, IResource[] theResources) { + super(parentShell, CompareMessages.SelectAncestorDialog_title, + null, CompareMessages.SelectAncestorDialog_message, + MessageDialog.QUESTION, + new String[] { IDialogConstants.OK_LABEL, + IDialogConstants.CANCEL_LABEL }, 0); + this.theResources = theResources; + } + + protected Control createCustomArea(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout()); + buttons = new Button[3]; + for (int i = 0; i < 3; i++) { + buttons[i] = new Button(composite, SWT.RADIO); + buttons[i].addSelectionListener(selectionListener); + buttons[i].setText(NLS.bind(CompareMessages.SelectAncestorDialog_option, + theResources[i].getFullPath().toPortableString())); + buttons[i].setFont(parent.getFont()); + // set initial state + buttons[i].setSelection(i == 0); + } + pickAncestor(0); + return composite; + } + + private void pickAncestor(int i) { + ancestorResource = theResources[i]; + leftResource = theResources[i == 0 ? 1 : 0]; + rightResource = theResources[i == 2 ? 1 : 2]; + } + + private SelectionListener selectionListener = new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + Button selectedButton = (Button) e.widget; + if (!selectedButton.getSelection()) + return; + for (int i = 0; i < 3; i++) + if (selectedButton == buttons[i]) + pickAncestor(i); + } + }; + } + // If the compare is three-way, this method asks the user which resource + // to use as the ancestor. Depending on the value of + // showSelectAncestorDialog flag it uses different dialogs to get the + // feedback from the user. Returns false if the user cancels the prompt, + // true otherwise. + boolean setSelection(ISelection s, Shell shell, boolean showSelectAncestorDialog) { + + if (!showSelectAncestorDialog) + return showCompareWithOtherResourceDialog(shell, s); + + IResource[] selection= Utilities.getResources(s); + + fThreeWay= selection.length == 3; + + if (fThreeWay) { + SelectAncestorDialog dialog = + new SelectAncestorDialog(shell, selection); + int code = dialog.open(); + if (code != Window.OK) + return false; + + fAncestorResource= dialog.ancestorResource; + fAncestor= getStructure(fAncestorResource); + fLeftResource= dialog.leftResource; + fRightResource= dialog.rightResource; + } else { + fAncestorResource= null; + fAncestor= null; + fLeftResource= selection[0]; + fRightResource= selection[1]; + } + fLeft= getStructure(fLeftResource); + fRight= getStructure(fRightResource); + return true; + } + + private boolean showCompareWithOtherResourceDialog(Shell shell, ISelection s) { + CompareWithOtherResourceDialog dialog = new CompareWithOtherResourceDialog(shell, s); + if (dialog.open() != IDialogConstants.OK_ID) + return false; + IResource[] selection = dialog.getResult(); + if (!checkSelection(selection)) + return false; + + fThreeWay = selection.length == 3; + if (fThreeWay) { + fAncestorResource = selection[0]; + fAncestor = getStructure(fAncestorResource); + fLeftResource = selection[1]; + fRightResource = selection[2]; + } else { + fAncestorResource = null; + fAncestor = null; + fLeftResource = selection[0]; + fRightResource = selection[1]; + } + fLeft= getStructure(fLeftResource); + fRight= getStructure(fRightResource); + return true; + } + + private boolean checkSelection(IResource[] resources) { + for (int i = 0; i < resources.length; i++) + if (resources[i] == null) + return false; + return true; + } + + /* + * Returns true if compare can be executed for the given selection. + */ + public boolean isEnabled(ISelection s) { + + IResource[] selection= Utilities.getResources(s); + if (selection.length < 2 || selection.length > 3) + return false; + + boolean threeWay= selection.length == 3; + + if (threeWay) + // It only makes sense if they're all mutually comparable. + // If not, the user should compare two of them. + return comparable(selection[0], selection[1]) + && comparable(selection[0], selection[2]) + && comparable(selection[1], selection[2]); + + return comparable(selection[0], selection[1]); + } + + /** + * Initializes the images in the compare configuration. + */ + void initializeCompareConfiguration() { + CompareConfiguration cc= getCompareConfiguration(); + if (fLeftResource != null) { + cc.setLeftLabel(buildLabel(fLeftResource)); + cc.setLeftImage(CompareUIPlugin.getImage(fLeftResource)); + } + if (fRightResource != null) { + cc.setRightLabel(buildLabel(fRightResource)); + cc.setRightImage(CompareUIPlugin.getImage(fRightResource)); + } + if (fThreeWay && fAncestorResource != null) { + cc.setAncestorLabel(buildLabel(fAncestorResource)); + cc.setAncestorImage(CompareUIPlugin.getImage(fAncestorResource)); + } + } + + /* + * Returns true if both resources are either structured or unstructured. + */ + private boolean comparable(IResource c1, IResource c2) { + return hasStructure(c1) == hasStructure(c2); + } + + /* + * Returns true if the given argument has a structure. + */ + private boolean hasStructure(IResource input) { + + if (input instanceof IContainer) + return true; + + if (input instanceof IFile) { + IFile file= (IFile) input; + String type= file.getFileExtension(); + if (type != null) { + type= normalizeCase(type); + return "JAR".equals(type) || "ZIP".equals(type); //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + return false; + } + + /* + * Creates a <code>IStructureComparator</code> for the given input. + * Returns <code>null</code> if no <code>IStructureComparator</code> + * can be found for the <code>IResource</code>. + */ + private IStructureComparator getStructure(IResource input) { + + if (input instanceof IContainer) + return new FilteredBufferedResourceNode(input); + + if (input instanceof IFile) { + IStructureComparator rn= new FilteredBufferedResourceNode(input); + IFile file= (IFile) input; + String type= normalizeCase(file.getFileExtension()); + if ("JAR".equals(type) || "ZIP".equals(type)) //$NON-NLS-2$ //$NON-NLS-1$ + return new ZipFileStructureCreator().getStructure(rn); + return rn; + } + return null; + } + + /* + * Performs a two-way or three-way diff on the current selection. + */ + public Object prepareInput(IProgressMonitor pm) throws InvocationTargetException { + + try { + // fix for PR 1GFMLFB: ITPUI:WIN2000 - files that are out of sync with the file system appear as empty + fLeftResource.refreshLocal(IResource.DEPTH_INFINITE, pm); + fRightResource.refreshLocal(IResource.DEPTH_INFINITE, pm); + if (fThreeWay && fAncestorResource != null) + fAncestorResource.refreshLocal(IResource.DEPTH_INFINITE, pm); + // end fix + + pm.beginTask(Utilities.getString("ResourceCompare.taskName"), IProgressMonitor.UNKNOWN); //$NON-NLS-1$ + + String leftLabel= fLeftResource.getName(); + String rightLabel= fRightResource.getName(); + + String title; + if (fThreeWay) { + String format= Utilities.getString("ResourceCompare.threeWay.title"); //$NON-NLS-1$ + String ancestorLabel= fAncestorResource.getName(); + title= MessageFormat.format(format, new String[] {ancestorLabel, leftLabel, rightLabel}); + } else { + String format= Utilities.getString("ResourceCompare.twoWay.title"); //$NON-NLS-1$ + title= MessageFormat.format(format, new String[] {leftLabel, rightLabel}); + } + setTitle(title); + + Differencer d= new Differencer() { + protected Object visit(Object parent, int description, Object ancestor, Object left, Object right) { + return new MyDiffNode((IDiffContainer) parent, description, (ITypedElement)ancestor, (ITypedElement)left, (ITypedElement)right); + } + }; + + fRoot= d.findDifferences(fThreeWay, pm, null, fAncestor, fLeft, fRight); + return fRoot; + + } catch (CoreException ex) { + throw new InvocationTargetException(ex); + } finally { + pm.done(); + } + } + + public String getToolTipText() { + if (fLeftResource != null && fRightResource != null) { + String leftLabel= fLeftResource.getFullPath().makeRelative().toString(); + String rightLabel= fRightResource.getFullPath().makeRelative().toString(); + if (fThreeWay) { + String format= Utilities.getString("ResourceCompare.threeWay.tooltip"); //$NON-NLS-1$ + String ancestorLabel= fAncestorResource.getFullPath().makeRelative().toString(); + return MessageFormat.format(format, new String[] {ancestorLabel, leftLabel, rightLabel}); + } + String format= Utilities.getString("ResourceCompare.twoWay.tooltip"); //$NON-NLS-1$ + return MessageFormat.format(format, new String[] {leftLabel, rightLabel}); + } + // fall back + return super.getToolTipText(); + } + + private String buildLabel(IResource r) { + // for a linked resource in a hidden project use its local file system location + if (r.isLinked() && r.getProject().isHidden()) + return r.getLocation().toString(); + String n= r.getFullPath().toString(); + if (n.charAt(0) == IPath.SEPARATOR) + return n.substring(1); + return n; + } + + public void saveChanges(IProgressMonitor pm) throws CoreException { + super.saveChanges(pm); + if (fRoot instanceof DiffNode) { + try { + commit(pm, (DiffNode) fRoot); + } finally { + if (fDiffViewer != null) + fDiffViewer.refresh(); + setDirty(false); + } + } + } + + /* + * Recursively walks the diff tree and commits all changes. + */ + private static void commit(IProgressMonitor pm, DiffNode node) throws CoreException { + + if (node instanceof MyDiffNode) + ((MyDiffNode)node).clearDirty(); + + ITypedElement left= node.getLeft(); + if (left instanceof BufferedResourceNode) + ((BufferedResourceNode) left).commit(pm); + + ITypedElement right= node.getRight(); + if (right instanceof BufferedResourceNode) + ((BufferedResourceNode) right).commit(pm); + + IDiffElement[] children= node.getChildren(); + if (children != null) { + for (int i= 0; i < children.length; i++) { + IDiffElement element= children[i]; + if (element instanceof DiffNode) + commit(pm, (DiffNode) element); + } + } + } + + /* (non Javadoc) + * see IAdaptable.getAdapter + */ + public Object getAdapter(Class adapter) { + if (IFile.class.equals(adapter)) { + IProgressMonitor pm= new NullProgressMonitor(); + // flush changes in any dirty viewer + flushViewers(pm); + IFile[] files= (IFile[]) getAdapter(IFile[].class); + if (files != null && files.length > 0) + return files[0]; // can only return one: limitation on IDE.saveAllEditors; see #64617 + return null; + } + if (IFile[].class.equals(adapter)) { + HashSet collector= new HashSet(); + collectDirtyResources(fRoot, collector); + return collector.toArray(new IFile[collector.size()]); + } + return super.getAdapter(adapter); + } + + private void collectDirtyResources(Object o, Set collector) { + if (o instanceof DiffNode) { + DiffNode node= (DiffNode) o; + + ITypedElement left= node.getLeft(); + if (left instanceof BufferedResourceNode) { + BufferedResourceNode bn= (BufferedResourceNode) left; + if (bn.isDirty()) { + IResource resource= bn.getResource(); + if (resource instanceof IFile) + collector.add(resource); + } + } + + ITypedElement right= node.getRight(); + if (right instanceof BufferedResourceNode) { + BufferedResourceNode bn= (BufferedResourceNode) right; + if (bn.isDirty()) { + IResource resource= bn.getResource(); + if (resource instanceof IFile) + collector.add(resource); + } + } + + IDiffElement[] children= node.getChildren(); + if (children != null) { + for (int i= 0; i < children.length; i++) { + IDiffElement element= children[i]; + if (element instanceof DiffNode) + collectDirtyResources(element, collector); + } + } + } + } + + private static String normalizeCase(String s) { + if (NORMALIZE_CASE && s != null) + return s.toUpperCase(); + return s; + } + + public boolean canRunAsJob() { + return true; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java new file mode 100644 index 000000000..2b2e14771 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ShowWhitespaceAction.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.WhitespaceCharacterPainter; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.texteditor.AbstractTextEditor; + +public class ShowWhitespaceAction extends TextEditorPropertyAction { + + private Map fPainters; + private boolean isWhitespaceShowing; + private boolean[] fNeedsPainters; + /** @since 3.7 */ + private boolean fShowLeadingSpaces; + /** @since 3.7 */ + private boolean fShowEnclosedSpaces; + /** @since 3.7 */ + private boolean fShowTrailingSpaces; + /** @since 3.7 */ + private boolean fShowLeadingIdeographicSpaces; + /** @since 3.7 */ + private boolean fShowEnclosedIdeographicSpaces; + /** @since 3.7 */ + private boolean fShowTrailingIdeographicSpace; + /** @since 3.7 */ + private boolean fShowLeadingTabs; + /** @since 3.7 */ + private boolean fShowEnclosedTabs; + /** @since 3.7 */ + private boolean fShowTrailingTabs; + /** @since 3.7 */ + private boolean fShowCarriageReturn; + /** @since 3.7 */ + private boolean fShowLineFeed; + /** @since 3.7 */ + private IPreferenceStore fStore = EditorsUI.getPreferenceStore(); + /** @since 3.7 */ + private int fAlpha; + + public ShowWhitespaceAction(MergeSourceViewer[] viewers, boolean[] needsPainters) { + super(CompareMessages.ShowWhitespaceAction_0, viewers, AbstractTextEditor.PREFERENCE_SHOW_WHITESPACE_CHARACTERS); + fNeedsPainters = needsPainters; + synchronizeWithPreference(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.compare.internal.TextEditorPropertyAction#synchronizeWithPreference() + */ + protected void synchronizeWithPreference() { + boolean checked = false; + if (fStore != null) { + checked = fStore.getBoolean(getPreferenceKey()); + fShowLeadingSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LEADING_SPACES); + fShowEnclosedSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_SPACES); + fShowTrailingSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_TRAILING_SPACES); + fShowLeadingIdeographicSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LEADING_IDEOGRAPHIC_SPACES); + fShowEnclosedIdeographicSpaces = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES); + fShowTrailingIdeographicSpace = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_TRAILING_IDEOGRAPHIC_SPACES); + fShowLeadingTabs = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LEADING_TABS); + fShowEnclosedTabs = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_TABS); + fShowTrailingTabs = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_TRAILING_TABS); + fShowCarriageReturn = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_CARRIAGE_RETURN); + fShowLineFeed = fStore.getBoolean(AbstractTextEditor.PREFERENCE_SHOW_LINE_FEED); + fAlpha = fStore.getInt(AbstractTextEditor.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE); + } + if (checked != isChecked()) { + if (toggleState(checked)) + setChecked(checked); + } else if (fNeedsPainters != null && checked) { + hideWhitespace(); + showWhitespace(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.compare.internal.TextEditorPropertyAction#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + String property = event.getProperty(); + if (property.equals(getPreferenceKey()) || AbstractTextEditor.PREFERENCE_SHOW_LEADING_SPACES.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_SPACES.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_TRAILING_SPACES.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_LEADING_IDEOGRAPHIC_SPACES.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_TRAILING_IDEOGRAPHIC_SPACES.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_LEADING_TABS.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_ENCLOSED_TABS.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_TRAILING_TABS.equals(property) || AbstractTextEditor.PREFERENCE_SHOW_CARRIAGE_RETURN.equals(property) + || AbstractTextEditor.PREFERENCE_SHOW_LINE_FEED.equals(property) || AbstractTextEditor.PREFERENCE_WHITESPACE_CHARACTER_ALPHA_VALUE.equals(property)) { + synchronizeWithPreference(); + } + } + + protected boolean toggleState(boolean checked) { + if (fNeedsPainters == null) + return false; // Not initialized yet + if (checked) { + showWhitespace(); + } else { + hideWhitespace(); + } + return true; + } + + private synchronized Map getPainters() { + if (fPainters == null) + fPainters = new HashMap(); + return fPainters; + } + + private void showWhitespace() { + if (isWhitespaceShowing) + return; + try { + Map painters = getPainters(); + MergeSourceViewer[] viewers = getViewers(); + for (int i = 0; i < viewers.length; i++) { + if (fNeedsPainters[i]) { + MergeSourceViewer viewer = viewers[i]; + SourceViewer sourceViewer = viewer.getSourceViewer(); + WhitespaceCharacterPainter painter; + if (fStore != null) { + painter = new WhitespaceCharacterPainter(sourceViewer, fShowLeadingSpaces, fShowEnclosedSpaces, fShowTrailingSpaces, fShowLeadingIdeographicSpaces, + fShowEnclosedIdeographicSpaces, fShowTrailingIdeographicSpace, fShowLeadingTabs, fShowEnclosedTabs, fShowTrailingTabs, fShowCarriageReturn, fShowLineFeed, fAlpha); + } else { + painter = new WhitespaceCharacterPainter(sourceViewer); + } + sourceViewer.addPainter(painter); + painters.put(viewer, painter); + } + } + } finally { + isWhitespaceShowing = true; + } + } + + private void hideWhitespace() { + Map painters = getPainters(); + for (Iterator iterator = painters.keySet().iterator(); iterator.hasNext();) { + MergeSourceViewer viewer = (MergeSourceViewer) iterator.next(); + WhitespaceCharacterPainter painter = (WhitespaceCharacterPainter)painters.get(viewer); + if (painter != null) { + viewer.getSourceViewer().removePainter(painter); + painter.deactivate(true); + } + } + painters.clear(); + isWhitespaceShowing = false; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java new file mode 100644 index 000000000..a3d1bac5d --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/SimpleTextViewer.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.text.Document; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.compare.*; +import org.eclipse.compare.structuremergeviewer.ICompareInput; + + +public class SimpleTextViewer extends AbstractViewer { + + private SourceViewer fSourceViewer; + private ICompareInput fInput; + + + SimpleTextViewer(Composite parent) { + fSourceViewer= new SourceViewer(parent, null, SWT.H_SCROLL | SWT.V_SCROLL); + fSourceViewer.setEditable(false); + } + + public Control getControl() { + return fSourceViewer.getTextWidget(); + } + + public void setInput(Object input) { + if (input instanceof IStreamContentAccessor) { + fSourceViewer.setDocument(new Document(getString(input))); + } else if (input instanceof ICompareInput) { + fInput= (ICompareInput) input; + ITypedElement left= fInput.getLeft(); + fSourceViewer.setDocument(new Document(getString(left))); + } + } + + public Object getInput() { + return fInput; + } + + private String getString(Object input) { + + if (input instanceof IStreamContentAccessor) { + try { + return Utilities.readString((IStreamContentAccessor) input); + } catch (CoreException ex) { + // NeedWork + CompareUIPlugin.log(ex); + } + } + return ""; //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java new file mode 100644 index 000000000..5fc385ed2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StreamMergerDescriptor.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.IStreamMerger; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; + +/** + * A factory proxy for creating a StructureCreator. + */ +class StreamMergerDescriptor { + + private final static String CLASS_ATTRIBUTE= "class"; //$NON-NLS-1$ + + private IConfigurationElement fElement; + + /* + * Creates a new sorter node with the given configuration element. + */ + public StreamMergerDescriptor(IConfigurationElement element) { + fElement= element; + } + + /* + * Creates a new stream merger from this node. + */ + public IStreamMerger createStreamMerger() { + try { + return (IStreamMerger)fElement.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException ex) { + //ExceptionHandler.handle(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } catch (ClassCastException ex) { + //ExceptionHandler.displayMessageDialog(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java new file mode 100644 index 000000000..7f9011c55 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/StructureCreatorDescriptor.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; + +import org.eclipse.compare.structuremergeviewer.IStructureCreator; + +/** + * A factory proxy for creating a StructureCreator. + */ +public class StructureCreatorDescriptor { + + private final static String CLASS_ATTRIBUTE= "class"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE= "extensions"; //$NON-NLS-1$ + + private IConfigurationElement fElement; + + /* + * Creates a new sorter node with the given configuration element. + */ + public StructureCreatorDescriptor(IConfigurationElement element) { + fElement= element; + } + + /* + * Creates a new sorter from this node. + */ + public IStructureCreator createStructureCreator() { + try { + return (IStructureCreator)fElement.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException ex) { + CompareUIPlugin.log(ex.getStatus()); + //ExceptionHandler.handle(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } catch (ClassCastException ex) { + //ExceptionHandler.displayMessageDialog(ex, SearchMessages.getString("Search.Error.createSorter.title"), SearchMessages.getString("Search.Error.createSorter.message")); //$NON-NLS-2$ //$NON-NLS-1$ + return null; + } + } + + /* + * Returns the structure creator's extensions. + */ + public String getExtension() { + return fElement.getAttribute(EXTENSIONS_ATTRIBUTE); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java new file mode 100644 index 000000000..fc8e61c1e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TabFolderLayout.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Layout; + +public class TabFolderLayout extends Layout { + + protected Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache) { + if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT) + return new Point(wHint, hHint); + + Control [] children = composite.getChildren (); + int count = children.length; + int maxWidth = 0, maxHeight = 0; + for (int i=0; i<count; i++) { + Control child = children [i]; + Point pt = child.computeSize (SWT.DEFAULT, SWT.DEFAULT, flushCache); + maxWidth = Math.max (maxWidth, pt.x); + maxHeight = Math.max (maxHeight, pt.y); + } + + if (wHint != SWT.DEFAULT) + maxWidth= wHint; + if (hHint != SWT.DEFAULT) + maxHeight= hHint; + + return new Point(maxWidth, maxHeight); + + } + + protected void layout (Composite composite, boolean flushCache) { + Rectangle rect= composite.getClientArea(); + + Control[] children = composite.getChildren(); + for (int i = 0; i < children.length; i++) { + children[i].setBounds(rect); + } + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java new file mode 100644 index 000000000..f7a49847f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextEditorPropertyAction.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.editors.text.EditorsUI; + +public class TextEditorPropertyAction extends Action implements IPropertyChangeListener { + + private final MergeSourceViewer[] viewers; + private final String preferenceKey; + private IPreferenceStore store; + + public TextEditorPropertyAction(String label, MergeSourceViewer[] viewers, String preferenceKey) { + super(label, IAction.AS_CHECK_BOX); + this.viewers = viewers; + this.preferenceKey = preferenceKey; + this.store = EditorsUI.getPreferenceStore(); + if (store != null) + store.addPropertyChangeListener(this); + synchronizeWithPreference(); + addActionToViewers(); + } + + private void addActionToViewers() { + for (int i = 0; i < viewers.length; i++) { + MergeSourceViewer viewer = viewers[i]; + viewer.addTextAction(this); + } + } + + public MergeSourceViewer[] getViewers() { + return viewers; + } + + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(getPreferenceKey())) { + synchronizeWithPreference(); + } + } + + protected void synchronizeWithPreference() { + boolean checked = false; + if (store != null) { + checked = store.getBoolean(getPreferenceKey()); + } + if (checked != isChecked()) { + if (toggleState(checked)) + setChecked(checked); + } + } + + public String getPreferenceKey() { + return preferenceKey; + } + + public void run() { + toggleState(isChecked()); + if (store != null) + store.setValue(getPreferenceKey(), isChecked()); + } + + public void dispose() { + if (store != null) + store.removePropertyChangeListener(this); + } + + /** + * @param checked + * new state + * @return <code>true</code> if state has been changed, toggle has been + * successful + */ + protected boolean toggleState(boolean checked) { + // No-op by default + return false; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java new file mode 100644 index 000000000..7c92beb49 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextMergeViewerCreator.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.*; +import org.eclipse.compare.contentmergeviewer.TextMergeViewer; + +/** + * A factory object for the <code>TextMergeViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class TextMergeViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new TextMergeViewer(parent, mp); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java new file mode 100644 index 000000000..e6dbe26d3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/TextViewerCreator.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.swt.widgets.Composite; + +import org.eclipse.jface.viewers.Viewer; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.IViewerCreator; + + +/** + * A factory object for the <code>TextMergeViewer</code>. + * This indirection is necessary because only objects with a default + * constructor can be created via an extension point + * (this precludes Viewers). + */ +public class TextViewerCreator implements IViewerCreator { + + public Viewer createViewer(Composite parent, CompareConfiguration mp) { + return new SimpleTextViewer(parent); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java new file mode 100644 index 000000000..fe48c7219 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Utilities.java @@ -0,0 +1,915 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ISharedDocumentAdapter; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.SharedDocumentAdapter; +import org.eclipse.compare.contentmergeviewer.IDocumentRange; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.internal.patch.PatchMessages; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourceAttributes; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceMappingContext; +import org.eclipse.core.resources.mapping.ResourceTraversal; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.IWorkbenchSite; +import org.eclipse.ui.progress.IWorkbenchSiteProgressService; +import org.eclipse.ui.texteditor.IDocumentProvider; + +import com.ibm.icu.text.MessageFormat; + +/** + * Convenience and utility methods. + */ +public class Utilities { + + private static final IPath ICONS_PATH= new Path("$nl$/icons/full/"); //$NON-NLS-1$ + + public static IWorkbenchPartSite findSite(Control c) { + while (c != null && !c.isDisposed()) { + Object data= c.getData(); + if (data instanceof IWorkbenchPart) + return ((IWorkbenchPart)data).getSite(); + c= c.getParent(); + } + return null; + } + + public static IActionBars findActionBars(Control c) { + while (c != null && !c.isDisposed()) { + Object data= c.getData(); + if (data instanceof CompareEditor) + return ((CompareEditor)data).getActionBars(); + + // PR 1GDVZV7: ITPVCM:WIN98 - CTRL + C does not work in Java source compare + if (data instanceof IViewPart) + return ((IViewPart)data).getViewSite().getActionBars(); + // end PR 1GDVZV7 + + c= c.getParent(); + } + return null; + } + + public static void setEnableComposite(Composite composite, boolean enable) { + Control[] children= composite.getChildren(); + for (int i= 0; i < children.length; i++) + children[i].setEnabled(enable); + } + + public static boolean getBoolean(CompareConfiguration cc, String key, boolean dflt) { + if (cc != null) { + Object value= cc.getProperty(key); + if (value instanceof Boolean) + return ((Boolean) value).booleanValue(); + } + return dflt; + } + + public static void firePropertyChange(ListenerList listenerList, Object source, String property, Object old, Object newValue) { + PropertyChangeEvent event= new PropertyChangeEvent(source, property, old, newValue); + firePropertyChange(listenerList, event); + } + + public static void firePropertyChange(final ListenerList listenerList, final PropertyChangeEvent event) { + if (listenerList == null || listenerList.isEmpty()) + return; + // Legacy listeners may expect to get notified in the UI thread + Runnable runnable = new Runnable() { + public void run() { + Object[] listeners= listenerList.getListeners(); + for (int i= 0; i < listeners.length; i++) { + final IPropertyChangeListener listener= (IPropertyChangeListener) listeners[i]; + SafeRunner.run(new ISafeRunnable() { + public void run() throws Exception { + listener.propertyChange(event); + } + public void handleException(Throwable exception) { + // Logged by SafeRunner + } + }); + } + } + }; + if (Display.getCurrent() == null) { + Display.getDefault().syncExec(runnable); + } else { + runnable.run(); + } + } + + public static boolean okToUse(Widget widget) { + return widget != null && !widget.isDisposed(); + } + + private static ArrayList internalGetResources(ISelection selection, Class type) { + ArrayList tmp= new ArrayList(); + if (selection instanceof IStructuredSelection) { + Object[] s= ((IStructuredSelection)selection).toArray(); + + for (int i= 0; i < s.length; i++) { + IResource resource= null; + Object o= s[i]; + if (type.isInstance(o)) { + resource= (IResource) o; + + } else if (o instanceof ResourceMapping) { + try { + ResourceTraversal[] travs= ((ResourceMapping)o).getTraversals(ResourceMappingContext.LOCAL_CONTEXT, null); + if (travs != null) { + for (int k= 0; k < travs.length; k++) { + IResource[] resources= travs[k].getResources(); + for (int j= 0; j < resources.length; j++) { + if (type.isInstance(resources[j]) && resources[j].isAccessible()) + tmp.add(resources[j]); + } + } + } + } catch (CoreException ex) { + CompareUIPlugin.log(ex); + } + } else if (o instanceof IAdaptable) { + IAdaptable a= (IAdaptable) o; + Object adapter= a.getAdapter(IResource.class); + if (type.isInstance(adapter)) + resource= (IResource) adapter; + } + + if (resource != null && resource.isAccessible()) + tmp.add(resource); + } + } + return tmp; + } + + + /* + * Convenience method: extract all accessible <code>IResources</code> from given selection. + * Never returns null. + */ + public static IResource[] getResources(ISelection selection) { + ArrayList tmp= internalGetResources(selection, IResource.class); + return (IResource[]) tmp.toArray(new IResource[tmp.size()]); + } + + /* + * Convenience method: extract all accessible <code>IFiles</code> from given selection. + * Never returns null. + */ + public static IFile[] getFiles(ISelection selection) { + ArrayList tmp= internalGetResources(selection, IFile.class); + return (IFile[]) tmp.toArray(new IFile[tmp.size()]); + } + + public static byte[] readBytes(InputStream in) { + ByteArrayOutputStream bos= new ByteArrayOutputStream(); + try { + while (true) { + int c= in.read(); + if (c == -1) + break; + bos.write(c); + } + + } catch (IOException ex) { + return null; + + } finally { + Utilities.close(in); + try { + bos.close(); + } catch (IOException x) { + // silently ignored + } + } + + return bos.toByteArray(); + } + + public static IPath getIconPath(Display display) { + return ICONS_PATH; + } + + /* + * Initialize the given Action from a ResourceBundle. + */ + public static void initAction(IAction a, ResourceBundle bundle, String prefix) { + + String labelKey= "label"; //$NON-NLS-1$ + String tooltipKey= "tooltip"; //$NON-NLS-1$ + String imageKey= "image"; //$NON-NLS-1$ + String descriptionKey= "description"; //$NON-NLS-1$ + + if (prefix != null && prefix.length() > 0) { + labelKey= prefix + labelKey; + tooltipKey= prefix + tooltipKey; + imageKey= prefix + imageKey; + descriptionKey= prefix + descriptionKey; + } + + a.setText(getString(bundle, labelKey, labelKey)); + a.setToolTipText(getString(bundle, tooltipKey, null)); + a.setDescription(getString(bundle, descriptionKey, null)); + + String relPath= getString(bundle, imageKey, null); + if (relPath != null && relPath.trim().length() > 0) { + + String dPath; + String ePath; + + if (relPath.indexOf("/") >= 0) { //$NON-NLS-1$ + String path= relPath.substring(1); + dPath= 'd' + path; + ePath= 'e' + path; + } else { + dPath= "dlcl16/" + relPath; //$NON-NLS-1$ + ePath= "elcl16/" + relPath; //$NON-NLS-1$ + } + + ImageDescriptor id= CompareUIPlugin.getImageDescriptor(dPath); // we set the disabled image first (see PR 1GDDE87) + if (id != null) + a.setDisabledImageDescriptor(id); + id= CompareUIPlugin.getImageDescriptor(ePath); + if (id != null) { + a.setImageDescriptor(id); + a.setHoverImageDescriptor(id); + } + } + } + + public static void initToggleAction(IAction a, ResourceBundle bundle, String prefix, boolean checked) { + + String tooltip= null; + if (checked) + tooltip= getString(bundle, prefix + "tooltip.checked", null); //$NON-NLS-1$ + else + tooltip= getString(bundle, prefix + "tooltip.unchecked", null); //$NON-NLS-1$ + if (tooltip == null) + tooltip= getString(bundle, prefix + "tooltip", null); //$NON-NLS-1$ + + if (tooltip != null) + a.setToolTipText(tooltip); + + String description= null; + if (checked) + description= getString(bundle, prefix + "description.checked", null); //$NON-NLS-1$ + else + description= getString(bundle, prefix + "description.unchecked", null); //$NON-NLS-1$ + if (description == null) + description= getString(bundle, prefix + "description", null); //$NON-NLS-1$ + + if (description != null) + a.setDescription(description); + + } + + public static String getString(ResourceBundle bundle, String key, String dfltValue) { + + if (bundle != null) { + try { + return bundle.getString(key); + } catch (MissingResourceException x) { + // fall through + } + } + return dfltValue; + } + + public static String getFormattedString(ResourceBundle bundle, String key, String arg) { + + if (bundle != null) { + try { + return MessageFormat.format(bundle.getString(key), new String[] { arg }); + } catch (MissingResourceException x) { + CompareUIPlugin.log(x); + } + } + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + + public static String getString(String key) { + try { + return CompareUI.getResourceBundle().getString(key); + } catch (MissingResourceException e) { + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + public static String getFormattedString(String key, String arg) { + try { + return MessageFormat.format(CompareUI.getResourceBundle().getString(key), new String[] { arg }); + } catch (MissingResourceException e) { + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + public static String getFormattedString(String key, String arg0, String arg1) { + try { + return MessageFormat.format(CompareUI.getResourceBundle().getString(key), new String[] { arg0, arg1 }); + } catch (MissingResourceException e) { + return "!" + key + "!";//$NON-NLS-2$ //$NON-NLS-1$ + } + } + + public static String getString(ResourceBundle bundle, String key) { + return getString(bundle, key, key); + } + + public static int getInteger(ResourceBundle bundle, String key, int dfltValue) { + + if (bundle != null) { + try { + String s= bundle.getString(key); + if (s != null) + return Integer.parseInt(s); + } catch (NumberFormatException x) { + CompareUIPlugin.log(x); + } catch (MissingResourceException x) { + // silently ignore Exception + } + } + return dfltValue; + } + + /** + * Answers <code>true</code> if the given selection contains resources that don't + * have overlapping paths and <code>false</code> otherwise. + */ + /* + public static boolean isSelectionNonOverlapping() throws TeamException { + IResource[] resources = getSelectedResources(); + // allow operation for non-overlapping resource selections + if(resources.length>0) { + List validPaths = new ArrayList(2); + for (int i = 0; i < resources.length; i++) { + IResource resource = resources[i]; + + // only allow cvs resources to be selected + if(RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()) == null) { + return false; + } + + // check if this resource overlaps other selections + IPath resourceFullPath = resource.getFullPath(); + if(!validPaths.isEmpty()) { + for (Iterator it = validPaths.iterator(); it.hasNext();) { + IPath path = (IPath) it.next(); + if(path.isPrefixOf(resourceFullPath) || + resourceFullPath.isPrefixOf(path)) { + return false; + } + } + } + validPaths.add(resourceFullPath); + + // ensure that resources are managed + ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource); + if(cvsResource.isFolder()) { + if( ! ((ICVSFolder)cvsResource).isCVSFolder()) return false; + } else { + if( ! cvsResource.isManaged()) return false; + } + } + return true; + } + return false; + } + */ + + /* validate edit utilities */ + + /** + * Status constant indicating that an validateEdit call has changed the + * content of a file on disk. + */ + private static final int VALIDATE_EDIT_PROBLEM= 10004; + + /** + * Constant used to indicate that tests are being run. + */ + public static boolean RUNNING_TESTS = false; + + /** + * Constant used while testing the indicate that changes should be flushed + * when the compare input changes and a viewer is dirty. + */ + public static boolean TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE = false; + + /* + * Makes the given resources committable. Committable means that all + * resources are writeable and that the content of the resources hasn't + * changed by calling <code>validateEdit</code> for a given file on + * <tt>IWorkspace</tt>. + * + * @param resources the resources to be checked + * @param shell the Shell passed to <code>validateEdit</code> as a context + * @return returns <code>true</code> if all resources are committable, <code>false</code> otherwise + * + * @see org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object) + */ + public static boolean validateResource(IResource resource, Shell shell, String title) { + return validateResources(new IResource[] { resource }, shell, title); + } + + /* + * Makes the given resources committable. Committable means that all + * resources are writeable and that the content of the resources hasn't + * changed by calling <code>validateEdit</code> for a given file on + * <tt>IWorkspace</tt>. + * + * @param resources the resources to be checked + * @param shell the Shell passed to <code>validateEdit</code> as a context + * @return returns <code>true</code> if all resources are committable, <code>false</code> otherwise + * + * @see org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object) + */ + public static boolean validateResources(List resources, Shell shell, String title) { + IResource r[]= (IResource[]) resources.toArray(new IResource[resources.size()]); + return validateResources(r, shell, title); + } + + /* + * Makes the given resources committable. Committable means that all + * resources are writeable and that the content of the resources hasn't + * changed by calling <code>validateEdit</code> for a given file on + * <tt>IWorkspace</tt>. + * + * @param resources the resources to be checked + * @param shell the Shell passed to <code>validateEdit</code> as a context + * @return returns <code>true</code> if all resources are committable, <code>false</code> otherwise + * + * @see org.eclipse.core.resources.IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], java.lang.Object) + */ + public static boolean validateResources(IResource[] resources, Shell shell, String title) { + + // get all readonly files + List readOnlyFiles= getReadonlyFiles(resources); + if (readOnlyFiles.size() == 0) + return true; + + // get timestamps of readonly files before validateEdit + Map oldTimeStamps= createModificationStampMap(readOnlyFiles); + + IFile[] files= (IFile[]) readOnlyFiles.toArray(new IFile[readOnlyFiles.size()]); + IStatus status= ResourcesPlugin.getWorkspace().validateEdit(files, shell); + if (! status.isOK()) { + String message= getString("ValidateEdit.error.unable_to_perform"); //$NON-NLS-1$ + displayError(shell, title, status, message); + return false; + } + + IStatus modified= null; + Map newTimeStamps= createModificationStampMap(readOnlyFiles); + for (Iterator iter= oldTimeStamps.keySet().iterator(); iter.hasNext();) { + IFile file= (IFile) iter.next(); + if (file.isReadOnly()) { + IStatus entry= new Status(IStatus.ERROR, + CompareUIPlugin.getPluginId(), + VALIDATE_EDIT_PROBLEM, + getFormattedString("ValidateEdit.error.stillReadonly", file.getFullPath().toString()), //$NON-NLS-1$ + null); + modified= addStatus(modified, entry); + } else if (! oldTimeStamps.get(file).equals(newTimeStamps.get(file))) { + IStatus entry= new Status(IStatus.ERROR, + CompareUIPlugin.getPluginId(), + VALIDATE_EDIT_PROBLEM, + getFormattedString("ValidateEdit.error.fileModified", file.getFullPath().toString()), //$NON-NLS-1$ + null); + modified= addStatus(modified, entry); + } + } + if (modified != null) { + String message= getString("ValidateEdit.error.unable_to_perform"); //$NON-NLS-1$ + displayError(shell, title, modified, message); + return false; + } + return true; + } + + private static void displayError(final Shell shell, final String title, final IStatus status, final String message) { + if (Display.getCurrent() != null) + ErrorDialog.openError(shell, title, message, status); + else { + Display.getDefault().syncExec(new Runnable() { + public void run() { + ErrorDialog.openError(shell, title, message, status); + } + }); + } + } + + private static List getReadonlyFiles(IResource[] resources) { + List readOnlyFiles= new ArrayList(); + for (int i= 0; i < resources.length; i++) { + IResource resource= resources[i]; + ResourceAttributes resourceAttributes= resource.getResourceAttributes(); + if (resource.getType() == IResource.FILE && resourceAttributes != null && resourceAttributes.isReadOnly()) + readOnlyFiles.add(resource); + } + return readOnlyFiles; + } + + private static Map createModificationStampMap(List files) { + Map map= new HashMap(); + for (Iterator iter= files.iterator(); iter.hasNext(); ) { + IFile file= (IFile)iter.next(); + map.put(file, new Long(file.getModificationStamp())); + } + return map; + } + + private static IStatus addStatus(IStatus status, IStatus entry) { + + if (status == null) + return entry; + + if (status.isMultiStatus()) { + ((MultiStatus)status).add(entry); + return status; + } + + MultiStatus result= new MultiStatus(CompareUIPlugin.getPluginId(), + VALIDATE_EDIT_PROBLEM, + getString("ValidateEdit.error.unable_to_perform"), null); //$NON-NLS-1$ + result.add(status); + result.add(entry); + return result; + } + + // encoding + + public static String readString(IStreamContentAccessor sca, String encoding) throws CoreException { + String s = null; + try { + try { + s= Utilities.readString(sca.getContents(), encoding); + } catch (UnsupportedEncodingException e) { + if (!encoding.equals(ResourcesPlugin.getEncoding())) { + s = Utilities.readString(sca.getContents(), ResourcesPlugin.getEncoding()); + } + } + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, e.getMessage(), e)); + } + return s; + } + + /* + * Returns null if an error occurred. + */ + public static String readString(InputStream is, String encoding) throws IOException { + return readString(is, encoding, -1, null); + } + + public static String readString(InputStream is, String encoding, int length, IProgressMonitor monitor) throws IOException { + SubMonitor progress = SubMonitor.convert(monitor); + progress.setWorkRemaining(length); + if (is == null) + return null; + BufferedReader reader= null; + try { + StringBuffer buffer= new StringBuffer(); + char[] part= new char[2048]; + int read= 0; + reader= new BufferedReader(new InputStreamReader(is, encoding)); + while ((read= reader.read(part)) != -1) { + buffer.append(part, 0, read); + progress.worked(2048); + if (progress.isCanceled()) + throw new OperationCanceledException(); + } + + return buffer.toString(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + } + + public static String getCharset(Object resource) { + if (resource instanceof IEncodedStorage) { + try { + return ((IEncodedStorage)resource).getCharset(); + } catch (CoreException ex) { + CompareUIPlugin.log(ex); + } + } + return ResourcesPlugin.getEncoding(); + } + + public static byte[] getBytes(String s, String encoding) { + byte[] bytes= null; + if (s != null) { + try { + bytes= s.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + bytes= s.getBytes(); + } + } + return bytes; + } + + public static String readString(IStreamContentAccessor sa) throws CoreException { + String encoding= null; + if (sa instanceof IEncodedStreamContentAccessor) + encoding= ((IEncodedStreamContentAccessor)sa).getCharset(); + if (encoding == null) + encoding= ResourcesPlugin.getEncoding(); + return Utilities.readString(sa, encoding); + } + + public static void close(InputStream is) { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + + public static IResource getFirstResource(ISelection selection) { + IResource[] resources = getResources(selection); + if (resources.length > 0) + return resources[0]; + return null; + } + + public static Object getAdapter(Object element, Class adapterType, boolean load) { + if (adapterType.isInstance(element)) + return element; + if (element instanceof IAdaptable) { + Object adapted = ((IAdaptable) element).getAdapter(adapterType); + if (adapterType.isInstance(adapted)) + return adapted; + } + if (load) { + Object adapted = Platform.getAdapterManager().loadAdapter(element, adapterType.getName()); + if (adapterType.isInstance(adapted)) + return adapted; + } else { + Object adapted = Platform.getAdapterManager().getAdapter(element, adapterType); + if (adapterType.isInstance(adapted)) + return adapted; + } + return null; + } + + public static Object getAdapter(Object element, Class adapterType) { + return getAdapter(element, adapterType, false); + } + + public static ITypedElement getLeg(char type, Object input) { + if (input instanceof ICompareInput) { + switch (type) { + case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR: + return ((ICompareInput)input).getAncestor(); + case MergeViewerContentProvider.LEFT_CONTRIBUTOR: + return ((ICompareInput)input).getLeft(); + case MergeViewerContentProvider.RIGHT_CONTRIBUTOR: + return ((ICompareInput)input).getRight(); + } + } + return null; + } + + public static IDocument getDocument(char type, Object element, boolean isUsingDefaultContentProvider, boolean canHaveSharedDocument) { + ITypedElement te= getLeg(type, element); + if (te == null) + return null; + if (te instanceof IDocument) + return (IDocument) te; + if (te instanceof IDocumentRange) + return ((IDocumentRange) te).getDocument(); + + if (isUsingDefaultContentProvider && canHaveSharedDocument) { + ISharedDocumentAdapter sda = (ISharedDocumentAdapter)Utilities.getAdapter(te, ISharedDocumentAdapter.class, true); + if (sda != null) { + IEditorInput input= sda.getDocumentKey(te); + if (input != null) { + IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(input); + if (provider != null) + return provider.getDocument(input); + } + } + } + + if (te instanceof IStreamContentAccessor) + return DocumentManager.get(te); + + return null; + } + + /** + * Return whether either the left or right sides of the given input + * represents a hunk. A hunk is a portion of a file. + * @param input the compare input + * @return whether the left or right side of the input represents a hunk + */ + public static boolean isHunk(Object input) { + if (input != null && input instanceof DiffNode){ + ITypedElement right = ((DiffNode) input).getRight(); + if (right != null) { + Object element = Utilities.getAdapter(right, IHunk.class); + if (element instanceof IHunk) + return true; + } + ITypedElement left = ((DiffNode) input).getLeft(); + if (left != null) { + Object element = Utilities.getAdapter(left, IHunk.class); + if (element instanceof IHunk) + return true; + } + } + return false; + } + + public static boolean isHunkOk(Object input) { + if (input != null && input instanceof DiffNode){ + ITypedElement right = ((DiffNode) input).getRight(); + if (right != null) { + Object element = Utilities.getAdapter(right, HunkResult.class); + if (element instanceof HunkResult) { + return ((HunkResult)element).isOK(); + } + } + ITypedElement left = ((DiffNode) input).getLeft(); + if (left != null) { + Object element = Utilities.getAdapter(left, HunkResult.class); + if (element instanceof HunkResult) + return ((HunkResult)element).isOK(); + } + } + return false; + } + + public static void schedule(Job job, IWorkbenchSite site) { + if (site != null) { + IWorkbenchSiteProgressService siteProgress = (IWorkbenchSiteProgressService) site.getAdapter(IWorkbenchSiteProgressService.class); + if (siteProgress != null) { + siteProgress.schedule(job, 0, true /* use half-busy cursor */); + return; + } + } + job.schedule(); + } + + public static void runInUIThread(final Runnable runnable) { + if (Display.getCurrent() != null) { + BusyIndicator.showWhile(Display.getCurrent(), runnable); + } else { + Display.getDefault().syncExec(new Runnable() { + public void run() { + BusyIndicator.showWhile(Display.getCurrent(), runnable); + } + }); + } + } + + /** + * @param connection a connection for which the timeout is set + * @param timeout an int that specifies the connect timeout value in milliseconds + * @return whether the timeout has been successfully set + */ + public static boolean setReadTimeout(URLConnection connection, int timeout) { + Method[] methods = connection.getClass().getMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals("setReadTimeout")) //$NON-NLS-1$ + try { + methods[i].invoke(connection, new Object[] {new Integer(timeout)}); + return true; + } catch (IllegalArgumentException e) { // ignore + } catch (IllegalAccessException e) { // ignore + } catch (InvocationTargetException e) { // ignore + } + } + return false; + } + + /** + * Load content of file under <code>url</code> displaying progress on given + * context. + * + * @param url + * @param context + * @return the content of file under given URL, or <code>null</code> if URL + * could not be loaded + * @throws InvocationTargetException + * thrown on errors while URL loading + * @throws OperationCanceledException + * @throws InterruptedException + */ + public static String getURLContents(final URL url, IRunnableContext context) + throws InvocationTargetException, OperationCanceledException, + InterruptedException { + final String[] result = new String[1]; + context.run(true, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) + throws InvocationTargetException, InterruptedException { + SubMonitor progress = SubMonitor.convert(monitor, + PatchMessages.InputPatchPage_URLConnecting, 100); + try { + URLConnection connection = url.openConnection(); + progress.worked(10); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + setReadTimeout(connection, 60 * 1000); + progress.setTaskName(PatchMessages.InputPatchPage_URLFetchingContent); + String enc = connection.getContentEncoding(); + if (enc == null) + enc = ResourcesPlugin.getEncoding(); + result[0] = Utilities.readString( + connection.getInputStream(), enc, + connection.getContentLength(), + progress.newChild(90)); + } catch (SocketTimeoutException e) { + throw new InvocationTargetException(e); + } catch (IOException e) { + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + } + }); + return result[0]; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java new file mode 100644 index 000000000..e5d35bcf9 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerDescriptor.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.IViewerCreator; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.widgets.Composite; + +/** + * Creates <code>Viewer</code>s from an <code>IConfigurationElement</code>. + */ +public class ViewerDescriptor implements IViewerDescriptor { + + private final static String CLASS_ATTRIBUTE= "class"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE= "extensions"; //$NON-NLS-1$ + private final static String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$ + + private IConfigurationElement fConfiguration; + private IViewerCreator fViewerCreator; + private Class fViewerClass; + + public ViewerDescriptor(IConfigurationElement config) { + fConfiguration= config; + } + + public Viewer createViewer(Viewer currentViewer, Composite parent, CompareConfiguration mp) { + + if (currentViewer != null && currentViewer.getClass() == fViewerClass) { + //System.out.println("reused viewer: " + currentViewer.getClass().getName()); + return currentViewer; + } + + if (fViewerCreator == null) { + try { + fViewerCreator= (IViewerCreator) fConfiguration.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + } + + if (fViewerCreator != null) { + // If we are going to return a new viewer, we want to preemptively deregister + // any handlers to avoid the logging of conflict warnings + if (currentViewer != null) { + CompareHandlerService[] compareHandlerService = (CompareHandlerService[]) Utilities.getAdapter(currentViewer, CompareHandlerService[].class); + if (compareHandlerService != null) { + for (int i = 0; i < compareHandlerService.length; i++) { + compareHandlerService[i].dispose(); + } + } + } + Viewer viewer= fViewerCreator.createViewer(parent, mp); + if (viewer != null) + fViewerClass= viewer.getClass(); + return viewer; + } + + return null; + } + + public String getExtension() { + return fConfiguration.getAttribute(EXTENSIONS_ATTRIBUTE); + } + + String getLabel() { + return fConfiguration.getAttribute(LABEL_ATTRIBUTE); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java new file mode 100644 index 000000000..6bd7a541f --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ViewerSwitchingCancelled.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + + +public class ViewerSwitchingCancelled extends RuntimeException { + + private static final long serialVersionUID = 1L; + +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java new file mode 100644 index 000000000..14ebbc46a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkQueue.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.*; + +import org.eclipse.jface.operation.IRunnableWithProgress; + +/** + * A work queue maintains a list of tasks that need to be run. + * If the same task is added multiple times, the last occurrence of + * the task will be run(i.e. the task will be removed from it's + * previous location and aded to the end of the queue. + */ +public class WorkQueue { + + private List runnables = new ArrayList(); + + public boolean add(IRunnableWithProgress runnable) { + if (runnables.contains(runnable)) + runnables.remove(runnable); + return runnables.add(runnable); + } + + public void clear() { + runnables.clear(); + } + + public boolean contains(IRunnableWithProgress runnable) { + return runnables.contains(runnable); + } + + public boolean isEmpty() { + return runnables.isEmpty(); + } + + public boolean remove(IRunnableWithProgress runnable) { + return runnables.remove(runnable); + } + + public int size() { + return runnables.size(); + } + public IRunnableWithProgress remove() { + return (IRunnableWithProgress)runnables.remove(0); + } + + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java new file mode 100644 index 000000000..69455f981 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/Worker.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.*; +import org.eclipse.jface.operation.IRunnableWithProgress; + +/** + * A worker performs a set of tasks in order and accumulates any errors + * that may have occurred. If the same task is queued multiple times, + * the last occurrence will be run. If a task is queued while it is + * running, the running task will be canceled and the task added + * to the end of the work queue. + */ +public class Worker implements IRunnableWithProgress { + + private final WorkQueue work = new WorkQueue(); + private boolean isWorking; + private final List errors = new ArrayList(); + private WorkProgressMonitor currentMonitor; + private IRunnableWithProgress currentTask; + private final String taskName; + + /** + * Progress monitor that supports local cancelation of a task. + */ + private static class WorkProgressMonitor extends ProgressMonitorWrapper { + private boolean localCancel; + protected WorkProgressMonitor(IProgressMonitor monitor) { + super(monitor); + } + public void cancelTask() { + localCancel = true; + } + public boolean isCanceled() { + return localCancel || super.isCanceled(); + } + } + + public Worker(String taskName) { + this.taskName = taskName; + } + + public void run(IProgressMonitor monitor) { + errors.clear(); + SubMonitor pm = SubMonitor.convert(monitor, getTaskName(), 100); + try { + isWorking = true; + while (!work.isEmpty()) { + try { + performNextTask(pm); + checkCancelled(pm); + } catch (OperationCanceledException e) { + // Only cancel all the work if the outer monitor is canceled + checkCancelled(pm); + } catch (InterruptedException e) { + // Only cancel all the work if the outer monitor is canceled + checkCancelled(pm); + } catch (InvocationTargetException e) { + handleError(e.getTargetException()); + } + pm.setWorkRemaining(100); + } + } catch (OperationCanceledException e) { + // The user chose to cancel + work.clear(); + } finally { + isWorking = false; + if (monitor!= null) + monitor.done(); + currentMonitor = null; + currentTask = null; + } + } + + private WorkProgressMonitor subMonitorFor(SubMonitor pm, int ticks) { + return new WorkProgressMonitor(pm.newChild(ticks)); + } + + private void handleError(Throwable targetException) { + errors.add(targetException); + } + + public Throwable[] getErrors() { + return (Throwable[]) errors.toArray(new Throwable[errors.size()]); + } + + private void checkCancelled(SubMonitor pm) { + if (pm.isCanceled()) + throw new OperationCanceledException(); + } + + protected String getTaskName() { + return taskName; + } + + private void performNextTask(SubMonitor pm) throws InvocationTargetException, InterruptedException { + synchronized (this) { + if (work.isEmpty()) + return; + currentTask = work.remove(); + } + currentMonitor = subMonitorFor(pm, 10); + currentTask.run(currentMonitor); + } + + public synchronized void add(IRunnableWithProgress r) { + if (currentTask != null && currentTask.equals(r)) { + currentMonitor.cancelTask(); + } + work.add(r); + } + + public boolean isWorking() { + return isWorking; + } + + public boolean hasWork() { + return isWorking() || !work.isEmpty(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java new file mode 100644 index 000000000..7dd38c9d4 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/WorkerJob.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.operation.IRunnableWithProgress; + +public class WorkerJob extends Job { + + private final Worker worker; + + public WorkerJob(String name) { + super(name); + worker = new Worker(name); + } + + protected IStatus run(IProgressMonitor monitor) { + worker.run(monitor); + // reschedule to ensure we don't miss a task + IStatus result = getResult(worker); + schedule(); + return result; + } + + private IStatus getResult(Worker w) { + Throwable[] errors = w.getErrors(); + if (errors.length == 0) + return Status.OK_STATUS; + if (errors.length == 1) + return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, errors[0].getMessage(), errors[0]); + List statii = new ArrayList(); + for (int i = 0; i < errors.length; i++) { + Throwable throwable = errors[i]; + statii.add(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, errors[0].getMessage(), throwable)); + } + return new MultiStatus(CompareUIPlugin.PLUGIN_ID, 0, (IStatus[]) statii.toArray(new IStatus[statii.size()]), CompareMessages.WorkerJob_0, null); + } + + public boolean shouldRun() { + return worker.hasWork(); + } + + public void add(IRunnableWithProgress runnable) { + worker.add(runnable); + schedule(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java new file mode 100644 index 000000000..463d8d159 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java @@ -0,0 +1,1409 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.merge; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import org.eclipse.jface.operation.IRunnableWithProgress; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; + +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressService; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.contentmergeviewer.ITokenComparator; +import org.eclipse.compare.internal.CompareContentViewerSwitchingPane; +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.DocLineComparator; +import org.eclipse.compare.internal.MergeViewerContentProvider; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.core.LCS; +import org.eclipse.compare.rangedifferencer.IRangeComparator; +import org.eclipse.compare.rangedifferencer.RangeDifference; +import org.eclipse.compare.rangedifferencer.RangeDifferencer; +import org.eclipse.compare.structuremergeviewer.Differencer; + +/** + * A document merger manages the differences between two documents + * for either a 2-way or 3-way comparison. + * <p> + * This class should not have any UI dependencies. + */ +public class DocumentMerger { + + private static final String DIFF_RANGE_CATEGORY = CompareUIPlugin.PLUGIN_ID + ".DIFF_RANGE_CATEGORY"; //$NON-NLS-1$ + + /** Selects between smartTokenDiff and mergingTokenDiff */ + private static final boolean USE_MERGING_TOKEN_DIFF= false; + + /** if true copying conflicts from one side to other concatenates both sides */ + private static final boolean APPEND_CONFLICT= true; + + /** All diffs for calculating scrolling position (includes line ranges without changes) */ + private ArrayList fAllDiffs; + /** Subset of above: just real differences. */ + private ArrayList fChangeDiffs; + + private final boolean fLeftIsLocal; + + private IDocumentMergerInput fInput; + + /** + * Interface that defines that input to the document merge process + */ + public interface IDocumentMergerInput { + + IDocument getDocument(char contributor); + + Position getRegion(char contributor); + + boolean isIgnoreAncestor(); + + boolean isThreeWay(); + + CompareConfiguration getCompareConfiguration(); + + ITokenComparator createTokenComparator(String s); + + boolean isHunkOnLeft(); + + int getHunkStart(); + + boolean isPatchHunk(); + + boolean isShowPseudoConflicts(); + + boolean isPatchHunkOk(); + } + + public class Diff { + /** character range in ancestor document */ + Position fAncestorPos; + /** character range in left document */ + Position fLeftPos; + /** character range in right document */ + Position fRightPos; + /** if this is a TokenDiff fParent points to the enclosing LineDiff */ + Diff fParent; + /** if Diff has been resolved */ + boolean fResolved; + int fDirection; + boolean fIsToken= false; + /** child token diffs */ + ArrayList fDiffs; + boolean fIsWhitespace= false; + + /* + * Create Diff from two ranges and an optional parent diff. + */ + Diff(Diff parent, int dir, IDocument ancestorDoc, Position aRange, int ancestorStart, int ancestorEnd, + IDocument leftDoc, Position lRange, int leftStart, int leftEnd, + IDocument rightDoc, Position rRange, int rightStart, int rightEnd) { + fParent= parent != null ? parent : this; + fDirection= dir; + + fLeftPos= createPosition(leftDoc, lRange, leftStart, leftEnd); + fRightPos= createPosition(rightDoc, rRange, rightStart, rightEnd); + if (ancestorDoc != null) + fAncestorPos= createPosition(ancestorDoc, aRange, ancestorStart, ancestorEnd); + } + + public Position getPosition(char type) { + switch (type) { + case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR: + return fAncestorPos; + case MergeViewerContentProvider.LEFT_CONTRIBUTOR: + return fLeftPos; + case MergeViewerContentProvider.RIGHT_CONTRIBUTOR: + return fRightPos; + } + return null; + } + + boolean isInRange(char type, int pos) { + Position p= getPosition(type); + return (pos >= p.offset) && (pos < (p.offset+p.length)); + } + + public String changeType() { + boolean leftEmpty= fLeftPos.length == 0; + boolean rightEmpty= fRightPos.length == 0; + + if (fDirection == RangeDifference.LEFT) { + if (!leftEmpty && rightEmpty) + return CompareMessages.TextMergeViewer_changeType_addition; + if (leftEmpty && !rightEmpty) + return CompareMessages.TextMergeViewer_changeType_deletion; + } else { + if (leftEmpty && !rightEmpty) + return CompareMessages.TextMergeViewer_changeType_addition; + if (!leftEmpty && rightEmpty) + return CompareMessages.TextMergeViewer_changeType_deletion; + } + return CompareMessages.TextMergeViewer_changeType_change; + } + + public Image getImage() { + int code= Differencer.CHANGE; + switch (fDirection) { + case RangeDifference.RIGHT: + code+= Differencer.LEFT; + break; + case RangeDifference.LEFT: + code+= Differencer.RIGHT; + break; + case RangeDifference.ANCESTOR: + case RangeDifference.CONFLICT: + code+= Differencer.CONFLICTING; + break; + } + if (code != 0) + return getCompareConfiguration().getImage(code); + return null; + } + + Position createPosition(IDocument doc, Position range, int start, int end) { + try { + int l= end-start; + if (range != null) { + int dl= range.length; + if (l > dl) + l= dl; + } else { + int dl= doc.getLength(); + if (start+l > dl) + l= dl-start; + } + + Position p= null; + try { + p= new Position(start, l); + } catch (RuntimeException ex) { + p= new Position(0, 0); + } + + try { + doc.addPosition(DIFF_RANGE_CATEGORY, p); + } catch (BadPositionCategoryException ex) { + // silently ignored + } + return p; + } catch (BadLocationException ee) { + // silently ignored + } + return null; + } + + void add(Diff d) { + if (fDiffs == null) + fDiffs= new ArrayList(); + fDiffs.add(d); + } + + public boolean isDeleted() { + if (fAncestorPos != null && fAncestorPos.isDeleted()) + return true; + return fLeftPos.isDeleted() || fRightPos.isDeleted(); + } + + void setResolved(boolean r) { + fResolved= r; + if (r) + fDiffs= null; + } + + public boolean isResolved() { + if (!fResolved && fDiffs != null) { + Iterator e= fDiffs.iterator(); + while (e.hasNext()) { + Diff d= (Diff) e.next(); + if (!d.isResolved()) + return false; + } + return true; + } + return fResolved; + } + +// private boolean isIncoming() { +// switch (fDirection) { +// case RangeDifference.RIGHT: +// if (fLeftIsLocal) +// return true; +// break; +// case RangeDifference.LEFT: +// if (!fLeftIsLocal) +// return true; +// break; +// } +// return false; +// } + + public boolean isIncomingOrConflicting() { + switch (fDirection) { + case RangeDifference.RIGHT: + if (fLeftIsLocal) + return true; + break; + case RangeDifference.LEFT: + if (!fLeftIsLocal) + return true; + break; + case RangeDifference.CONFLICT: + return true; + } + return false; + } + +// private boolean isUnresolvedIncoming() { +// if (fResolved) +// return false; +// return isIncoming(); +// } + + public boolean isUnresolvedIncomingOrConflicting() { + if (fResolved) + return false; + return isIncomingOrConflicting(); + } + + Position getPosition(int contributor) { + if (contributor == MergeViewerContentProvider.LEFT_CONTRIBUTOR) + return fLeftPos; + if (contributor == MergeViewerContentProvider.RIGHT_CONTRIBUTOR) + return fRightPos; + if (contributor == MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR) + return fAncestorPos; + return null; + } + + /* + * Returns true if given character range overlaps with this Diff. + */ + public boolean overlaps(int contributor, int start, int end, int docLength) { + Position h= getPosition(contributor); + if (h != null) { + int ds= h.getOffset(); + int de= ds + h.getLength(); + if ((start < de) && (end >= ds)) + return true; + if ((start == docLength) && (start <= de) && (end >= ds)) + return true; + } + return false; + } + + public int getMaxDiffHeight() { + Point region= new Point(0, 0); + int h= getLineRange(getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR), fLeftPos, region).y; + if (isThreeWay()) + h= Math.max(h, getLineRange(getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR), fAncestorPos, region).y); + return Math.max(h, getLineRange(getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR), fRightPos, region).y); + } + + public int getAncestorHeight() { + Point region= new Point(0, 0); + return getLineRange(getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR), fAncestorPos, region).y; + } + + public int getLeftHeight() { + Point region= new Point(0, 0); + return getLineRange(getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR), fLeftPos, region).y; + } + + public int getRightHeight() { + Point region= new Point(0, 0); + return getLineRange(getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR), fRightPos, region).y; + } + + public Diff[] getChangeDiffs(int contributor, IRegion region) { + if (fDiffs != null && intersectsRegion(contributor, region)) { + List result = new ArrayList(); + for (Iterator iterator = fDiffs.iterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.intersectsRegion(contributor, region)) { + result.add(diff); + } + } + return (Diff[]) result.toArray(new Diff[result.size()]); + } + return new Diff[0]; + } + + private boolean intersectsRegion(int contributor, IRegion region) { + Position p = getPosition(contributor); + if (p != null) + return p.overlapsWith(region.getOffset(), region.getLength()); + return false; + } + + public boolean hasChildren() { + return fDiffs != null && !fDiffs.isEmpty(); + } + + public int getKind() { + return fDirection; + } + + public boolean isToken() { + return fIsToken; + } + + public Diff getParent() { + return fParent; + } + + public Iterator childIterator() { + if (fDiffs == null) + return new ArrayList().iterator(); + return fDiffs.iterator(); + } + } + + public DocumentMerger(IDocumentMergerInput input) { + this.fInput = input; + fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$ + } + + /** + * Perform a two level 2- or 3-way diff. + * The first level is based on line comparison, the second level on token comparison. + * @throws CoreException + */ + public void doDiff() throws CoreException { + + fChangeDiffs= new ArrayList(); + IDocument lDoc = getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + IDocument rDoc = getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + + if (lDoc == null || rDoc == null) + return; + + Position lRegion= getRegion(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + Position rRegion= getRegion(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + + IDocument aDoc = null; + Position aRegion= null; + if (isThreeWay() && !isIgnoreAncestor()) { + aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); + aRegion= getRegion(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); + } + + resetPositions(lDoc); + resetPositions(rDoc); + resetPositions(aDoc); + + boolean ignoreWhiteSpace= isIgnoreWhitespace(); + + DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace); + DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace); + DocLineComparator sancestor= null; + if (aDoc != null) { + sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + /*if (isPatchHunk()) { + if (isHunkOnLeft()) { + sright= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + } else { + sleft= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + } + }*/ + } + + final Object[] result= new Object[1]; + final DocLineComparator sa= sancestor, sl= sleft, sr= sright; + IRunnableWithProgress runnable= new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException { + monitor.beginTask(CompareMessages.DocumentMerger_0, maxWork(sa, sl, sr)); + try { + result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr); + } catch (OutOfMemoryError ex) { + System.gc(); + throw new InvocationTargetException(ex); + } + if (monitor.isCanceled()) { // canceled + throw new InterruptedException(); + } + monitor.done(); + } + }; + + RangeDifference[] e= null; + try { + getCompareConfiguration().getContainer().run(true, true, runnable); + e= (RangeDifference[]) result[0]; + } catch (InvocationTargetException ex) { + // we create a NOCHANGE range for the whole document + Diff diff= new Diff(null, RangeDifference.NOCHANGE, + aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0, + lDoc, lRegion, 0, lDoc.getLength(), + rDoc, rRegion, 0, rDoc.getLength()); + + fAllDiffs = new ArrayList(); + fAllDiffs.add(diff); + throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, CompareMessages.DocumentMerger_1, ex.getTargetException())); + } catch (InterruptedException ex) { + // we create a NOCHANGE range for the whole document + Diff diff= new Diff(null, RangeDifference.NOCHANGE, + aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0, + lDoc, lRegion, 0, lDoc.getLength(), + rDoc, rRegion, 0, rDoc.getLength()); + + fAllDiffs = new ArrayList(); + fAllDiffs.add(diff); + return; + } + + if (isCapped(sa, sl, sr)) + fInput.getCompareConfiguration().setProperty( + CompareContentViewerSwitchingPane.OPTIMIZED_ALGORITHM_USED, + new Boolean(true)); + else + fInput.getCompareConfiguration().setProperty( + CompareContentViewerSwitchingPane.OPTIMIZED_ALGORITHM_USED, + new Boolean(false)); + + ArrayList newAllDiffs = new ArrayList(); + for (int i= 0; i < e.length; i++) { + RangeDifference es= e[i]; + + int ancestorStart= 0; + int ancestorEnd= 0; + if (sancestor != null) { + ancestorStart= sancestor.getTokenStart(es.ancestorStart()); + ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength()); + } + + int leftStart= sleft.getTokenStart(es.leftStart()); + int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength()); + + int rightStart= sright.getTokenStart(es.rightStart()); + int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength()); + + /*if (isPatchHunk()) { + if (isHunkOnLeft()) { + rightStart = rightEnd = getHunkStart(); + } else { + leftStart = leftEnd = getHunkStart(); + } + }*/ + + Diff diff= new Diff(null, es.kind(), + aDoc, aRegion, ancestorStart, ancestorEnd, + lDoc, lRegion, leftStart, leftEnd, + rDoc, rRegion, rightStart, rightEnd); + + newAllDiffs.add(diff); // remember all range diffs for scrolling + + if (isPatchHunk()) { + if (useChange(diff)) { + recordChangeDiff(diff); + } + } else { + if (ignoreWhiteSpace || useChange(es.kind())) { + + // Extract the string for each contributor. + String a= null; + if (sancestor != null) + a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength()); + String s= extract2(lDoc, sleft, es.leftStart(), es.leftLength()); + String d= extract2(rDoc, sright, es.rightStart(), es.rightLength()); + + // Indicate whether all contributors are whitespace + if (ignoreWhiteSpace + && (a == null || a.trim().length() == 0) + && s.trim().length() == 0 + && d.trim().length() == 0) { + diff.fIsWhitespace= true; + } + + // If the diff is of interest, record it and generate the token diffs + if (useChange(diff)) { + recordChangeDiff(diff); + if (s.length() > 0 && d.length() > 0) { + if (a == null && sancestor != null) + a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength()); + if (USE_MERGING_TOKEN_DIFF) + mergingTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s); + else + simpleTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s); + } + } + } + } + } + fAllDiffs = newAllDiffs; + } + + private boolean isCapped(DocLineComparator ancestor, + DocLineComparator left, DocLineComparator right) { + if (isCappingDisabled()) + return false; + int aLength = ancestor == null? 0 : ancestor.getRangeCount(); + int lLength = left.getRangeCount(); + int rLength = right.getRangeCount(); + if ((double) aLength * (double) lLength > LCS.TOO_LONG + || (double) aLength * (double) rLength > LCS.TOO_LONG + || (double) lLength * (double) rLength > LCS.TOO_LONG) + return true; + return false; + } + + public Diff findDiff(char type, int pos) throws CoreException { + + IDocument aDoc= null; + IDocument lDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + IDocument rDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + if (lDoc == null || rDoc == null) + return null; + + Position aRegion= null; + Position lRegion= null; + Position rRegion= null; + + boolean threeWay= isThreeWay(); + + if (threeWay && !isIgnoreAncestor()) + aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR); + + boolean ignoreWhiteSpace= isIgnoreWhitespace(); + + DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace); + DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace); + DocLineComparator sancestor= null; + if (aDoc != null) + sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace); + + final Object[] result= new Object[1]; + final DocLineComparator sa= sancestor, sl= sleft, sr= sright; + IRunnableWithProgress runnable= new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException { + monitor.beginTask(CompareMessages.DocumentMerger_2, maxWork(sa, sl, sr)); + try { + result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr); + } catch (OutOfMemoryError ex) { + System.gc(); + throw new InvocationTargetException(ex); + } + if (monitor.isCanceled()) { // canceled + throw new InterruptedException(); + } + monitor.done(); + } + }; + IProgressService progressService= PlatformUI.getWorkbench().getProgressService(); + + RangeDifference[] e= null; + try { + progressService.run(true, true, runnable); + e= (RangeDifference[]) result[0]; + } catch (InvocationTargetException ex) { + throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, CompareMessages.DocumentMerger_3, ex.getTargetException())); + } catch (InterruptedException ex) { + // + } + + if (e != null) { + for (int i= 0; i < e.length; i++) { + RangeDifference es= e[i]; + + int kind= es.kind(); + + int ancestorStart= 0; + int ancestorEnd= 0; + if (sancestor != null) { + ancestorStart= sancestor.getTokenStart(es.ancestorStart()); + ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength()); + } + + int leftStart= sleft.getTokenStart(es.leftStart()); + int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength()); + + int rightStart= sright.getTokenStart(es.rightStart()); + int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength()); + + Diff diff= new Diff(null, kind, + aDoc, aRegion, ancestorStart, ancestorEnd, + lDoc, lRegion, leftStart, leftEnd, + rDoc, rRegion, rightStart, rightEnd); + + if (diff.isInRange(type, pos)) + return diff; + } + } + + return null; + } + + private void recordChangeDiff(Diff diff) { + fChangeDiffs.add(diff); // here we remember only the real diffs + } + + /*private boolean isHunkOnLeft() { + return fInput.isHunkOnLeft(); + } + + private int getHunkStart() { + return fInput.getHunkStart(); + }*/ + + private boolean isPatchHunk() { + return fInput.isPatchHunk(); + } + + private boolean isIgnoreWhitespace() { + return Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false); + } + + private boolean isCappingDisabled() { + return CompareUIPlugin.getDefault().getPreferenceStore().getBoolean(ComparePreferencePage.CAPPING_DISABLED); + } + + private IDocument getDocument(char contributor) { + return fInput.getDocument(contributor); + } + + private Position getRegion(char contributor) { + return fInput.getRegion(contributor); + } + + public boolean isIgnoreAncestor() { + return fInput.isIgnoreAncestor(); + } + + public boolean isThreeWay() { + return fInput.isThreeWay(); + } + + /** + * Return the compare configuration associated with this merger. + * @return the compare configuration associated with this merger + */ + public CompareConfiguration getCompareConfiguration() { + return fInput.getCompareConfiguration(); + } + + /* + * Returns true if kind of change should be shown. + */ + public boolean useChange(Diff diff) { + if (diff.fIsWhitespace) + return false; + int kind = diff.getKind(); + return useChange(kind); + } + + private boolean useChange(int kind) { + if (kind == RangeDifference.NOCHANGE) + return false; + if (fInput.getCompareConfiguration().isChangeIgnored(kind)) + return false; + if (kind == RangeDifference.ANCESTOR) + return fInput.isShowPseudoConflicts(); + return true; + } + + private int getTokenEnd(ITokenComparator tc, int start, int count) { + if (count <= 0) + return tc.getTokenStart(start); + int index= start + count - 1; + return tc.getTokenStart(index) + tc.getTokenLength(index); + } + + private static int getTokenEnd2(ITokenComparator tc, int start, int length) { + return tc.getTokenStart(start + length); + } + + /** + * Returns the content of lines in the specified range as a String. + * This includes the line separators. + * + * @param doc the document from which to extract the characters + * @param start index of first line + * @param length number of lines + * @return the contents of the specified line range as a String + */ + private String extract2(IDocument doc, ITokenComparator tc, int start, int length) { + int count= tc.getRangeCount(); + if (length > 0 && count > 0) { + +// +// int startPos= tc.getTokenStart(start); +// int endPos= startPos; +// +// if (length > 1) +// endPos= tc.getTokenStart(start + (length-1)); +// endPos+= tc.getTokenLength(start + (length-1)); +// + + int startPos= tc.getTokenStart(start); + int endPos; + + if (length == 1) { + endPos= startPos + tc.getTokenLength(start); + } else { + endPos= tc.getTokenStart(start + length); + } + + try { + return doc.get(startPos, endPos - startPos); + } catch (BadLocationException e) { + // silently ignored + } + + } + return ""; //$NON-NLS-1$ + } + + private static IRegion toRegion(Position position) { + if (position != null) + return new Region(position.getOffset(), position.getLength()); + return null; + } + + /* + * Performs a "smart" token based 3-way diff on the character range specified by the given baseDiff. + * It is "smart" because it tries to minimize the number of token diffs by merging them. + */ + private void mergingTokenDiff(Diff baseDiff, + IDocument ancestorDoc, String a, + IDocument rightDoc, String d, + IDocument leftDoc, String s) { + ITokenComparator sa= null; + int ancestorStart= 0; + if (ancestorDoc != null) { + sa= createTokenComparator(a); + ancestorStart= baseDiff.fAncestorPos.getOffset(); + } + + int rightStart= baseDiff.fRightPos.getOffset(); + ITokenComparator sm= createTokenComparator(d); + + int leftStart= baseDiff.fLeftPos.getOffset(); + ITokenComparator sy= createTokenComparator(s); + + RangeDifference[] r= RangeDifferencer.findRanges(sa, sy, sm); + for (int i= 0; i < r.length; i++) { + RangeDifference es= r[i]; + // determine range of diffs in one line + int start= i; + int leftLine= -1; + int rightLine= -1; + try { + leftLine= leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart())); + rightLine= rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart())); + } catch (BadLocationException e) { + // silently ignored + } + i++; + for (; i < r.length; i++) { + es= r[i]; + try { + if (leftLine != leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart()))) + break; + if (rightLine != rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart()))) + break; + } catch (BadLocationException e) { + // silently ignored + } + } + int end= i; + + // find first diff from left + RangeDifference first= null; + for (int ii= start; ii < end; ii++) { + es= r[ii]; + if (useChange(es.kind())) { + first= es; + break; + } + } + + // find first diff from mine + RangeDifference last= null; + for (int ii= end-1; ii >= start; ii--) { + es= r[ii]; + if (useChange(es.kind())) { + last= es; + break; + } + } + + if (first != null && last != null) { + + int ancestorStart2= 0; + int ancestorEnd2= 0; + if (ancestorDoc != null) { + ancestorStart2= ancestorStart+sa.getTokenStart(first.ancestorStart()); + ancestorEnd2= ancestorStart+getTokenEnd(sa, last.ancestorStart(), last.ancestorLength()); + } + + int leftStart2= leftStart+sy.getTokenStart(first.leftStart()); + int leftEnd2= leftStart+getTokenEnd(sy, last.leftStart(), last.leftLength()); + + int rightStart2= rightStart+sm.getTokenStart(first.rightStart()); + int rightEnd2= rightStart+getTokenEnd(sm, last.rightStart(), last.rightLength()); + Diff diff= new Diff(baseDiff, first.kind(), + ancestorDoc, null, ancestorStart2, ancestorEnd2, + leftDoc, null, leftStart2, leftEnd2, + rightDoc, null, rightStart2, rightEnd2); + diff.fIsToken= true; + baseDiff.add(diff); + } + } + } + + /* + * Performs a token based 3-way diff on the character range specified by the given baseDiff. + */ + private void simpleTokenDiff(final Diff baseDiff, + IDocument ancestorDoc, String a, + IDocument rightDoc, String d, + IDocument leftDoc, String s) { + + int ancestorStart= 0; + ITokenComparator sa= null; + if (ancestorDoc != null) { + ancestorStart= baseDiff.fAncestorPos.getOffset(); + sa= createTokenComparator(a); + } + + int rightStart= baseDiff.fRightPos.getOffset(); + ITokenComparator sm= createTokenComparator(d); + + int leftStart= baseDiff.fLeftPos.getOffset(); + ITokenComparator sy= createTokenComparator(s); + + RangeDifference[] e= RangeDifferencer.findRanges(sa, sy, sm); + for (int i= 0; i < e.length; i++) { + RangeDifference es= e[i]; + int kind= es.kind(); + if (kind != RangeDifference.NOCHANGE) { + + int ancestorStart2= ancestorStart; + int ancestorEnd2= ancestorStart; + if (ancestorDoc != null) { + ancestorStart2 += sa.getTokenStart(es.ancestorStart()); + ancestorEnd2 += getTokenEnd(sa, es.ancestorStart(), es.ancestorLength()); + } + + int leftStart2= leftStart + sy.getTokenStart(es.leftStart()); + int leftEnd2= leftStart + getTokenEnd(sy, es.leftStart(), es.leftLength()); + + int rightStart2= rightStart + sm.getTokenStart(es.rightStart()); + int rightEnd2= rightStart + getTokenEnd(sm, es.rightStart(), es.rightLength()); + + Diff diff= new Diff(baseDiff, kind, + ancestorDoc, null, ancestorStart2, ancestorEnd2, + leftDoc, null, leftStart2, leftEnd2, + rightDoc, null, rightStart2, rightEnd2); + + // ensure that token diff is smaller than basediff + int leftS= baseDiff.fLeftPos.offset; + int leftE= baseDiff.fLeftPos.offset+baseDiff.fLeftPos.length; + int rightS= baseDiff.fRightPos.offset; + int rightE= baseDiff.fRightPos.offset+baseDiff.fRightPos.length; + if (leftS != leftStart2 || leftE != leftEnd2 || + rightS != rightStart2 || rightE != rightEnd2) { + diff.fIsToken= true; + // add to base Diff + baseDiff.add(diff); + } + } + } + } + + private ITokenComparator createTokenComparator(String s) { + return fInput.createTokenComparator(s); + } + + private static int maxWork(IRangeComparator a, IRangeComparator l, IRangeComparator r) { + int ln= l.getRangeCount(); + int rn= r.getRangeCount(); + if (a != null) { + int an= a.getRangeCount(); + return (2 * Math.max(an, ln)) + (2 * Math.max(an, rn)); + } + return 2 * Math.max(ln, rn); + } + + private void resetPositions(IDocument doc) { + if (doc == null) + return; + try { + doc.removePositionCategory(DIFF_RANGE_CATEGORY); + } catch (BadPositionCategoryException e) { + // Ignore + } + doc.addPositionCategory(DIFF_RANGE_CATEGORY); + } + + /* + * Returns the start line and the number of lines which correspond to the given position. + * Starting line number is 0 based. + */ + protected Point getLineRange(IDocument doc, Position p, Point region) { + + if (p == null || doc == null) { + region.x= 0; + region.y= 0; + return region; + } + + int start= p.getOffset(); + int length= p.getLength(); + + int startLine= 0; + try { + startLine= doc.getLineOfOffset(start); + } catch (BadLocationException e) { + // silently ignored + } + + int lineCount= 0; + + if (length == 0) { +// // if range length is 0 and if range starts a new line +// try { +// if (start == doc.getLineStartOffset(startLine)) { +// lines--; +// } +// } catch (BadLocationException e) { +// lines--; +// } + + } else { + int endLine= 0; + try { + endLine= doc.getLineOfOffset(start + length - 1); // why -1? + } catch (BadLocationException e) { + // silently ignored + } + lineCount= endLine-startLine+1; + } + + region.x= startLine; + region.y= lineCount; + return region; + } + + public Diff findDiff(Position p, boolean left) { + for (Iterator iterator = fAllDiffs.iterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + Position diffPos; + if (left) { + diffPos = diff.fLeftPos; + } else { + diffPos = diff.fRightPos; + } + // If the element falls within a diff, highlight that diff + if (diffPos.offset + diffPos.length >= p.offset && diff.fDirection != RangeDifference.NOCHANGE) + return diff; + // Otherwise, highlight the first diff after the elements position + if (diffPos.offset >= p.offset) + return diff; + } + return null; + } + + public void reset() { + fChangeDiffs= null; + fAllDiffs= null; + } + + /** + * Returns the virtual position for the given view position. + * @param contributor + * @param vpos + * @return the virtual position for the given view position + */ + public int realToVirtualPosition(char contributor, int vpos) { + + if (fAllDiffs == null) + return vpos; + + int viewPos= 0; // real view position + int virtualPos= 0; // virtual position + Point region= new Point(0, 0); + + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + Position pos= diff.getPosition(contributor); + getLineRange(getDocument(contributor),pos, region); + int realHeight= region.y; + int virtualHeight= diff.getMaxDiffHeight(); + if (vpos <= viewPos + realHeight) { // OK, found! + vpos-= viewPos; // make relative to this slot + // now scale position within this slot to virtual slot + if (realHeight <= 0) + vpos= 0; + else + vpos= (vpos*virtualHeight)/realHeight; + return virtualPos+vpos; + } + viewPos+= realHeight; + virtualPos+= virtualHeight; + } + return virtualPos; + } + + /** + * maps given virtual position into a real view position of this view. + * @param contributor + * @param v + * @return the real view position + */ + public int virtualToRealPosition(char contributor, int v) { + + if (fAllDiffs == null) + return v; + + int virtualPos= 0; + int viewPos= 0; + Point region= new Point(0, 0); + + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + Position pos= diff.getPosition(contributor); + int viewHeight= getLineRange(getDocument(contributor), pos, region).y; + int virtualHeight= diff.getMaxDiffHeight(); + if (v < (virtualPos + virtualHeight)) { + v-= virtualPos; // make relative to this slot + if (viewHeight <= 0) { + v= 0; + } else { + v= (int) (v * ((double)viewHeight/virtualHeight)); + } + return viewPos+v; + } + virtualPos+= virtualHeight; + viewPos+= viewHeight; + } + return viewPos; + } + + /* + * Calculates virtual height (in lines) of views by adding the maximum of corresponding diffs. + */ + public int getVirtualHeight() { + int h= 1; + if (fAllDiffs != null) { + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + h+= diff.getMaxDiffHeight(); + } + } + return h; + } + + /* + * Calculates height (in lines) of right view by adding the height of the right diffs. + */ + public int getRightHeight() { + int h= 1; + if (fAllDiffs != null) { + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + h+= diff.getRightHeight(); + } + } + return h; + } + + public int findInsertionPoint(Diff diff, char type) { + if (diff != null) { + switch (type) { + case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR: + if (diff.fAncestorPos != null) + return diff.fAncestorPos.offset; + break; + case MergeViewerContentProvider.LEFT_CONTRIBUTOR: + if (diff.fLeftPos != null) + return diff.fLeftPos.offset; + break; + case MergeViewerContentProvider.RIGHT_CONTRIBUTOR: + if (diff.fRightPos != null) + return diff.fRightPos.offset; + break; + } + } + return 0; + } + + public Diff[] getChangeDiffs(char contributor, IRegion region) { + if (fChangeDiffs == null) + return new Diff[0]; + List intersectingDiffs = new ArrayList(); + for (Iterator iterator = fChangeDiffs.iterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + Diff[] changeDiffs = diff.getChangeDiffs(contributor, region); + for (int i = 0; i < changeDiffs.length; i++) { + Diff changeDiff = changeDiffs[i]; + intersectingDiffs.add(changeDiff); + } + } + return (Diff[]) intersectingDiffs.toArray(new Diff[intersectingDiffs.size()]); + } + + public Diff findDiff(int viewportHeight, boolean synchronizedScrolling, Point size, int my) { + int virtualHeight= synchronizedScrolling ? getVirtualHeight() : getRightHeight(); + if (virtualHeight < viewportHeight) + return null; + + int yy, hh; + int y= 0; + if (fAllDiffs != null) { + Iterator e= fAllDiffs.iterator(); + while (e.hasNext()) { + Diff diff= (Diff) e.next(); + int h= synchronizedScrolling ? diff.getMaxDiffHeight() + : diff.getRightHeight(); + if (useChange(diff.getKind()) && !diff.fIsWhitespace) { + + yy= (y*size.y)/virtualHeight; + hh= (h*size.y)/virtualHeight; + if (hh < 3) + hh= 3; + + if (my >= yy && my < yy+hh) + return diff; + } + y+= h; + } + } + return null; + } + + public boolean hasChanges() { + return fChangeDiffs != null && !fChangeDiffs.isEmpty(); + } + + public Iterator changesIterator() { + if (fChangeDiffs == null) + return new ArrayList().iterator(); + return fChangeDiffs.iterator(); + } + + public Iterator rangesIterator() { + if (fAllDiffs == null) + return new ArrayList().iterator(); + return fAllDiffs.iterator(); + } + + public boolean isFirstChildDiff(char contributor, int startOffset, + Diff diff) { + if (!diff.hasChildren()) + return false; + Diff d = (Diff)diff.fDiffs.get(0); + Position p= d.getPosition(contributor); + return (p.getOffset() >= startOffset); + } + + public Diff getWrappedDiff(Diff diff, boolean down) { + if (fChangeDiffs != null && fChangeDiffs.size() > 0) { + if (down) + return (Diff) fChangeDiffs.get(0); + return (Diff) fChangeDiffs.get(fChangeDiffs.size()-1); + } + return null; + } + + /* + * Copy the contents of the given diff from one side to the other but + * doesn't reveal anything. + * Returns true if copy was successful. + */ + public boolean copy(Diff diff, boolean leftToRight) { + + if (diff != null && !diff.isResolved()) { + Position fromPos= null; + Position toPos= null; + IDocument fromDoc= null; + IDocument toDoc= null; + + if (leftToRight) { + fromPos= diff.getPosition(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + toPos= diff.getPosition(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + fromDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + toDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + } else { + fromPos= diff.getPosition(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + toPos= diff.getPosition(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + fromDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR); + toDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR); + } + + if (fromDoc != null) { + + int fromStart= fromPos.getOffset(); + int fromLen= fromPos.getLength(); + + int toStart= toPos.getOffset(); + int toLen= toPos.getLength(); + + try { + String s= null; + + switch (diff.getKind()) { + case RangeDifference.RIGHT: + case RangeDifference.LEFT: + s= fromDoc.get(fromStart, fromLen); + break; + case RangeDifference.ANCESTOR: + break; + case RangeDifference.CONFLICT: + if (APPEND_CONFLICT) { + s= toDoc.get(toStart, toLen); + String ls = TextUtilities.getDefaultLineDelimiter(toDoc); + if (!s.endsWith(ls)) + s += ls; + s+= fromDoc.get(fromStart, fromLen); + } else + s= fromDoc.get(fromStart, fromLen); + break; + } + if (s != null) { + toDoc.replace(toStart, toLen, s); + toPos.setOffset(toStart); + toPos.setLength(s.length()); + } + + } catch (BadLocationException e) { + // silently ignored + } + } + + diff.setResolved(true); + return true; + } + return false; + } + + public int changesCount() { + if (fChangeDiffs == null) + return 0; + return fChangeDiffs.size(); + } + + public Diff findDiff(char contributor, int rangeStart, int rangeEnd) { + if (hasChanges()) { + for (Iterator iterator = changesIterator(); iterator.hasNext();) { + Diff diff = (Diff) iterator.next(); + if (diff.isDeleted() || diff.getKind() == RangeDifference.NOCHANGE) + continue; + if (diff.overlaps(contributor, rangeStart, rangeEnd, getDocument(contributor).getLength())) + return diff; + } + } + return null; + } + + public Diff findDiff(char contributor, Position range) { + int start= range.getOffset(); + int end= start + range.getLength(); + return findDiff(contributor, start, end); + } + + public Diff findNext(char contributor, int start, int end, boolean deep) { + return findNext(contributor, fChangeDiffs, start, end, deep); + } + + private Diff findNext(char contributor, List v, int start, int end, boolean deep) { + if (v == null) + return null; + for (int i= 0; i < v.size(); i++) { + Diff diff= (Diff) v.get(i); + Position p= diff.getPosition(contributor); + if (p != null) { + int startOffset= p.getOffset(); + if (end < startOffset) // <= + return diff; + if (deep && diff.hasChildren()) { + Diff d= null; + int endOffset= startOffset + p.getLength(); + if (start == startOffset && (end == endOffset || end == endOffset-1)) { + d= findNext(contributor, diff.fDiffs, start-1, start-1, deep); + } else if (end < endOffset) { + d= findNext(contributor, diff.fDiffs, start, end, deep); + } + if (d != null) + return d; + } + } + } + return null; + } + + public Diff findPrev(char contributor, int start, int end, boolean deep) { + return findPrev(contributor, fChangeDiffs, start, end, deep); + } + + private Diff findPrev(char contributor, List v, int start, int end, boolean deep) { + if (v == null) + return null; + for (int i= v.size()-1; i >= 0; i--) { + Diff diff= (Diff) v.get(i); + Position p= diff.getPosition(contributor); + if (p != null) { + int startOffset= p.getOffset(); + int endOffset= startOffset + p.getLength(); + if (start > endOffset) { + if (deep && diff.hasChildren()) { + // If we are going deep, find the last change in the diff + return findPrev(contributor, diff.fDiffs, end, end, deep); + } + return diff; + } + if (deep && diff.hasChildren()) { + Diff d= null; + if (start == startOffset && end == endOffset) { + // A whole diff is selected so we'll fall through + // and go the the last change in the previous diff + } else if (start >= startOffset) { + // If we are at or before the first diff, select the + // entire diff so next and previous are symmetrical + if (isFirstChildDiff(contributor, startOffset, diff)) { + return diff; + } + d= findPrev(contributor, diff.fDiffs, start, end, deep); + } + if (d != null) + return d; + } + } + } + return null; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java new file mode 100644 index 000000000..4b4891637 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/LineComparator.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.merge; + +import java.io.*; +import java.util.ArrayList; +import org.eclipse.compare.rangedifferencer.IRangeComparator; + +/** + * This implementation of IRangeComparator breaks an input stream into lines. + */ +class LineComparator implements IRangeComparator { + + private String[] fLines; + + public LineComparator(InputStream is, String encoding) throws IOException { + + BufferedReader br = new BufferedReader(new InputStreamReader(is, encoding)); + String line; + ArrayList ar = new ArrayList(); + while ((line = br.readLine()) != null) { + ar.add(line); + } + // It is the responsibility of the caller to close the stream + fLines = (String[]) ar.toArray(new String[ar.size()]); + } + + String getLine(int ix) { + return fLines[ix]; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#getRangeCount() + */ + public int getRangeCount() { + return fLines.length; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#rangesEqual(int, org.eclipse.compare.rangedifferencer.IRangeComparator, int) + */ + public boolean rangesEqual(int thisIndex, IRangeComparator other, + int otherIndex) { + String s1 = fLines[thisIndex]; + String s2 = ((LineComparator) other).fLines[otherIndex]; + return s1.equals(s2); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.rangedifferencer.IRangeComparator#skipRangeComparison(int, int, org.eclipse.compare.rangedifferencer.IRangeComparator) + */ + public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) { + return false; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java new file mode 100644 index 000000000..f3fa54dea --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.merge; + +import org.eclipse.osgi.util.NLS; + +public final class MergeMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.merge.MergeMessages";//$NON-NLS-1$ + + private MergeMessages() { + // Do not instantiate + } + + public static String TextAutoMerge_inputEncodingError; + public static String TextAutoMerge_outputEncodingError; + public static String TextAutoMerge_outputIOError; + public static String TextAutoMerge_conflict; + + static { + NLS.initializeMessages(BUNDLE_NAME, MergeMessages.class); + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties new file mode 100644 index 000000000..a548ea8e6 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/MergeMessages.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2004, 2006 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### + +TextAutoMerge_inputEncodingError= Unsupported encoding for input streams +TextAutoMerge_outputEncodingError= Unsupported encoding for output streams +TextAutoMerge_outputIOError= IO error on writing +TextAutoMerge_conflict= Conflict: cannot auto merge diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java new file mode 100644 index 000000000..2aaecd12b --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/TextStreamMerger.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2003, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.merge; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.eclipse.compare.*; +import org.eclipse.compare.rangedifferencer.RangeDifference; +import org.eclipse.compare.rangedifferencer.RangeDifferencer; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * A simple merger for streams containing text lines. + */ +public class TextStreamMerger implements IStreamMerger { + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.internal.merge.IAutoMerger#automerge(java.io.OutputStream, + * org.eclipse.core.resources.IEncodedStorage, + * org.eclipse.core.resources.IEncodedStorage, + * org.eclipse.core.resources.IEncodedStorage, + * org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus merge(OutputStream output, String outputEncoding, InputStream ancestor, String ancestorEncoding, InputStream target, String targetEncoding, InputStream other, String otherEncoding, IProgressMonitor monitor) { + + LineComparator a, t, o; + + try { + a= new LineComparator(ancestor, ancestorEncoding); + t= new LineComparator(target, targetEncoding); + o= new LineComparator(other, otherEncoding); + } catch (UnsupportedEncodingException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, MergeMessages.TextAutoMerge_inputEncodingError, e); + } catch (IOException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, e.getMessage(), e); + } + + try { + String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$ + if (lineSeparator == null) + lineSeparator= "\n"; //$NON-NLS-1$ + + RangeDifference[] diffs= RangeDifferencer.findRanges(monitor, a, t, o); + + for (int i= 0; i < diffs.length; i++) { + RangeDifference rd= diffs[i]; + switch (rd.kind()) { + case RangeDifference.ANCESTOR: // pseudo conflict + case RangeDifference.NOCHANGE: + case RangeDifference.RIGHT: + for (int j= rd.rightStart(); j < rd.rightEnd(); j++) { + String s= o.getLine(j); + output.write(s.getBytes(outputEncoding)); + output.write(lineSeparator.getBytes(outputEncoding)); + } + break; + + case RangeDifference.LEFT: + for (int j= rd.leftStart(); j < rd.leftEnd(); j++) { + String s= t.getLine(j); + output.write(s.getBytes(outputEncoding)); + output.write(lineSeparator.getBytes(outputEncoding)); + } + break; + + case RangeDifference.CONFLICT: + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, CONFLICT, MergeMessages.TextAutoMerge_conflict, null); + + default: + break; + } + } + + } catch (UnsupportedEncodingException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, MergeMessages.TextAutoMerge_outputEncodingError, e); + } catch (IOException e) { + return new Status(IStatus.ERROR, CompareUI.PLUGIN_ID, 1, MergeMessages.TextAutoMerge_outputIOError, e); + } + + return Status.OK_STATUS; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java new file mode 100644 index 000000000..1a8e3d44c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DecoratorOverlayIcon.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.util.Arrays; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.swt.graphics.*; + +/** + * An DecoratorOverlayIcon consists of a main icon and several adornments. + * Copied until bug 164394 is resolved. + */ +class DecoratorOverlayIcon extends CompositeImageDescriptor { + // the base image + private Image base; + + // the overlay images + private ImageDescriptor[] overlays; + + // the size + private Point size; + + + /** + * OverlayIcon constructor. + * + * @param baseImage the base image + * @param overlaysArray the overlay images + * @param sizeValue the size + */ + public DecoratorOverlayIcon(Image baseImage, + ImageDescriptor[] overlaysArray, Point sizeValue) { + this.base = baseImage; + this.overlays = overlaysArray; + this.size = sizeValue; + } + + /** + * Draw the overlays for the receiver. + * @param overlaysArray the overlay images + */ + protected void drawOverlays(ImageDescriptor[] overlaysArray) { + + for (int i = 0; i < overlays.length; i++) { + ImageDescriptor overlay = overlaysArray[i]; + if (overlay == null) { + continue; + } + ImageData overlayData = overlay.getImageData(); + //Use the missing descriptor if it is not there. + if (overlayData == null) { + overlayData = ImageDescriptor.getMissingImageDescriptor() + .getImageData(); + } + switch (i) { + case IDecoration.TOP_LEFT: + drawImage(overlayData, 0, 0); + break; + case IDecoration.TOP_RIGHT: + drawImage(overlayData, size.x - overlayData.width, 0); + break; + case IDecoration.BOTTOM_LEFT: + drawImage(overlayData, 0, size.y - overlayData.height); + break; + case IDecoration.BOTTOM_RIGHT: + drawImage(overlayData, size.x - overlayData.width, size.y + - overlayData.height); + break; + } + } + } + + public boolean equals(Object o) { + if (!(o instanceof DecoratorOverlayIcon)) { + return false; + } + DecoratorOverlayIcon other = (DecoratorOverlayIcon) o; + return base.equals(other.base) + && Arrays.equals(overlays, other.overlays); + } + + public int hashCode() { + int code = base.hashCode(); + for (int i = 0; i < overlays.length; i++) { + if (overlays[i] != null) { + code ^= overlays[i].hashCode(); + } + } + return code; + } + + protected void drawCompositeImage(int width, int height) { + ImageDescriptor underlay = overlays[IDecoration.UNDERLAY]; + if (underlay != null) { + drawImage(underlay.getImageData(), 0, 0); + } + drawImage(base.getImageData(), 0, 0); + drawOverlays(overlays); + } + + protected Point getSize() { + return size; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.resource.CompositeImageDescriptor#getTransparentPixel() + */ + protected int getTransparentPixel() { + return base.getImageData().transparentPixel; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java new file mode 100644 index 000000000..5a0134edd --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/DiffViewerComparator.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.util.Comparator; +import java.util.regex.Pattern; + +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DocumentRangeNode; +import org.eclipse.jface.util.Policy; +import org.eclipse.jface.viewers.ViewerSorter; + +public class DiffViewerComparator extends ViewerSorter { + + public boolean isSorterProperty(Object element, Object property) { + return false; + } + + public int category(Object node) { + if (node instanceof DiffNode) { + Object o= ((DiffNode) node).getId(); + if (o instanceof DocumentRangeNode) + return ((DocumentRangeNode) o).getTypeCode(); + } + return 0; + } + + protected Comparator getComparator() { + return new Comparator() { + public int compare(Object arg0, Object arg1) { + String label0 = arg0 == null ? "" : arg0.toString(); //$NON-NLS-1$ + String label1 = arg1 == null ? "" : arg1.toString(); //$NON-NLS-1$ + + // see org.eclipse.compare.internal.patch.Hunk.getDescription() + String pattern = "\\d+,\\d+ -> \\d+,\\d+.*"; //$NON-NLS-1$ + + if (Pattern.matches(pattern, label0) + && Pattern.matches(pattern, label1)) { + int oldStart0 = Integer.parseInt(label0.split(",")[0]); //$NON-NLS-1$ + int oldStart1 = Integer.parseInt(label1.split(",")[0]); //$NON-NLS-1$ + + return oldStart0 - oldStart1; + } + return Policy.getComparator().compare(arg0, arg1); + } + }; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java new file mode 100644 index 000000000..5841f269e --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/FilePatch.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.patch.IFilePatch; +import org.eclipse.compare.patch.IFilePatchResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +public class FilePatch extends FilePatch2 implements IFilePatch { + + public FilePatch(IPath oldPath, long oldDate, IPath newPath, + long newDate) { + super(oldPath, oldDate, newPath, newDate); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.compare.patch.IFilePatch#apply(org.eclipse.core.resources + * .IStorage, org.eclipse.compare.patch.PatchConfiguration, + * org.eclipse.core.runtime.IProgressMonitor) + */ + public IFilePatchResult apply(IStorage content, + PatchConfiguration configuration, IProgressMonitor monitor) { + return apply(content != null ? Utilities.getReaderCreator(content) + : null, configuration, monitor); + } + + protected FilePatch2 create(IPath oldPath, long oldDate, IPath newPath, + long newDate) { + return new FilePatch(oldPath, oldDate, newPath, newDate); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java new file mode 100644 index 000000000..1fa0f3a79 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkDiffNode.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.core.resources.IResource; + +public class HunkDiffNode extends PatchDiffNode { + + private final HunkResult result; + + public static HunkDiffNode createDiffNode(PatchFileDiffNode parent, HunkResult result, boolean fullContext) { + return createDiffNode(parent, result, fullContext, fullContext, fullContext); + } + + public static HunkDiffNode createDiffNode(PatchFileDiffNode parent, HunkResult result, boolean ancestorFullContext, boolean leftFullContext, boolean rightFullContext) { + return new HunkDiffNode(result, parent, Differencer.CHANGE, getAncestorElement(result, ancestorFullContext), getLeftElement(result, leftFullContext), getRightElement(result, rightFullContext)); + } + + public static ITypedElement getRightElement(HunkResult result, boolean fullContext) { + return new HunkTypedElement(result, true /* isResult */, fullContext); + } + + private static ITypedElement getLeftElement(HunkResult result, + boolean fullContext) { + if (fullContext && !result.isOK()) + return new UnmatchedHunkTypedElement(result); + return new HunkTypedElement(result, false /* before state */, fullContext); + } + + public static ITypedElement getAncestorElement(HunkResult result, boolean fullContext) { + if (!fullContext && result.isOK()) { + return new HunkTypedElement(result, false /* before state */, fullContext); + } + if (!fullContext) { + // Don't provide an ancestor if the hunk didn't match or we're not doing fullContext + return null; + } + // Make the ancestor the same as the left so we have an incoming change + return new HunkTypedElement(result, false /* before state */, result.isOK()); + } + + public HunkDiffNode(HunkResult result, PatchFileDiffNode parent, int kind, ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(result.getHunk(), parent, kind, ancestor, left, right); + this.result = result; + } + + public HunkResult getHunkResult() { + return result; + } + + protected PatchConfiguration getConfiguration() { + return result.getDiffResult().getConfiguration(); + } + + public boolean isManuallyMerged() { + Object left = getLeft(); + if (left instanceof UnmatchedHunkTypedElement) { + UnmatchedHunkTypedElement element = (UnmatchedHunkTypedElement) left; + return element.isManuallyMerged(); + } + return false; + } + + public boolean isFuzzUsed() { + return result.getFuzz() > 0; + } + + public boolean isAllContextIgnored() { + int fuzz = result.getFuzz(); + if (fuzz > 0) { + String[] lines = result.getHunk().getLines(); + int contextLines = 0; + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + char c = line.charAt(0); + if (c == ' ') { + contextLines++; + } else { + if (contextLines > 0 && fuzz >= contextLines) { + return true; + } + contextLines = 0; + } + } + if (contextLines > 0 && fuzz >= contextLines) { + return true; + } + + } + return false; + } + + public IResource getResource() { + return ((PatchFileDiffNode)getParent()).getResource(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java new file mode 100644 index 000000000..4ff4ab349 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/HunkTypedElement.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.InputStream; + +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.DiffImageDescriptor; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.graphics.Image; + +public class HunkTypedElement implements ITypedElement, IEncodedStreamContentAccessor, IAdaptable { + + private final HunkResult fHunkResult; + private final boolean fIsAfterState; + private final boolean fFullContext; + + public HunkTypedElement(HunkResult result, boolean isAfterState, boolean fullContext) { + this.fHunkResult = result; + this.fIsAfterState = isAfterState; + this.fFullContext = fullContext; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getImage() + */ + public Image getImage() { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(fHunkResult.getDiffResult().getConfiguration()); + ImageDescriptor imageDesc = CompareUIPlugin.getImageDescriptor(ICompareUIConstants.HUNK_OBJ); + Image image = imageCache.createImage(imageDesc); + if (!fHunkResult.isOK()) { + return getHunkErrorImage(image, imageCache, true); + } else if (fHunkResult.getFuzz() > 0) { + return getHunkOverlayImage(image, imageCache, ICompareUIConstants.WARNING_OVERLAY, true); + } + return image; + } + + public static Image getHunkErrorImage(Image baseImage, LocalResourceManager imageCache, boolean onLeft) { + return getHunkOverlayImage(baseImage, imageCache, ICompareUIConstants.ERROR_OVERLAY, onLeft); + } + + private static Image getHunkOverlayImage(Image baseImage, LocalResourceManager imageCache, String path, boolean onLeft) { + ImageDescriptor desc = new DiffImageDescriptor(baseImage, CompareUIPlugin.getImageDescriptor(path), ICompareUIConstants.COMPARE_IMAGE_WIDTH, onLeft); + Image image = imageCache.createImage(desc); + return image; + } + + public boolean isManuallyMerged() { + return getPatcher().isManuallyMerged(getHunkResult().getHunk()); + } + + private Patcher getPatcher() { + return Patcher.getPatcher(fHunkResult.getDiffResult().getConfiguration()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getName() + */ + public String getName() { + return fHunkResult.getHunk().getLabel(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.ITypedElement#getType() + */ + public String getType() { + return fHunkResult.getDiffResult().getDiff().getTargetPath(fHunkResult.getDiffResult().getConfiguration()).getFileExtension(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IStreamContentAccessor#getContents() + */ + public InputStream getContents() throws CoreException { + String contents = fHunkResult.getContents(fIsAfterState, fFullContext); + return FileDiffResult.asInputStream(contents, fHunkResult.getCharset()); + } + + public String getCharset() throws CoreException { + return fHunkResult.getCharset(); + } + + public HunkResult getHunkResult() { + return fHunkResult; + } + + public Object getAdapter(Class adapter) { + if (adapter == IHunk.class) + return fHunkResult; + if (adapter == HunkResult.class) + return fHunkResult; + return Platform.getAdapterManager().getAdapter(this, adapter); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java new file mode 100644 index 000000000..7248fccb3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/InputPatchPage.java @@ -0,0 +1,990 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Sebastian Davids <sdavids@gmx.de> - layout tweaks + * Matt McCutchen <hashproduct+eclipse@gmail.com> - Bug 180358 [Apply Patch] Cursor jumps to beginning of filename field on keystroke + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.compare.internal.ICompareContextIds; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; + +import com.ibm.icu.text.MessageFormat; + +public class InputPatchPage extends WizardPage { + + // constants + protected static final int SIZING_TEXT_FIELD_WIDTH= 250; + protected static final int COMBO_HISTORY_LENGTH= 5; + + // dialog store id constants + private final static String PAGE_NAME= "PatchWizardPage1"; //$NON-NLS-1$ + private final static String STORE_PATCH_FILES_ID= PAGE_NAME+".PATCH_FILES"; //$NON-NLS-1$ + private final static String STORE_PATCH_URLS_ID= PAGE_NAME+".PATCH_URLS"; //$NON-NLS-1$ + private final static String STORE_INPUT_METHOD_ID= PAGE_NAME+".INPUT_METHOD"; //$NON-NLS-1$ + private final static String STORE_WORKSPACE_PATH_ID= PAGE_NAME+".WORKSPACE_PATH"; //$NON-NLS-1$ + + //patch input constants + protected final static int CLIPBOARD= 1; + protected final static int FILE= 2; + protected final static int WORKSPACE= 3; + protected final static int URL= 4; + + protected final static String INPUTPATCHPAGE_NAME= "InputPatchPage"; //$NON-NLS-1$ + + static final char SEPARATOR= System.getProperty("file.separator").charAt(0); //$NON-NLS-1$ + + private boolean fShowError= false; + private String fPatchSource; + private boolean fPatchRead= false; + private PatchWizard fPatchWizard; + private ActivationListener fActivationListener= new ActivationListener(); + + // SWT widgets + private Button fUseClipboardButton; + private Combo fPatchFileNameField; + private Button fPatchFileBrowseButton; + private Button fUsePatchFileButton; + private Button fUseWorkspaceButton; + private Button fUseURLButton; + private Combo fPatchURLField; + private Label fWorkspaceSelectLabel; + private TreeViewer fTreeViewer; + + class ActivationListener extends ShellAdapter { + public void shellActivated(ShellEvent e) { + // allow error messages if the selected input actually has something selected in it + fShowError=true; + switch(getInputMethod()) { + case FILE: + fShowError= (fPatchFileNameField.getText() != ""); //$NON-NLS-1$ + break; + case URL: + fShowError = (fPatchURLField.getText() != ""); //$NON-NLS-1$ + break; + case WORKSPACE: + fShowError= (!fTreeViewer.getSelection().isEmpty()); + break; + } + updateWidgetEnablements(); + } + } + + public InputPatchPage(PatchWizard pw) { + super(INPUTPATCHPAGE_NAME, PatchMessages.InputPatchPage_title, null); + fPatchWizard= pw; + setMessage(PatchMessages.InputPatchPage_message); + } + + /* + * Get a path from the supplied text widget. + * @return org.eclipse.core.runtime.IPath + */ + protected IPath getPathFromText(Text textField) { + return (new Path(textField.getText())).makeAbsolute(); + } + + /* package */ String getPatchName() { + if (getInputMethod() == CLIPBOARD) + return PatchMessages.InputPatchPage_Clipboard; + return getPatchFilePath(); + } + + public void createControl(Composite parent) { + + Composite composite= new Composite(parent, SWT.NULL); + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); + setControl(composite); + + initializeDialogUnits(parent); + + buildPatchFileGroup(composite); + + // by default, whatever was used last was selected or + // default to File if nothing has been selected + restoreWidgetValues(); + + // see if there are any better options presently selected (i.e workspace + // or clipboard or URL from clipboard) + adjustToCurrentTarget(); + + // No error for dialog opening + fShowError= false; + clearErrorMessage(); + updateWidgetEnablements(); + + Shell shell= getShell(); + shell.addShellListener(fActivationListener); + + Dialog.applyDialogFont(composite); + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.PATCH_INPUT_WIZARD_PAGE); + } + + /** + * Returns the next page depending on what type of patch is being applied: + * i) If the patch is a Workspace patch then it will proceed right to the PreviewPatchPage + * ii) If the patch is a single project patch then it will proceed to the PatchTargetPage, which + * allows the user to specify where to root the patch + * @return PreviewPatchPage if multi-project patch, PatchTargetPage if single project patch + */ + public IWizardPage getNextPage() { + + WorkspacePatcher patcher= ((PatchWizard) getWizard()).getPatcher(); + + // Read in the patch + readInPatch(); + + FilePatch2[] diffs= patcher.getDiffs(); + if (diffs == null || diffs.length == 0) { + String format= PatchMessages.InputPatchPage_NoDiffsFound_format; + String message= MessageFormat.format(format, new String[] { fPatchSource }); + MessageDialog.openInformation(null, + PatchMessages.InputPatchPage_PatchErrorDialog_title, message); + return this; + } + + // guess prefix count + int guess= 0; // guessPrefix(diffs); + patcher.setStripPrefixSegments(guess); + + // If this is a workspace patch we don't need to set a target as the targets will be figured out from + // all of the projects that make up the patch and continue on to final preview page + // else go on to target selection page + if (patcher.isWorkspacePatch()) { + // skip 'Patch Target' page + IWizardPage page = super.getNextPage(); + if (page.getName().equals(PatchTargetPage.PATCHTARGETPAGE_NAME)) + return page.getNextPage(); + } + + return super.getNextPage(); + } + + /* + * Reads in the patch contents + */ + public void readInPatch(){ + WorkspacePatcher patcher= ((PatchWizard) getWizard()).getPatcher(); + // Create a reader for the input + Reader reader= null; + try { + int inputMethod= getInputMethod(); + if (inputMethod == CLIPBOARD) { + Control c= getControl(); + if (c != null) { + Clipboard clipboard= new Clipboard(c.getDisplay()); + Object o= clipboard.getContents(TextTransfer.getInstance()); + clipboard.dispose(); + if (o instanceof String) + reader= new StringReader((String)o); + } + fPatchSource= PatchMessages.InputPatchPage_Clipboard_title; + } else if (inputMethod==FILE) { + String patchFilePath= getPatchFilePath(); + if (patchFilePath != null) { + try { + reader= new FileReader(patchFilePath); + } catch (FileNotFoundException ex) { + MessageDialog.openError(null, + PatchMessages.InputPatchPage_PatchErrorDialog_title, + PatchMessages.InputPatchPage_PatchFileNotFound_message); + } + } + fPatchSource= PatchMessages.InputPatchPage_PatchFile_title; + } else if (inputMethod==URL) { + String patchFileURL = fPatchURLField.getText(); + if (patchFileURL != null) { + try { + String contents = Utilities.getURLContents(new URL( + patchFileURL), getContainer()); + if (contents != null) + reader = new StringReader(contents); + } catch (MalformedURLException e) { + // ignore as we tested it with modify listener on combo + } catch (InvocationTargetException e) { // ignore + } catch (OperationCanceledException e) { // ignore + } catch (InterruptedException e) { // ignore + } + } + fPatchSource= PatchMessages.InputPatchPage_URL_title; + } else if (inputMethod==WORKSPACE) { + // Get the selected patch file (tree will only allow for one selection) + IResource[] resources= Utilities.getResources(fTreeViewer.getSelection()); + IResource patchFile= resources[0]; + if (patchFile != null) { + try { + reader= new FileReader(patchFile.getLocation().toFile()); + } catch (FileNotFoundException ex) { + MessageDialog.openError(null, PatchMessages.InputPatchPage_PatchErrorDialog_title, PatchMessages.InputPatchPage_PatchFileNotFound_message); + } catch (NullPointerException nex) { + //in case the path doesn't exist (eg. getLocation() returned null) + MessageDialog.openError(null, PatchMessages.InputPatchPage_PatchErrorDialog_title, PatchMessages.InputPatchPage_PatchFileNotFound_message); + } + } + fPatchSource= PatchMessages.InputPatchPage_WorkspacePatch_title; + } + + // parse the input + if (reader != null) { + try { + patcher.parse(new BufferedReader(reader)); + //report back to the patch wizard that the patch has been read in + fPatchWizard.patchReadIn(); + fPatchRead=true; + } catch (Exception ex) { + // Ignore. User will be informed of error since patcher contains no diffs + } + } + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException x) { + // silently ignored + } + } + } + } + + /* (non-JavaDoc) + * Method declared in IWizardPage. + */ + public boolean canFlipToNextPage() { + // we can't call getNextPage to determine if flipping is allowed since computing + // the next page is quite expensive. So we say yes if the page is complete. + return isPageComplete(); + } + + private void setEnablePatchFile(boolean enable) { + fPatchFileNameField.setEnabled(enable); + fPatchFileBrowseButton.setEnabled(enable); + } + + private void setEnableWorkspacePatch(boolean enable) { + fWorkspaceSelectLabel.setEnabled(enable); + fTreeViewer.getTree().setEnabled(enable); + } + + private void setEnableURLPatch(boolean enable) { + fPatchURLField.setEnabled(enable); + } + + /* + * Create the group for selecting the patch file + */ + private void buildPatchFileGroup(Composite parent) { + + final Composite composite= new Composite(parent, SWT.NULL); + GridLayout gridLayout= new GridLayout(); + gridLayout.numColumns= 3; + composite.setLayout(gridLayout); + composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + // 1st row + GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + gd.horizontalSpan= 3; + fUseClipboardButton= new Button(composite, SWT.RADIO); + fUseClipboardButton.setText(PatchMessages.InputPatchPage_UseClipboardButton_text); + fUseClipboardButton.setLayoutData(gd); + + // 2nd row + fUsePatchFileButton= new Button(composite, SWT.RADIO); + fUsePatchFileButton.setText(PatchMessages.InputPatchPage_FileButton_text); + + fPatchFileNameField= new Combo(composite, SWT.BORDER); + gd= new GridData(GridData.FILL_HORIZONTAL); + gd.widthHint= SIZING_TEXT_FIELD_WIDTH; + fPatchFileNameField.setLayoutData(gd); + + fPatchFileBrowseButton= new Button(composite, SWT.PUSH); + fPatchFileBrowseButton.setText(PatchMessages.InputPatchPage_ChooseFileButton_text); + GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL); + int widthHint= convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); + Point minSize= fPatchFileBrowseButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + data.widthHint= Math.max(widthHint, minSize.x); + fPatchFileBrowseButton.setLayoutData(data); + + //3rd row + fUseURLButton = new Button(composite, SWT.RADIO); + fUseURLButton.setText(PatchMessages.InputPatchPage_URLButton_text); + + fPatchURLField = new Combo(composite, SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + fPatchURLField.setLayoutData(gd); + + //4th row + fUseWorkspaceButton= new Button(composite, SWT.RADIO); + fUseWorkspaceButton.setText(PatchMessages.InputPatchPage_UseWorkspaceButton_text); + gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + fUseWorkspaceButton.setLayoutData(gd); + + addWorkspaceControls(parent); + + // Add listeners + fUseClipboardButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (!fUseClipboardButton.getSelection()) + return; + + clearErrorMessage(); + fShowError= true; + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + fPatchRead = false; + } + }); + + fUsePatchFileButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (!fUsePatchFileButton.getSelection()) + return; + //If there is anything typed in at all + clearErrorMessage(); + fShowError= (fPatchFileNameField.getText() != ""); //$NON-NLS-1$ + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + fPatchRead = false; + } + }); + fPatchFileNameField.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + updateWidgetEnablements(); + } + }); + fPatchFileNameField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + clearErrorMessage(); + fShowError= true; + updateWidgetEnablements(); + } + }); + fPatchFileBrowseButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + clearErrorMessage(); + fShowError= true; + handlePatchFileBrowseButtonPressed(); + updateWidgetEnablements(); + } + }); + fUseURLButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + clearErrorMessage(); + fShowError= (fPatchURLField.getText() != ""); //$NON-NLS-1$ + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + } + }); + fPatchURLField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + clearErrorMessage(); + fShowError = true; + updateWidgetEnablements(); + } + }); + fUseWorkspaceButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (!fUseWorkspaceButton.getSelection()) + return; + clearErrorMessage(); + // If there is anything typed in at all + fShowError= (!fTreeViewer.getSelection().isEmpty()); + int state= getInputMethod(); + setEnablePatchFile(state == FILE); + setEnableURLPatch(state == URL); + setEnableWorkspacePatch(state == WORKSPACE); + updateWidgetEnablements(); + fPatchRead = false; + } + }); + + fTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + clearErrorMessage(); + updateWidgetEnablements(); + } + }); + + fTreeViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ISelection selection= event.getSelection(); + if (selection instanceof TreeSelection) { + TreeSelection treeSel= (TreeSelection) selection; + Object res= treeSel.getFirstElement(); + if (res != null) { + if (res instanceof IProject || res instanceof IFolder) { + if (fTreeViewer.getExpandedState(res)) + fTreeViewer.collapseToLevel(res, 1); + else + fTreeViewer.expandToLevel(res, 1); + } else if (res instanceof IFile) + fPatchWizard.showPage(getNextPage()); + } + } + } + }); + } + + private void addWorkspaceControls(Composite composite) { + + Composite newComp= new Composite(composite, SWT.NONE); + GridLayout layout= new GridLayout(1, false); + layout.marginLeft= 16; // align w/ lable of check button + newComp.setLayout(layout); + newComp.setLayoutData(new GridData(GridData.FILL_BOTH)); + + fWorkspaceSelectLabel= new Label(newComp, SWT.LEFT); + fWorkspaceSelectLabel.setText(PatchMessages.InputPatchPage_WorkspaceSelectPatch_text); + + fTreeViewer= new TreeViewer(newComp, SWT.BORDER); + fTreeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + fTreeViewer.setLabelProvider(new WorkbenchLabelProvider()); + fTreeViewer.setContentProvider(new WorkbenchContentProvider()); + fTreeViewer.setComparator(new ResourceComparator(ResourceComparator.NAME)); + fTreeViewer.setInput(ResourcesPlugin.getWorkspace().getRoot()); + } + + + /** + * Updates the enable state of this page's controls. + */ + private void updateWidgetEnablements() { + + String error= null; + + boolean gotPatch= false; + int inputMethod= getInputMethod(); + if (inputMethod==CLIPBOARD) { + Control c= getControl(); + if (c != null) { + Clipboard clipboard= new Clipboard(c.getDisplay()); + Object o= clipboard.getContents(TextTransfer.getInstance()); + clipboard.dispose(); + if (o instanceof String) { + String s= ((String) o).trim(); + if (s.length() > 0) + gotPatch= true; + else + error= PatchMessages.InputPatchPage_ClipboardIsEmpty_message; + } else + error= PatchMessages.InputPatchPage_NoTextInClipboard_message; + } else + error= PatchMessages.InputPatchPage_CouldNotReadClipboard_message; + } else if (inputMethod==FILE) { + String path= fPatchFileNameField.getText(); + if (path != null && path.length() > 0) { + File file= new File(path); + gotPatch= file.exists() && file.isFile() && file.length() > 0; + if (!gotPatch) + error= PatchMessages.InputPatchPage_CannotLocatePatch_message + path; + } else { + error= PatchMessages.InputPatchPage_NoFileName_message; + } + } else if (inputMethod == URL) { + String urlText = fPatchURLField.getText(); + if(urlText != null) { + try { + new URL(urlText); + // Checking the URL is a bit too heavy for each keystroke. + // Let's assume it contains a valid patch. + gotPatch = true; + } catch (MalformedURLException e) { + error= PatchMessages.InputPatchPage_MalformedURL; + } + } else { + error= PatchMessages.InputPatchPage_NoURL; + } + } else if (inputMethod == WORKSPACE) { + //Get the selected patch file (tree will only allow for one selection) + IResource[] resources= Utilities.getResources(fTreeViewer.getSelection()); + if (resources != null && resources.length > 0) { + IResource patchFile= resources[0]; + if (patchFile != null && patchFile.getType() == IResource.FILE) { + IPath location = patchFile.getLocation(); + if (location == null) { + error = PatchMessages.InputPatchPage_PatchFileNotFound_message; + } else { + File actualFile= location.toFile(); + gotPatch= actualFile.exists()&&actualFile.isFile()&&actualFile.length() > 0; + if (!gotPatch) + error= PatchMessages.InputPatchPage_FileSelectedNotPatch_message; + } + } + } else { + error= PatchMessages.InputPatchPage_NoFileName_message; + } + } + + setPageComplete(gotPatch); + + if (fShowError) + setErrorMessage(error); + } + + protected void handlePatchFileBrowseButtonPressed() { + FileDialog dialog= new FileDialog(getShell(), SWT.NONE); + dialog.setText(PatchMessages.InputPatchPage_SelectPatchFileDialog_title); + String patchFilePath= getPatchFilePath(); + if (patchFilePath != null) { + int lastSegment= patchFilePath.lastIndexOf(SEPARATOR); + if (lastSegment > 0) { + patchFilePath= patchFilePath.substring(0, lastSegment); + } + } + dialog.setFilterPath(patchFilePath); + String res= dialog.open(); + if (res == null) + return; + + patchFilePath= dialog.getFileName(); + IPath filterPath= new Path(dialog.getFilterPath()); + IPath path= filterPath.append(patchFilePath).makeAbsolute(); + patchFilePath= path.toOSString(); + //fDialogSettings.put(IUIConstants.DIALOGSTORE_LASTEXTJAR, filterPath.toOSString()); + + fPatchFileNameField.setText(patchFilePath); + //setSourceName(patchFilePath); + } + + /** + * Sets the source name of the import to be the supplied path. + * Adds the name of the path to the list of items in the + * source combo and selects it. + * + * @param path the path to be added + */ + protected void setSourceName(String path) { + + if (path.length() > 0) { + + String[] currentItems= fPatchFileNameField.getItems(); + int selectionIndex= -1; + for (int i= 0; i < currentItems.length; i++) + if (currentItems[i].equals(path)) + selectionIndex= i; + + if (selectionIndex < 0) { // not found in history + int oldLength= currentItems.length; + String[] newItems= new String[oldLength + 1]; + System.arraycopy(currentItems, 0, newItems, 0, oldLength); + newItems[oldLength]= path; + fPatchFileNameField.setItems(newItems); + selectionIndex= oldLength; + } + fPatchFileNameField.select(selectionIndex); + + //resetSelection(); + } + } + + /** + * The Finish button was pressed. Try to do the required work now and answer + * a boolean indicating success. If false is returned then the wizard will + * not close. + * + * @return boolean + */ + public boolean finish() { +// if (!ensureSourceIsValid()) +// return false; + + saveWidgetValues(); + +// Iterator resourcesEnum= getSelectedResources().iterator(); +// List fileSystemObjects= new ArrayList(); +// while (resourcesEnum.hasNext()) { +// fileSystemObjects.add( +// ((FileSystemElement) resourcesEnum.next()).getFileSystemObject()); +// } +// +// if (fileSystemObjects.size() > 0) +// return importResources(fileSystemObjects); +// +// MessageDialog +// .openInformation( +// getContainer().getShell(), +// DataTransferMessages.getString("DataTransfer.information"), //$NON-NLS-1$ +// DataTransferMessages.getString("FileImport.noneSelected")); //$NON-NLS-1$ +// +// return false; + + return true; + } + + /** + * Use the dialog store to restore widget values to the values that they held + * last time this wizard was used to completion + */ + private void restoreWidgetValues() { + + int inputMethod= FILE; + + IDialogSettings settings= getDialogSettings(); + if (settings != null) { + + try { + inputMethod= settings.getInt(STORE_INPUT_METHOD_ID); + } catch (NumberFormatException ex) { + //OK - no value stored in settings; just use CLIPBOARD + } + + // set filenames history + String[] sourceNames= settings.getArray(STORE_PATCH_FILES_ID); + if (sourceNames != null) + for (int i= 0; i < sourceNames.length; i++) + if (sourceNames[i] != null && sourceNames[i].length() > 0) + fPatchFileNameField.add(sourceNames[i]); + + // set patch file path + String patchFilePath= settings.get(STORE_PATCH_FILES_ID); + if (patchFilePath != null) + setSourceName(patchFilePath); + + // set URLs history + String[] sourceURLs= settings.getArray(STORE_PATCH_URLS_ID); + if (sourceURLs != null) + for (int i= 0; i < sourceURLs.length; i++) + if (sourceURLs[i] != null && sourceURLs[i].length() > 0) + fPatchURLField.add(sourceURLs[i]); + + // If the previous apply patch was used with a clipboard, we need to check + // if there is a valid patch on the clipboard. This will be done in adjustToCurrentTarget() + // so just set it to FILE now and, if there exists a patch on the clipboard, then clipboard + // will be selected automatically + if (inputMethod == CLIPBOARD){ + inputMethod= FILE; + fPatchFileNameField.deselectAll(); + } + + //set the workspace patch selection + String workspaceSetting= settings.get(STORE_WORKSPACE_PATH_ID); + if (workspaceSetting != null && workspaceSetting.length() > 0) { + // See if this resource still exists in the workspace + try { + IPath path= new Path(workspaceSetting); + IFile targetFile= ResourcesPlugin.getWorkspace().getRoot().getFile(path); + if (fTreeViewer != null && targetFile.exists()){ + fTreeViewer.expandToLevel(targetFile, 0); + fTreeViewer.setSelection(new StructuredSelection(targetFile)); + } + } catch (RuntimeException e) { + // Ignore. The setting was invalid + } + } else { + //check to see if the current input is set to workspace - if it is switch it + //back to clipboard since there is no corresponding element to go along with + //the tree viewer + if (inputMethod == WORKSPACE) + inputMethod= FILE; + } + } + + // set radio buttons state + setInputButtonState(inputMethod); + } + + /** + * Since Finish was pressed, write widget values to the dialog store so that they + * will persist into the next invocation of this wizard page + */ + void saveWidgetValues() { + IDialogSettings settings= getDialogSettings(); + if (settings != null) { + + settings.put(STORE_INPUT_METHOD_ID, getInputMethod()); + settings.put(STORE_PATCH_FILES_ID, getPatchFilePath()); + + // update source names history + String[] sourceNames= settings.getArray(STORE_PATCH_FILES_ID); + if (sourceNames == null) + sourceNames= new String[0]; + + sourceNames= addToHistory(sourceNames, getPatchFilePath()); + settings.put(STORE_PATCH_FILES_ID, sourceNames); + + // update source URLs history + String[] sourceURLs= settings.getArray(STORE_PATCH_URLS_ID); + if (sourceURLs == null) + sourceURLs= new String[0]; + + sourceURLs= addToHistory(sourceURLs, fPatchURLField.getText()); + settings.put(STORE_PATCH_URLS_ID, sourceURLs); + + // save the workspace selection + settings.put(STORE_WORKSPACE_PATH_ID, getWorkspacePath()); + + } + } + + private String getWorkspacePath() { + if (fTreeViewer != null){ + IResource[] resources= Utilities.getResources(fTreeViewer.getSelection()); + if (resources.length > 0) { + IResource patchFile= resources[0]; + return patchFile.getFullPath().toString(); + } + + } + return ""; //$NON-NLS-1$ + } + + // static helpers + + /** + * Checks to see if the file that has been selected for Apply Patch is + * actually a patch + * + * @return true if the file selected to run Apply Patch on in the workspace + * is a patch file or if the clipboard contains a patch or if the + * clipboard contains an URL (we assume it points to a patch ) + */ + private boolean adjustToCurrentTarget() { + // readjust selection if there is a patch selected in the workspace or on the clipboard + // check workspace first + IResource patchTarget= fPatchWizard.getTarget(); + if (patchTarget instanceof IFile) { + Reader reader= null; + try { + try { + reader= new FileReader(patchTarget.getLocation().toFile()); + if (isPatchFile(reader)) { + // set choice to workspace + setInputButtonState(WORKSPACE); + if (fTreeViewer != null && patchTarget.exists()) { + fTreeViewer.expandToLevel(patchTarget, 0); + fTreeViewer.setSelection(new StructuredSelection(patchTarget)); + } + return true; + } + } catch (FileNotFoundException ex) { + // silently ignored + } catch (NullPointerException nex) { + // silently ignored + } + + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException x) { + // silently ignored + } + } + } + } + // check out clipboard contents + Reader reader = null; + Control c = getControl(); + if (c != null) { + Clipboard clipboard= new Clipboard(c.getDisplay()); + Object o= clipboard.getContents(TextTransfer.getInstance()); + clipboard.dispose(); + try { + if (o instanceof String) { + reader= new StringReader((String) o); + if (isPatchFile(reader)) { + setInputButtonState(CLIPBOARD); + return true; + } + // maybe it's an URL + try { + URL url = new URL((String)o); + if(url != null) { + setInputButtonState(URL); + fPatchURLField.setText((String)o); + return true; + } + } catch (MalformedURLException e) { + // ignore + } + } + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException x) { + // silently ignored + } + } + } + } + return false; + } + + private boolean isPatchFile(Reader reader) { + WorkspacePatcher patcher= ((PatchWizard) getWizard()).getPatcher(); + + try { + patcher.parse(new BufferedReader(reader)); + } catch (Exception ex) { + return false; + } + + FilePatch2[] diffs= patcher.getDiffs(); + if (diffs == null || diffs.length == 0) + return false; + return true; + } + + /* + * Clears the dialog message box + */ + private void clearErrorMessage(){ + setErrorMessage(null); + } + + private void setInputButtonState(int state) { + + switch (state) { + case CLIPBOARD: + fUseClipboardButton.setSelection(true); + fUsePatchFileButton.setSelection(false); + fUseURLButton.setSelection(false); + fUseWorkspaceButton.setSelection(false); + break; + + case FILE: + fUseClipboardButton.setSelection(false); + fUsePatchFileButton.setSelection(true); + fUseURLButton.setSelection(false); + fUseWorkspaceButton.setSelection(false); + break; + + case URL: + fUseClipboardButton.setSelection(false); + fUsePatchFileButton.setSelection(false); + fUseURLButton.setSelection(true); + fUseWorkspaceButton.setSelection(false); + break; + + case WORKSPACE: + fUseClipboardButton.setSelection(false); + fUsePatchFileButton.setSelection(false); + fUseURLButton.setSelection(false); + fUseWorkspaceButton.setSelection(true); + break; + } + + setEnablePatchFile(state == FILE); + setEnableWorkspacePatch(state == WORKSPACE); + setEnableURLPatch(state == URL); + } + + protected int getInputMethod() { + if (fUseClipboardButton.getSelection()) + return CLIPBOARD; + if (fUsePatchFileButton.getSelection()) + return FILE; + if(fUseURLButton.getSelection()) + return URL; + return WORKSPACE; + } + + private String getPatchFilePath() { + if (fPatchFileNameField != null) + return fPatchFileNameField.getText(); + return ""; //$NON-NLS-1$ + } + + /* + * Adds an entry to a history, while taking care of duplicate history items + * and excessively long histories. The assumption is made that all histories + * should be of length <code>COMBO_HISTORY_LENGTH</code>. + * + * @param history the current history + * @param newEntry the entry to add to the history + */ + protected static String[] addToHistory(String[] history, String newEntry) { + java.util.ArrayList l= new java.util.ArrayList(java.util.Arrays.asList(history)); + + l.remove(newEntry); + l.add(0,newEntry); + + // since only one new item was added, we can be over the limit + // by at most one item + if (l.size() > COMBO_HISTORY_LENGTH) + l.remove(COMBO_HISTORY_LENGTH); + + return (String[]) l.toArray(new String[l.size()]); + } + + public boolean isPatchRead() { + return fPatchRead; + } +} + diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java new file mode 100644 index 000000000..735b1c3d3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/LineReader.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brock Janiczak <brockj@tpg.com.au> - Bug 181919 LineReader creating unneeded garbage + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.Platform; + +public class LineReader { + + /* + * Reads the contents from the given file and returns them as a List of + * lines. + */ + public static List load(IStorage file, boolean create) { + List lines = null; + if (!create && file != null && exists(file)) { + // read current contents + String charset = Utilities.getCharset(file); + InputStream is = null; + try { + is = file.getContents(); + + Reader streamReader = null; + try { + streamReader = new InputStreamReader(is, charset); + } catch (UnsupportedEncodingException x) { + // use default encoding + streamReader = new InputStreamReader(is); + } + + BufferedReader reader = new BufferedReader(streamReader); + lines = readLines(reader); + } catch (CoreException ex) { + // TODO + CompareUIPlugin.log(ex); + } finally { + if (is != null) + try { + is.close(); + } catch (IOException ex) { + // silently ignored + } + } + } + + if (lines == null) + lines = new ArrayList(); + return lines; + } + + private static boolean exists(IStorage file) { + if (file instanceof IFile) { + return ((IFile) file).exists(); + } + return true; + } + + public static List readLines(BufferedReader reader) { + List lines; + LineReader lr = new LineReader(reader); + if (!Platform.WS_CARBON.equals(Platform.getWS())) + // Don't treat single CRs as line feeds to be consistent with command line patch + lr.ignoreSingleCR(); + lines = lr.readLines(); + return lines; + } + + /* + * Concatenates all strings found in the given List. + */ + public static String createString(boolean preserveLineDelimeters, List lines) { + StringBuffer sb = new StringBuffer(); + Iterator iter = lines.iterator(); + if (preserveLineDelimeters) { + while (iter.hasNext()) + sb.append((String) iter.next()); + } else { + String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ + while (iter.hasNext()) { + String line = (String) iter.next(); + int l = length(line); + if (l < line.length()) { // line has delimiter + sb.append(line.substring(0, l)); + sb.append(lineSeparator); + } else { + sb.append(line); + } + } + } + return sb.toString(); + } + + /* + * Returns the length (excluding a line delimiter CR, LF, CR/LF) of the + * given string. + */ + /* package */static int length(String s) { + int l = s.length(); + if (l > 0) { + char c = s.charAt(l - 1); + if (c == '\r') + return l - 1; + if (c == '\n') { + if (l > 1 && s.charAt(l - 2) == '\r') + return l - 2; + return l - 1; + } + } + return l; + } + + private boolean fHaveChar = false; + private int fLastChar; + private boolean fSawEOF = false; + private BufferedReader fReader; + private boolean fIgnoreSingleCR = false; + private StringBuffer fBuffer = new StringBuffer(); + + public LineReader(BufferedReader reader) { + fReader = reader; + Assert.isNotNull(reader); + } + + public void ignoreSingleCR() { + fIgnoreSingleCR = true; + } + + /** + * Reads a line of text. A line is considered to be terminated by any one of + * a line feed ('\n'), a carriage return ('\r'), or a carriage return + * followed immediately by a line-feed. + * + * @return A string containing the contents of the line including the + * line-termination characters, or <code>null</code> if the end of + * the stream has been reached + * @exception IOException + * If an I/O error occurs + */ + /* package */String readLine() throws IOException { + try { + while (!fSawEOF) { + int c = readChar(); + if (c == -1) { + fSawEOF = true; + break; + } + fBuffer.append((char) c); + if (c == '\n') + break; + if (c == '\r') { + c = readChar(); + if (c == -1) { + fSawEOF = true; + break; // EOF + } + if (c != '\n') { + if (fIgnoreSingleCR) { + fBuffer.append((char) c); + continue; + } + fHaveChar = true; + fLastChar = c; + } else + fBuffer.append((char) c); + break; + } + } + + if (fBuffer.length() != 0) { + return fBuffer.toString(); + } + return null; + } finally { + fBuffer.setLength(0); + } + } + + /* package */void close() { + try { + fReader.close(); + } catch (IOException ex) { + // silently ignored + } + } + + public List readLines() { + try { + List lines = new ArrayList(); + String line; + while ((line = readLine()) != null) + lines.add(line); + return lines; + } catch (IOException ex) { + // NeedWork + // System.out.println("error while reading file: " + fileName + "(" + // + ex + ")"); + } finally { + close(); + } + return null; + } + + /* + * Returns the number of characters in the given string without counting a + * trailing line separator. + */ + /* package */int lineContentLength(String line) { + if (line == null) + return 0; + int length = line.length(); + for (int i = length - 1; i >= 0; i--) { + char c = line.charAt(i); + if (c == '\n' || c == '\r') + length--; + else + break; + } + return length; + } + + // ---- private + + private int readChar() throws IOException { + if (fHaveChar) { + fHaveChar = false; + return fLastChar; + } + return fReader.read(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java new file mode 100644 index 000000000..c8bc3da77 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchCompareEditorInput.java @@ -0,0 +1,433 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.CompareViewerPane; +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.DiffTreeViewer; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.ICompareInput; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IOpenListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.OpenEvent; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; + +public abstract class PatchCompareEditorInput extends CompareEditorInput { + + private static final String IMAGE_CACHE_KEY = "IMAGE_CACHE"; //$NON-NLS-1$ + + public static ImageDescriptor createOverlay(Image baseImage, ImageDescriptor overlayImage, int quadrant) { + return new DecoratorOverlayIcon(baseImage, createArrayFrom(overlayImage, quadrant), new Point(Math.max(baseImage.getBounds().width, 16), Math.max(baseImage.getBounds().height, 16))); + } + + private static ImageDescriptor[] createArrayFrom( + ImageDescriptor overlayImage, int quadrant) { + ImageDescriptor[] descs = new ImageDescriptor[] { null, null, null, null, null }; + descs[quadrant] = overlayImage; + return descs; + } + + class PatcherCompareEditorLabelProvider extends LabelProvider { + private ILabelProvider wrappedProvider; + + public PatcherCompareEditorLabelProvider(ILabelProvider labelProvider) { + wrappedProvider = labelProvider; + } + + public String getText(Object element) { + String text = wrappedProvider.getText(element); + if (element instanceof PatchDiffNode){ + PatchDiffNode node = (PatchDiffNode) element; + if (node instanceof PatchProjectDiffNode) { + PatchProjectDiffNode projectNode = (PatchProjectDiffNode) node; + if (!Utilities.getProject(projectNode.getDiffProject()).exists()) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[]{text, PatchMessages.PreviewPatchLabelDecorator_ProjectDoesNotExist}); + } + } + if (!node.isEnabled()) { + return NLS.bind(PatchMessages.Diff_2Args, + new String[]{text, PatchMessages.PatcherCompareEditorInput_NotIncluded}); + } + if (node instanceof PatchFileDiffNode) { + PatchFileDiffNode fileNode = (PatchFileDiffNode) node; + if (getPatcher().hasCachedContents(fileNode.getDiffResult().getDiff())) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[] {text, PatchMessages.HunkMergePage_Merged}); + } + if (!fileNode.fileExists()) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[] {text, PatchMessages.PatchCompareEditorInput_0}); + } + } + if (node instanceof HunkDiffNode) { + HunkDiffNode hunkNode = (HunkDiffNode) node; + if (hunkNode.isManuallyMerged()) { + text = NLS.bind(PatchMessages.Diff_2Args, new String[] {text, PatchMessages.HunkMergePage_Merged}); + } + if (hunkNode.isFuzzUsed()) { + text = NLS.bind(PatchMessages.Diff_2Args, + new String[] { text, + NLS.bind(hunkNode.isAllContextIgnored() ? PatchMessages.PreviewPatchPage_AllContextIgnored : PatchMessages.PreviewPatchPage_FuzzUsed, + new String[] { hunkNode.getHunkResult().getFuzz() + ""}) }); //$NON-NLS-1$ + } + } + if (getPatcher().isRetargeted(node.getPatchElement())) + return NLS.bind(PatchMessages.Diff_2Args, + new String[]{getPatcher().getOriginalPath(node.getPatchElement()).toString(), + NLS.bind(PatchMessages.PreviewPatchPage_Target, new String[]{node.getName()})}); + } + return text; + } + + public Image getImage(Object element) { + Image image = wrappedProvider.getImage(element); + if (element instanceof PatchDiffNode){ + PatchDiffNode node = (PatchDiffNode) element; + if (!node.isEnabled() && image != null) { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(getPatcher().getConfiguration()); + return imageCache.createImage(createOverlay(image, CompareUIPlugin.getImageDescriptor(ICompareUIConstants.REMOVED_OVERLAY), IDecoration.TOP_LEFT)); + } + } + if (element instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) element; + if (node.isManuallyMerged()) { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(getPatcher().getConfiguration()); + return imageCache.createImage(PatchCompareEditorInput.createOverlay(image, CompareUIPlugin.getImageDescriptor(ICompareUIConstants.IS_MERGED_OVERLAY), IDecoration.TOP_LEFT)); + } + } + return image; + } + } + + private final DiffNode root; + private final WorkspacePatcher patcher; + private TreeViewer viewer; + private boolean fShowAll; + private boolean showMatched = false; + + /** + * Creates a new PatchCompareEditorInput and makes use of the passed in CompareConfiguration + * to configure the UI elements. + * @param patcher + * @param configuration + */ + public PatchCompareEditorInput(WorkspacePatcher patcher, CompareConfiguration configuration) { + super(configuration); + Assert.isNotNull(patcher); + this.patcher = patcher; + root = new DiffNode(Differencer.NO_CHANGE) { + public boolean hasChildren() { + return true; + } + }; + initializeImageCache(); + } + + private void initializeImageCache() { + initializeImageCache(patcher.getConfiguration()); + } + + private static LocalResourceManager initializeImageCache(PatchConfiguration patchConfiguration) { + LocalResourceManager imageCache = new LocalResourceManager(JFaceResources.getResources()); + patchConfiguration.setProperty(IMAGE_CACHE_KEY, imageCache); + return imageCache; + } + + protected void handleDispose() { + super.handleDispose(); + getImageCache(getPatcher().getConfiguration()).dispose(); + } + + public static LocalResourceManager getImageCache(PatchConfiguration patchConfiguration) { + LocalResourceManager cache = (LocalResourceManager)patchConfiguration.getProperty(IMAGE_CACHE_KEY); + if (cache == null) { + return initializeImageCache(patchConfiguration); + } + return cache; + } + + protected Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + initLabels(); + return root; + } + + private void initLabels() { + CompareConfiguration cc = getCompareConfiguration(); + // set left editable so that unmatched hunks can be edited + cc.setLeftEditable(true); + cc.setRightEditable(false); + //cc.setProperty(CompareEditor.CONFIRM_SAVE_PROPERTY, new Boolean(false)); + cc.setLeftLabel(getCompareConfiguration().getLeftLabel(root)); + cc.setLeftImage(getCompareConfiguration().getLeftImage(root)); + cc.setRightLabel(getCompareConfiguration().getRightLabel(root)); + cc.setRightImage(getCompareConfiguration().getRightImage(root)); + } + + /** + * Update the presentation of the diff tree. + */ + protected void updateTree() { + if (getViewer() != null && !getViewer().getControl().isDisposed()) + getViewer().refresh(true); + } + + /** + * Build the diff tree. + */ + protected void buildTree(){ + + // Reset the input node so it is empty + if (getRoot().hasChildren()) { + resetRoot(); + } + // Reset the input of the viewer so the old state is no longer used + getViewer().setInput(getRoot()); + + // Refresh the patcher state + getPatcher().refresh(); + + // Build the diff tree + if (getPatcher().isWorkspacePatch()){ + processProjects(getPatcher().getDiffProjects()); + } else { + processDiffs(getPatcher().getDiffs()); + } + + // Refresh the viewer + getViewer().refresh(); + } + + private void processDiffs(FilePatch2[] diffs) { + for (int i = 0; i < diffs.length; i++) { + processDiff(diffs[i], getRoot()); + } + } + + private void processProjects(DiffProject[] diffProjects) { + //create diffProject nodes + for (int i = 0; i < diffProjects.length; i++) { + PatchProjectDiffNode projectNode = new PatchProjectDiffNode(getRoot(), diffProjects[i], getPatcher().getConfiguration()); + FilePatch2[] diffs = diffProjects[i].getFileDiffs(); + for (int j = 0; j < diffs.length; j++) { + FilePatch2 fileDiff = diffs[j]; + processDiff(fileDiff, projectNode); + } + } + } + + private void processDiff(FilePatch2 diff, DiffNode parent) { + FileDiffResult diffResult = getPatcher().getDiffResult(diff); + PatchFileDiffNode node = PatchFileDiffNode.createDiffNode(parent, diffResult); + HunkResult[] hunkResults = diffResult.getHunkResults(); + for (int i = 0; i < hunkResults.length; i++) { + HunkResult hunkResult = hunkResults[i]; + if (!hunkResult.isOK()) { + HunkDiffNode hunkNode = HunkDiffNode.createDiffNode(node, hunkResult, true); + Object left = hunkNode.getLeft(); + if (left instanceof UnmatchedHunkTypedElement) { + UnmatchedHunkTypedElement element = (UnmatchedHunkTypedElement) left; + element.addContentChangeListener(new IContentChangeListener() { + public void contentChanged(IContentChangeNotifier source) { + if (getViewer() == null || getViewer().getControl().isDisposed()) + return; + getViewer().refresh(true); + } + }); + } + } else if (showMatched) { + HunkDiffNode.createDiffNode(node, hunkResult, false, true, false); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.CompareEditorInput#createDiffViewer(org.eclipse.swt.widgets.Composite) + */ + public Viewer createDiffViewer(Composite parent) { + viewer = new DiffTreeViewer(parent, getCompareConfiguration()){ + protected void fillContextMenu(IMenuManager manager) { + PatchCompareEditorInput.this.fillContextMenu(manager); + } + }; + + viewer.setLabelProvider(new PatcherCompareEditorLabelProvider((ILabelProvider)viewer.getLabelProvider())); + viewer.getTree().setData(CompareUI.COMPARE_VIEWER_TITLE, PatchMessages.PatcherCompareEditorInput_PatchContents); + viewer.addOpenListener(new IOpenListener() { + public void open(OpenEvent event) { + IStructuredSelection sel= (IStructuredSelection) event.getSelection(); + Object obj= sel.getFirstElement(); + if (obj instanceof HunkDiffNode) { + if (((HunkDiffNode) obj).getHunkResult().isOK()) { + getCompareConfiguration().setLeftLabel(PatchMessages.PatcherCompareEditorInput_LocalCopy); + getCompareConfiguration().setRightLabel(PatchMessages.PreviewPatchPage2_MatchedHunk); + } else { + getCompareConfiguration().setLeftLabel(PatchMessages.PreviewPatchPage2_PatchedLocalFile); + getCompareConfiguration().setRightLabel(PatchMessages.PreviewPatchPage2_OrphanedHunk); + } + } else { + getCompareConfiguration().setLeftLabel(PatchMessages.PatcherCompareEditorInput_LocalCopy); + getCompareConfiguration().setRightLabel(PatchMessages.PatcherCompareEditorInput_AfterPatch); + } + } + + }); + viewer.setFilters(getFilters()); + viewer.setInput(root); + return viewer; + } + + private ViewerFilter[] getFilters() { + return new ViewerFilter[] { new ViewerFilter() { + public boolean select(Viewer v, Object parentElement, Object element) { + if (element instanceof PatchDiffNode) { + PatchDiffNode node = (PatchDiffNode) element; + return node.isEnabled() || isShowAll(); + } + return false; + } + } }; + } + + protected boolean isShowAll() { + return fShowAll; + } + + protected void setShowAll(boolean show) { + fShowAll = show; + } + + public boolean isShowMatched() { + return showMatched; + } + + protected void setShowMatched(boolean show) { + showMatched = show; + } + + public void contributeDiffViewerToolbarItems(Action[] actions, boolean workspacePatch){ + ToolBarManager tbm= CompareViewerPane.getToolBarManager(viewer.getControl().getParent()); + if (tbm != null) { + tbm.removeAll(); + + tbm.add(new Separator("contributed")); //$NON-NLS-1$ + + for (int i = 0; i < actions.length; i++) { + tbm.appendToGroup("contributed", actions[i]); //$NON-NLS-1$ + } + + tbm.update(true); + } + } + + public TreeViewer getViewer() { + return viewer; + } + + public DiffNode getRoot() { + return root; + } + + public void resetRoot() { + IDiffElement[] children = root.getChildren(); + for (int i = 0; i < children.length; i++) { + IDiffElement child = children[i]; + root.remove(child); + } + } + + public WorkspacePatcher getPatcher() { + return patcher; + } + + public boolean confirmRebuild(String message) { + if (getPatcher().hasCachedContents()) { + if (promptToDiscardCachedChanges(message)) { + getPatcher().clearCachedContents(); + return true; + } + return false; + } + return true; + } + + private boolean promptToDiscardCachedChanges(String message) { + return MessageDialog.openConfirm(viewer.getControl().getShell(), PatchMessages.PatcherCompareEditorInput_0, message); + } + + /** + * Return whether this input has a result to apply. The input + * has a result to apply if at least one hunk is selected for inclusion. + * @return whether this input has a result to apply + */ + public boolean hasResultToApply() { + boolean atLeastOneIsEnabled = false; + if (getViewer() != null) { + IDiffElement[] elements = getRoot().getChildren(); + for (int i = 0; i < elements.length; i++) { + IDiffElement element = elements[i]; + if (isEnabled(element)) { + atLeastOneIsEnabled = true; + break; + } + } + } + return atLeastOneIsEnabled; + } + + private boolean isEnabled(IDiffElement element) { + if (element instanceof PatchDiffNode) { + PatchDiffNode node = (PatchDiffNode) element; + return node.isEnabled(); + } + return false; + } + + protected abstract void fillContextMenu(IMenuManager manager); + + public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, + Composite parent) { + if (org.eclipse.compare.internal.Utilities.isHunk(input)) + return null; + return super.findStructureViewer(oldViewer, input, parent); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java new file mode 100644 index 000000000..351af89d3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchDiffNode.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.compare.IResourceProvider; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.core.resources.IResource; + +public abstract class PatchDiffNode extends DiffNode implements IResourceProvider { + + private Object fElement; + + public PatchDiffNode(Object patchElement, IDiffContainer parent, int kind, + ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(parent, kind, ancestor, left, right); + fElement = patchElement; + } + + public PatchDiffNode(Object patchElement, IDiffContainer parent, int kind) { + super(parent, kind); + fElement = patchElement; + } + + public boolean isEnabled() { + return getPatcher().isEnabled(getPatchElement()); + } + + public void setEnabled(boolean enabled) { + getPatcher().setEnabled(getPatchElement(), enabled); + } + + protected final Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + public Object getPatchElement() { + return fElement; + } + + protected abstract PatchConfiguration getConfiguration(); + + public boolean equals(Object other) { + if (other instanceof PatchDiffNode) { + PatchDiffNode node = (PatchDiffNode) other; + return (node.getPatchElement().equals(getPatchElement())); + } + return super.equals(other); + } + + public int hashCode() { + return getPatchElement().hashCode(); + } + + public IResource getResource() { + return null; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java new file mode 100644 index 000000000..e0a60b348 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchErrorDialog.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +public class PatchErrorDialog { + + private PatchErrorDialog() { + // no instance. + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java new file mode 100644 index 000000000..949b1cd02 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileDiffNode.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.resources.IResource; + +public class PatchFileDiffNode extends PatchDiffNode implements IContentChangeListener { + + private final FileDiffResult result; + + public static PatchFileDiffNode createDiffNode(DiffNode parent, FileDiffResult result) { + return new PatchFileDiffNode(result, parent, getKind(result), getAncestorElement(result), getLeftElement(result), getRightElement(result)); + } + + public static int getKind(FileDiffResult result) { + if (!result.hasMatches()) + return Differencer.NO_CHANGE; + int fileDiffKind = result.getDiff().getDiffType(result.getConfiguration().isReversed()); + int kind = convertFileDiffTypeToDifferencerType(fileDiffKind); + return kind | Differencer.RIGHT; + } + + private static int convertFileDiffTypeToDifferencerType(int fileDiffKind) { + int kind; + switch (fileDiffKind) { + case FilePatch2.ADDITION: + kind = Differencer.ADDITION; + break; + case FilePatch2.DELETION: + kind = Differencer.DELETION; + break; + case FilePatch2.CHANGE: + kind = Differencer.CHANGE; + break; + default: + kind = Differencer.CHANGE; + break; + } + return kind; + } + + public static ITypedElement getRightElement(FileDiffResult result) { + return new PatchFileTypedElement(result, true); + } + + private static ITypedElement getLeftElement(FileDiffResult result) { + return new PatchFileTypedElement(result, false); + } + + public static ITypedElement getAncestorElement(FileDiffResult result) { + return new PatchFileTypedElement(result, false); + } + + public PatchFileDiffNode(FileDiffResult result, IDiffContainer parent, int kind, + ITypedElement ancestor, ITypedElement left, ITypedElement right) { + super(result.getDiff(), parent, kind, ancestor, left, right); + this.result = result; + } + + public FileDiffResult getDiffResult() { + return result; + } + + protected PatchConfiguration getConfiguration() { + return result.getConfiguration(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffContainer#add(org.eclipse.compare.structuremergeviewer.IDiffElement) + */ + public void add(IDiffElement diff) { + super.add(diff); + // Listen for content changes in unmatched children so we can fire an input change + if (diff instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) diff; + Object left = node.getLeft(); + if (left instanceof IContentChangeNotifier) { + IContentChangeNotifier notifier = (IContentChangeNotifier) left; + notifier.addContentChangeListener(this); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IContentChangeListener#contentChanged(org.eclipse.compare.IContentChangeNotifier) + */ + public void contentChanged(IContentChangeNotifier source) { + fireChange(); + } + + public int getKind() { + int kind = super.getKind(); + if (kind == Differencer.NO_CHANGE && getPatcher().hasCachedContents(getDiffResult().getDiff())) { + return Differencer.CHANGE | Differencer.RIGHT; + } + return kind; + } + + public boolean fileExists() { + IResource file = getResource(); + return file != null && file.isAccessible(); + } + + public IResource getResource() { + return ((WorkspaceFileDiffResult)getDiffResult()).getTargetFile(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java new file mode 100644 index 000000000..86c5fff2d --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchFileTypedElement.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.IEncodedStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.graphics.Image; + +public class PatchFileTypedElement implements ITypedElement, + IEncodedStreamContentAccessor { + + private final FileDiffResult result; + private final boolean isAfterState; + + public PatchFileTypedElement(FileDiffResult result, boolean isAfterState) { + this.result = result; + this.isAfterState = isAfterState; + } + + public Image getImage() { + IFile file = getPatcher().getTargetFile(result.getDiff()); + if (file == null) { + // We don't get a target file if the file doesn't exist + DiffProject project = result.getDiff().getProject(); + if (project != null) { + file = Utilities.getProject(project).getFile( + result.getDiff().getPath( + result.getConfiguration().isReversed())); + } else { + IResource target = getPatcher().getTarget(); + if (target instanceof IFile) { + file = (IFile) target; + } else if (target instanceof IContainer) { + IContainer container = (IContainer) target; + file = container.getFile(result.getTargetPath()); + } + } + } + Image image = null; + if (file != null) { + image = CompareUI.getImage(file); + } + if (result.containsProblems()) { + LocalResourceManager imageCache = PatchCompareEditorInput + .getImageCache(result.getConfiguration()); + image = HunkTypedElement.getHunkErrorImage(image, imageCache, true); + } + return image; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.ITypedElement#getName() + */ + public String getName() { + return result.getTargetPath().toString(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.ITypedElement#getType() + */ + public String getType() { + return result.getTargetPath().getFileExtension(); + } + + public String getCharset() throws CoreException { + return result.getCharset(); + } + + public InputStream getContents() throws CoreException { + // If there are cached contents, use them + if (isAfterState && getPatcher().hasCachedContents(result.getDiff())) + return new ByteArrayInputStream(getPatcher().getCachedContents( + result.getDiff())); + // Otherwise, get the lines from the diff result + List lines; + if (isAfterState) { + lines = result.getAfterLines(); + } else { + lines = result.getBeforeLines(); + } + String contents = LineReader.createString(getPatcher() + .isPreserveLineDelimeters(), lines); + String charSet = getCharset(); + byte[] bytes = null; + if (charSet != null) { + try { + bytes = contents.getBytes(charSet); + } catch (UnsupportedEncodingException e) { + CompareUIPlugin.log(e); + } + } + if (bytes == null) { + bytes = contents.getBytes(); + } + return new ByteArrayInputStream(bytes); + } + + private Patcher getPatcher() { + return Patcher.getPatcher(result.getConfiguration()); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java new file mode 100644 index 000000000..6fde7b42a --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.osgi.util.NLS; + +public final class PatchMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.patch.PatchMessages";//$NON-NLS-1$ + + private PatchMessages() { + // Do not instantiate + } + + public static String HunkMergePage_GenerateRejectFile; + public static String HunkMergePage_Merged; + public static String InputPatchPage_MalformedURL; + public static String InputPatchPage_NoURL; + public static String InputPatchPage_URLButton_text; + public static String InputPatchPage_URL_title; + public static String PatchCompareEditorInput_0; + public static String PatcherCompareEditorInput_0; + public static String PatcherCompareEditorInput_AfterPatch; + public static String PatcherCompareEditorInput_LocalCopy; + public static String PatcherCompareEditorInput_NotIncluded; + public static String PatcherCompareEditorInput_PatchContents; + public static String PatchTargetPage_0; + public static String PatchWizard_0; + public static String PatchWizard_1; + public static String PatchWizard_title; + public static String PatchWizard_unexpectedException_message; + public static String InputPatchPage_title; + public static String InputPatchPage_message; + public static String InputPatchPage_Clipboard; + public static String InputPatchPage_SelectInput; + public static String InputPatchPage_PatchErrorDialog_title; + public static String InputPatchPage_FileButton_text; + public static String InputPatchPage_ChooseFileButton_text; + public static String InputPatchPage_UseClipboardButton_text; + public static String InputPatchPage_UseWorkspaceButton_text; + public static String InputPatchPage_WorkspaceSelectPatch_text; + public static String InputPatchPage_NothingSelected_message; + public static String InputPatchPage_ClipboardIsEmpty_message; + public static String InputPatchPage_NoTextInClipboard_message; + public static String InputPatchPage_CouldNotReadClipboard_message; + public static String InputPatchPage_CannotLocatePatch_message; + public static String InputPatchPage_NoFileName_message; + public static String InputPatchPage_FileSelectedNotPatch_message; + public static String InputPatchPage_SelectPatchFileDialog_title; + public static String InputPatchPage_PatchFileNotFound_message; + public static String InputPatchPage_ParseError_message; + public static String InputPatchPage_Clipboard_title; + public static String InputPatchPage_PatchFile_title; + public static String InputPatchPage_WorkspacePatch_title; + public static String InputPatchPage_NoDiffsFound_format; + public static String InputPatchPage_SingleFileError_format; + public static String InputPatchPage_URLConnecting; + public static String InputPatchPage_URLFetchingContent; + public static String PatchTargetPage_title; + public static String PatchTargetPage_message; + public static String PreviewPatchPage_title; + public static String PreviewPatchPage_PatchOptions_title; + public static String PreviewPatchPage_IgnoreSegments_text; + public static String PreviewPatchPage_ReversePatch_text; + public static String PreviewPatchPage_FuzzFactor_text; + public static String PreviewPatchPage_FuzzFactor_tooltip; + public static String PreviewPatchPage_GuessFuzz_text; + public static String PreviewPatchPage_FuzzUsed; + public static String PreviewPatchPage_AllContextIgnored; + + static { + NLS.initializeMessages(BUNDLE_NAME, PatchMessages.class); + } + + public static String Diff_2Args; + public static String PreviewPatchPage_RetargetPatch; + public static String PreviewPatchPage_SelectProject; + public static String PreviewPatchPage_Target; + public static String PreviewPatchLabelDecorator_ProjectDoesNotExist; + public static String PreviewPatchPage2_0; + public static String PreviewPatchPage2_1; + public static String PreviewPatchPage2_2; + public static String PreviewPatchPage2_3; + public static String PreviewPatchPage2_4; + public static String PreviewPatchPage2_5; + public static String PreviewPatchPage2_6; + public static String PreviewPatchPage2_7; + public static String PreviewPatchPage2_8; + public static String PreviewPatchPage2_9; + public static String PreviewPatchPage2_CalculateReverse; + public static String PreviewPatchPage2_IgnoreWhitespace; + public static String PreviewPatchPage2_IgnoreWSAction; + public static String PreviewPatchPage2_IgnoreWSTooltip; + public static String PreviewPatchPage2_OrphanedHunk; + public static String PreviewPatchPage2_MatchedHunk; + public static String PreviewPatchPage2_PatchedLocalFile; + public static String PreviewPatchPage2_RetargetAction; + public static String PreviewPatchPage2_RetargetTooltip; + public static String PreviewPatchPage2_ShowMatched; + public static String PreviewPatchPage2_AddedRemovedLines; + public static String RetargetPatchElementDialog_0; + public static String RetargetPatchElementDialog_1; +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties new file mode 100644 index 000000000..95ac4b8f2 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchMessages.properties @@ -0,0 +1,117 @@ +############################################################################### +# Copyright (c) 2000, 2009 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +# Sebastian Davids <sdavids@gmx.de> - layout tweaks +############################################################################### + +# +# 'Compare with Patch' Action +# +PatcherCompareEditorInput_LocalCopy=Local Copy +PatcherCompareEditorInput_0=Discard Changes? +PatcherCompareEditorInput_AfterPatch=After Patch + +# +# PatchWizard +# +PatchWizard_title=Apply Patch +PatchWizard_0=Patch has rejects +PatchWizard_1=The patch you are applying has segments that did not match. Are you sure you want to apply it? +PatchWizard_unexpectedException_message= Unexpected exception while applying the patch. See log for a detailed error description. +PatcherCompareEditorInput_PatchContents=Patch Contents +PatcherCompareEditorInput_NotIncluded=(Not included) + +# +# InputPatchPage +# +InputPatchPage_title= Patch Input Specification +InputPatchPage_NoURL=Please enter an URL +InputPatchPage_message= Select the patch location. +InputPatchPage_Clipboard=Clipboard +InputPatchPage_SelectInput=Apply the patch to the &selected file, folder or project: +InputPatchPage_MalformedURL=Malformed URL +InputPatchPage_PatchErrorDialog_title=Patch Error +InputPatchPage_FileButton_text=Fil&e +InputPatchPage_URLButton_text=&URL +InputPatchPage_ChooseFileButton_text=&Browse... +InputPatchPage_UseClipboardButton_text=&Clipboard +InputPatchPage_UseWorkspaceButton_text=&Workspace +InputPatchPage_WorkspaceSelectPatch_text=&Select the location of the patch: +InputPatchPage_NothingSelected_message=Select a file or folder to be patched +InputPatchPage_ClipboardIsEmpty_message=Clipboard is empty +InputPatchPage_NoTextInClipboard_message=Clipboard does not contain text +InputPatchPage_CouldNotReadClipboard_message=Cannot retrieve clipboard contents +InputPatchPage_CannotLocatePatch_message=Cannot locate patch file: +InputPatchPage_NoFileName_message=No file name +InputPatchPage_FileSelectedNotPatch_message=Selected resource is not a valid patch +#SI - Select file name ? +InputPatchPage_SelectPatchFileDialog_title=Select Patch File +InputPatchPage_PatchFileNotFound_message=Patch file not found. +InputPatchPage_ParseError_message=Error while parsing patch +InputPatchPage_Clipboard_title=Clipboard +InputPatchPage_PatchFile_title=Patch file +InputPatchPage_URL_title=URL +InputPatchPage_WorkspacePatch_title=Workspace file +InputPatchPage_NoDiffsFound_format={0} does not contain valid patch. +InputPatchPage_SingleFileError_format={0} contains multiple patches. You cannot apply them to a single file. +InputPatchPage_URLConnecting=Opening connection to the URL +InputPatchPage_URLFetchingContent=Fetching content from the URL +# +# PatchTargetPage +# +PatchTargetPage_title=Target Resource +PatchTargetPage_0=Apply the patch to the &workspace root +PatchTargetPage_message=Select the target workspace resource for the patch. +PatchCompareEditorInput_0=(file does not exist) +# +# PreviewPatchPage +# +PreviewPatchPage_title=Review Patch +PreviewPatchPage2_0=&Exclude +PreviewPatchPage2_1=&Include +PreviewPatchPage2_2=Performing this operation will require that your manual changes be discarded. +PreviewPatchPage2_3=Reversing the patch will require that your manual changes be discarded. +PreviewPatchPage2_4=Performing this operation will require that your manual changes be discarded. +PreviewPatchPage2_5=Changing the fuzz factor will require that your manual changes be discarded. +PreviewPatchPage2_6=Changing the fuzz factor will require that your manual changes be discarded. +PreviewPatchPage2_7=&Show Excluded +PreviewPatchPage2_8=Review the patch with respect to the local file system and manually merge any unmatched portions. +PreviewPatchPage2_9=Double-click on file or patch segment entries to view their content: +PreviewPatchPage_Target=(target: {0}) +PreviewPatchPage_PatchOptions_title=Patch options +PreviewPatchPage_IgnoreSegments_text=&Ignore leading path name segments: +PreviewPatchPage_ReversePatch_text=&Reverse patch +PreviewPatchPage_FuzzFactor_text=Fu&zz factor: +PreviewPatchPage2_RetargetAction=&Move +PreviewPatchPage2_RetargetTooltip=Move the selected patch element to another resource +PreviewPatchPage2_OrphanedHunk=Unmatched Patch Segment +PreviewPatchPage2_MatchedHunk=Matched Hunk +PreviewPatchPage2_IgnoreWSAction=Ignore whitespace +PreviewPatchPage_FuzzFactor_tooltip=Allow this number of context lines to be ignored +PreviewPatchPage2_IgnoreWSTooltip=Ignore whitespace +PreviewPatchPage2_IgnoreWhitespace=Ignore whitespace +PreviewPatchPage2_PatchedLocalFile=Patched Local File +PreviewPatchPage2_CalculateReverse=Calculating reverse +PreviewPatchPage_RetargetPatch=Retarget Patch +PreviewPatchPage_SelectProject=Select the new target project for the portion of the patch targeted to project ''{0}'': +PreviewPatchPage_GuessFuzz_text= &Guess +PreviewPatchPage_FuzzUsed=(fuzz factor used: {0}) +PreviewPatchPage_AllContextIgnored=(fuzz factor used: {0}, all context lines ignored) +PreviewPatchPage2_ShowMatched=Show &matched hunks +PreviewPatchPage2_AddedRemovedLines=Patch contains {0} added and {1} removed lines. +PreviewPatchLabelDecorator_ProjectDoesNotExist=(Project does not exist in workspace) + +# +# Patcher +# +Diff_2Args={0} {1} +HunkMergePage_Merged=(merged) +HunkMergePage_GenerateRejectFile=G&enerate a .rej file for unmerged hunks +RetargetPatchElementDialog_0=Select the new target file for the portion of the patch targeted to file ''{0}'' +RetargetPatchElementDialog_1=Select the new target file for this portion of the patch targeted to file ''{0}'' diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java new file mode 100644 index 000000000..1aa09b3d5 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchProjectDiffNode.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IDiffContainer; +import org.eclipse.compare.structuremergeviewer.IDiffElement; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.graphics.Image; + +public class PatchProjectDiffNode extends PatchDiffNode { + + private final DiffProject project; + private final PatchConfiguration configuration; + + public PatchProjectDiffNode(IDiffContainer parent, DiffProject project, PatchConfiguration configuration) { + super(project, parent, Differencer.NO_CHANGE); + this.project = project; + this.configuration = configuration; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffNode#getName() + */ + public String getName() { + return project.getName(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffNode#getImage() + */ + public Image getImage() { + Image image = CompareUI.getImage(Utilities.getProject(project)); + if (containsProblems()) { + LocalResourceManager imageCache = PatchCompareEditorInput.getImageCache(getConfiguration()); + image = HunkTypedElement.getHunkErrorImage(image, imageCache, true); + } + return image; + } + + private boolean containsProblems() { + IDiffElement[] elements = getChildren(); + for (int i = 0; i < elements.length; i++) { + IDiffElement diffElement = elements[i]; + if (diffElement instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) diffElement; + if (node.getDiffResult().containsProblems()) + return true; + } + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.structuremergeviewer.DiffNode#getType() + */ + public String getType() { + return ITypedElement.FOLDER_TYPE; + } + + protected PatchConfiguration getConfiguration() { + return configuration; + } + + public DiffProject getDiffProject() { + return project; + } + + public IResource getResource() { + return ResourcesPlugin.getWorkspace().getRoot().getProject(getDiffProject().getName()); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java new file mode 100644 index 000000000..0f7317461 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchTargetPage.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import com.ibm.icu.text.MessageFormat; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardPage; + +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; + +import org.eclipse.compare.internal.ICompareContextIds; +import org.eclipse.compare.internal.Utilities; + +/*** + * This page only shows up if the user is trying to apply + * a non-workspace rooted patch. + */ +public class PatchTargetPage extends WizardPage { + + private boolean fShowError = false; + + // SWT widgets + private TreeViewer fPatchTargets; + private Button useWorkspaceAsTarget; + private Button selectTarget; + + protected WorkspacePatcher fPatcher; + + protected final static String PATCHTARGETPAGE_NAME = "PatchTargetPage"; //$NON-NLS-1$ + + public PatchTargetPage(WorkspacePatcher patcher) { + super(PATCHTARGETPAGE_NAME, PatchMessages.PatchTargetPage_title, null); + setMessage(PatchMessages.PatchTargetPage_message); + fPatcher = patcher; + } + + /* + * Get a path from the supplied text widget. + * @return org.eclipse.core.runtime.IPath + */ + protected IPath getPathFromText(Text textField) { + return (new Path(textField.getText())).makeAbsolute(); + } + + public void createControl(Composite parent) { + + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); + setControl(composite); + + useWorkspaceAsTarget = createRadioButton(composite, PatchMessages.PatchTargetPage_0, 1); + selectTarget = createRadioButton(composite, PatchMessages.InputPatchPage_SelectInput, 1); + + buildInputGroup(composite); + + updateWidgetEnablements(); + + Dialog.applyDialogFont(composite); + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.PATCH_INPUT_WIZARD_PAGE); + + useWorkspaceAsTarget.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + fShowError = true; + if (useWorkspaceAsTarget.getSelection()) { + fPatchTargets.getTree().setEnabled(false); + fPatcher.setTarget(ResourcesPlugin.getWorkspace().getRoot()); + } else { + fPatchTargets.getTree().setEnabled(true); + fPatcher.setTarget(Utilities.getFirstResource(fPatchTargets.getSelection())); + } + updateWidgetEnablements(); + } + }); + } + + private Button createRadioButton(Composite parent, String label, int span) { + Button button = new Button(parent, SWT.RADIO); + button.setText(label); + GridData data = new GridData(); + data.horizontalSpan = span; + button.setLayoutData(data); + return button; + } + + /* (non-JavaDoc) + * Method declared in IWizardPage. + */ + public IWizardPage getNextPage() { + + // if selected target is file ensure that patch file + // contains only a patch for a single file + if (!fPatcher.isWorkspacePatch() && fPatcher.getTarget() instanceof IFile && fPatcher.getDiffs().length > 1) { + InputPatchPage inputPage = (InputPatchPage) getWizard().getPage(InputPatchPage.INPUTPATCHPAGE_NAME); + String source = ""; //$NON-NLS-1$ + switch (inputPage.getInputMethod()) { + case InputPatchPage.CLIPBOARD : + source = PatchMessages.InputPatchPage_Clipboard_title; + break; + + case InputPatchPage.FILE : + source = PatchMessages.InputPatchPage_PatchFile_title; + break; + + case InputPatchPage.WORKSPACE : + source = PatchMessages.InputPatchPage_WorkspacePatch_title; + break; + } + String format = PatchMessages.InputPatchPage_SingleFileError_format; + String message = MessageFormat.format(format, new String[] {source}); + MessageDialog.openInformation(null, PatchMessages.InputPatchPage_PatchErrorDialog_title, message); + return this; + } + + return super.getNextPage(); + } + + /* (non-JavaDoc) + * Method declared in IWizardPage. + */ + public boolean canFlipToNextPage() { + // we can't call getNextPage to determine if flipping is allowed since computing + // the next page is quite expensive. So we say yes if the page is complete. + return isPageComplete(); + } + + private void buildInputGroup(Composite parent) { + Tree tree = new Tree(parent, SWT.BORDER); + GridData gd = new GridData(GridData.FILL_BOTH); + gd.heightHint = 200; + tree.setLayoutData(gd); + + fPatchTargets = new TreeViewer(tree); + fPatchTargets.setLabelProvider(new WorkbenchLabelProvider()); + fPatchTargets.setContentProvider(new WorkbenchContentProvider()); + fPatchTargets.setComparator(new ResourceComparator(ResourceComparator.NAME)); + fPatchTargets.setInput(ResourcesPlugin.getWorkspace().getRoot()); + + IResource target = fPatcher.getTarget(); + if (target != null && !(target instanceof IWorkspaceRoot)) { + fPatchTargets.expandToLevel(target, 0); + fPatchTargets.setSelection(new StructuredSelection(target)); + } + + // register listeners + fPatchTargets.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + fShowError = true; + fPatcher.setTarget(Utilities.getFirstResource(event.getSelection())); + updateWidgetEnablements(); + } + }); + + fPatchTargets.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ((PatchWizard)getWizard()).showPage(getNextPage()); + } + }); + } + + /** + * Updates the enable state of this page's controls. + */ + private void updateWidgetEnablements() { + String error = null; + + if (fPatcher.getTarget() == null) { + useWorkspaceAsTarget.setSelection(false); + selectTarget.setSelection(true); + error = PatchMessages.InputPatchPage_NothingSelected_message; + setPageComplete(false); + if (fShowError) + setErrorMessage(error); + return; + } + setErrorMessage(null); + useWorkspaceAsTarget.setSelection(fPatcher.getTarget() instanceof IWorkspaceRoot); + selectTarget.setSelection(!useWorkspaceAsTarget.getSelection()); + setPageComplete(true); + } + + /** + * The Finish button was pressed. Try to do the required work now and answer + * a boolean indicating success. If false is returned then the wizard will + * not close. + * + * @return boolean + */ + public boolean finish() { + return true; + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java new file mode 100644 index 000000000..ede6e0649 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizard.java @@ -0,0 +1,236 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ExceptionHandler; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.actions.WorkspaceModifyOperation; + +public class PatchWizard extends Wizard { + + // dialog store id constants + private final static String DIALOG_SETTINGS_KEY= "PatchWizard"; //$NON-NLS-1$ + + private boolean fHasNewDialogSettings; + + protected InputPatchPage fPatchWizardPage; + protected PatchTargetPage fPatchTargetPage; + protected PreviewPatchPage2 fPreviewPage2; + + private final WorkspacePatcher fPatcher; + + private CompareConfiguration fConfiguration; + private IStorage patch; + + private boolean patchReadIn = false; + + public PatchWizard(IStorage patch, IResource target, CompareConfiguration configuration) { + Assert.isNotNull(configuration); + this.fConfiguration = configuration; + setDefaultPageImageDescriptor(CompareUIPlugin.getImageDescriptor("wizban/applypatch_wizban.png")); //$NON-NLS-1$ + setWindowTitle(PatchMessages.PatchWizard_title); + initializeDialogSettings(); + fPatcher= new WorkspacePatcher(target); + if (patch != null) { + try { + fPatcher.parse(patch); + this.patch = patch; + patchReadIn = true; + } catch (IOException e) { + MessageDialog.openError(null, + PatchMessages.InputPatchPage_PatchErrorDialog_title, + PatchMessages.InputPatchPage_ParseError_message); + } catch (CoreException e) { + ErrorDialog.openError(getShell(), + PatchMessages.InputPatchPage_PatchErrorDialog_title, + PatchMessages.InputPatchPage_PatchFileNotFound_message, e.getStatus()); + } + } + } + + private void initializeDialogSettings() { + IDialogSettings workbenchSettings= CompareUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section= workbenchSettings.getSection(DIALOG_SETTINGS_KEY); + if (section == null) { + fHasNewDialogSettings= true; + } else { + fHasNewDialogSettings= false; + setDialogSettings(section); + } + } + + protected WorkspacePatcher getPatcher() { + return fPatcher; + } + + protected IStorage getPatch() { + return patch; + } + + IResource getTarget() { + return fPatcher.getTarget(); + } + + /* (non-Javadoc) + * Method declared on IWizard. + */ + public void addPages() { + if (patch == null) + addPage(fPatchWizardPage = new InputPatchPage(this)); + if (patch == null || !fPatcher.isWorkspacePatch()) + addPage(fPatchTargetPage = new PatchTargetPage(fPatcher)); + fPreviewPage2 = new PreviewPatchPage2(fPatcher, fConfiguration); + addPage(fPreviewPage2); + } + + /* (non-Javadoc) + * Method declared on IWizard. + */ + public boolean performFinish() { + + IWizardPage currentPage = getContainer().getCurrentPage(); + if (currentPage.getName().equals(PreviewPatchPage2.PREVIEWPATCHPAGE_NAME)){ + PreviewPatchPage2 previewPage = (PreviewPatchPage2) currentPage; + previewPage.ensureContentsSaved(); + } + + if (fPatchWizardPage != null){ + // make sure that the patch has been read + if (!fPatchWizardPage.isPatchRead()) + fPatchWizardPage.readInPatch(); + fPatcher.refresh(); + } else { + //either we have a patch from the patch input page or one has + //been specified; double check this + Assert.isNotNull(patch); + //make sure that the patch has been read in + Assert.isTrue(patchReadIn); + } + + if (!currentPage.getName().equals(PreviewPatchPage2.PREVIEWPATCHPAGE_NAME) && fPatcher.hasRejects()){ + if (!MessageDialog.openConfirm(getShell(), PatchMessages.PatchWizard_0, PatchMessages.PatchWizard_1)) { + return false; + } + } + + try { + // create scheduling rule based on the type of patch - single or workspace + ISchedulingRule scheduleRule = null; + if (fPatcher.isWorkspacePatch()) { + // workspace patch + ISchedulingRule[] projectRules = fPatcher.getTargetProjects(); + scheduleRule = new MultiRule(projectRules); + } else { + // single patch + IResource resource = getTarget(); + if (resource.getType() == IResource.FILE) { + // For a file, use the modify rule for the parent since we may need to include a reject file + resource = resource.getParent(); + } + scheduleRule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(resource); + } + + WorkspaceModifyOperation op = new WorkspaceModifyOperation(scheduleRule) { + protected void execute(IProgressMonitor monitor) throws InvocationTargetException { + try { + fPatcher.applyAll(monitor, new Patcher.IFileValidator() { + public boolean validateResources(IFile[] resoures) { + return Utilities.validateResources(resoures, getShell(), PatchMessages.PatchWizard_title); + } + }); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + }; + getContainer().run(true, false, op); + + } catch (InvocationTargetException e) { + ExceptionHandler.handle(e, PatchMessages.PatchWizard_title, PatchMessages.PatchWizard_unexpectedException_message); + } catch (InterruptedException e) { + // cannot happen + // NeedWork: use assert! + } + + // Save the dialog settings + if (fHasNewDialogSettings) { + IDialogSettings workbenchSettings = CompareUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section = workbenchSettings.getSection(DIALOG_SETTINGS_KEY); + section = workbenchSettings.addNewSection(DIALOG_SETTINGS_KEY); + setDialogSettings(section); + } + + if (fPatchWizardPage != null) + fPatchWizardPage.saveWidgetValues(); + fPreviewPage2.saveWidgetValues(); + return true; + } + + public void showPage(IWizardPage page) { + getContainer().showPage(page); + } + + public IWizardPage getNextPage(IWizardPage page) { + //no patch has been read in yet, input patch page + if (!patchReadIn) + return fPatchWizardPage; + + //Check to see if we're already on the patch target page and if + //a target has been set - if it has return the next page in sequence (the preview patch page) + if (page instanceof PatchTargetPage && getTarget() != null) { + return super.getNextPage(page); + } else if (page instanceof InputPatchPage && !fPatcher.isWorkspacePatch()) { + //Check to see if we need a target + return fPatchTargetPage; + } + return super.getNextPage(page); + } + + /** + * Used to report that the patch has + * + */ + protected void patchReadIn() { + patchReadIn = true; + } + + public CompareConfiguration getCompareConfiguration() { + return fConfiguration; + } + + public boolean canFinish() { + IWizardPage currentPage = getContainer().getCurrentPage(); + if (currentPage.getName().equals(PreviewPatchPage2.PREVIEWPATCHPAGE_NAME)){ + return currentPage.isPageComplete(); + } + return super.canFinish(); + } + +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java new file mode 100644 index 000000000..bd61ac389 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PatchWizardDialog.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2005, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Shell; + +public class PatchWizardDialog extends WizardDialog { + private static final String PATCH_WIZARD_SETTINGS_SECTION = "PatchWizard"; //$NON-NLS-1$ + + public PatchWizardDialog(Shell parent, IWizard wizard) { + super(parent, wizard); + + setShellStyle(getShellStyle() | SWT.RESIZE); + setMinimumPageSize(700, 500); + } + + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings settings = CompareUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section = settings.getSection(PATCH_WIZARD_SETTINGS_SECTION); + if (section == null) { + section = settings.addNewSection(PATCH_WIZARD_SETTINGS_SECTION); + } + return section; + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java new file mode 100644 index 000000000..a059819db --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Patcher.java @@ -0,0 +1,763 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Martin Burger <m@rtin-burger.de> patch for #93810 and #93901 + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.compare.internal.core.Messages; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.compare.internal.core.patch.PatchReader; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.patch.IHunkFilter; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; + +/** + * A Patcher + * - knows how to parse various patch file formats into some in-memory structure, + * - holds onto the parsed data and the options to use when applying the patches, + * - knows how to apply the patches to files and folders. + */ +public class Patcher implements IHunkFilter { + + static protected final String REJECT_FILE_EXTENSION= ".rej"; //$NON-NLS-1$ + + static protected final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker"; //$NON-NLS-1$ + + /** + * Property used to associate a patcher with a {@link PatchConfiguration} + */ + public static final String PROP_PATCHER = "org.eclipse.compare.patcher"; //$NON-NLS-1$ + + public interface IFileValidator { + boolean validateResources(IFile[] array); + } + + // diff formats + // private static final int CONTEXT= 0; + // private static final int ED= 1; + // private static final int NORMAL= 2; + // private static final int UNIFIED= 3; + + private FilePatch2[] fDiffs; + private IResource fTarget; + // patch options + private Set disabledElements = new HashSet(); + private Map diffResults = new HashMap(); + private final Map contentCache = new HashMap(); + private Set mergedHunks = new HashSet(); + + private final PatchConfiguration configuration; + private boolean fGenerateRejectFile = false; + + public Patcher() { + configuration = new PatchConfiguration(); + configuration.setProperty(PROP_PATCHER, this); + configuration.addHunkFilter(this); + } + + /* + * Returns an array of Diffs after a sucessfull call to <code>parse</code>. + * If <code>parse</code> hasn't been called returns <code>null</code>. + */ + public FilePatch2[] getDiffs() { + if (fDiffs == null) + return new FilePatch2[0]; + return fDiffs; + } + + public IPath getPath(FilePatch2 diff) { + return diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setStripPrefixSegments(int strip) { + if (strip != getConfiguration().getPrefixSegmentStripCount()) { + getConfiguration().setPrefixSegmentStripCount(strip); + return true; + } + return false; + } + + int getStripPrefixSegments() { + return getConfiguration().getPrefixSegmentStripCount(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setFuzz(int fuzz) { + if (fuzz != getConfiguration().getFuzz()) { + getConfiguration().setFuzz(fuzz); + return true; + } + return false; + } + + public int getFuzz(){ + return getConfiguration().getFuzz(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setIgnoreWhitespace(boolean ignoreWhitespace) { + if (ignoreWhitespace != getConfiguration().isIgnoreWhitespace()) { + getConfiguration().setIgnoreWhitespace(ignoreWhitespace); + return true; + } + return false; + } + + public boolean isIgnoreWhitespace() { + return getConfiguration().isIgnoreWhitespace(); + } + + public boolean isGenerateRejectFile() { + return fGenerateRejectFile; + } + + public void setGenerateRejectFile(boolean generateRejectFile) { + fGenerateRejectFile = generateRejectFile; + } + + //---- parsing patch files + + public void parse(IStorage storage) throws IOException, CoreException { + BufferedReader reader = Utilities.createReader(storage); + try { + parse(reader); + } finally { + try { + reader.close(); + } catch (IOException e) { //ignored + } + } + } + + public void parse(BufferedReader reader) throws IOException { + PatchReader patchReader = new PatchReader() { + protected FilePatch2 createFileDiff(IPath oldPath, long oldDate, + IPath newPath, long newDate) { + return new FilePatch(oldPath, oldDate, newPath, newDate); + } + }; + patchReader.parse(reader); + patchParsed(patchReader); + } + + protected void patchParsed(PatchReader patchReader) { + fDiffs = patchReader.getDiffs(); + } + + public void countLines() { + FilePatch2[] fileDiffs = getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + int addedLines = 0; + int removedLines = 0; + FilePatch2 fileDiff = fileDiffs[i]; + for (int j = 0; j < fileDiff.getHunkCount(); j++) { + IHunk hunk = fileDiff.getHunks()[j]; + String[] lines = ((Hunk) hunk).getLines(); + for (int k = 0; k < lines.length; k++) { + char c = lines[k].charAt(0); + switch (c) { + case '+': + addedLines++; + continue; + case '-': + removedLines++; + continue; + } + } + } + fileDiff.setAddedLines(addedLines); + fileDiff.setRemovedLines(removedLines); + } + } + + //---- applying a patch file + + public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException { + + int i; + + IFile singleFile= null; // file to be patched + IContainer container= null; + if (fTarget instanceof IContainer) + container= (IContainer) fTarget; + else if (fTarget instanceof IFile) { + singleFile= (IFile) fTarget; + container= singleFile.getParent(); + } else { + Assert.isTrue(false); + } + + // get all files to be modified in order to call validateEdit + List list= new ArrayList(); + if (singleFile != null) + list.add(singleFile); + else { + for (i= 0; i < fDiffs.length; i++) { + FilePatch2 diff= fDiffs[i]; + if (isEnabled(diff)) { + switch (diff.getDiffType(isReversed())) { + case FilePatch2.CHANGE: + list.add(createPath(container, getPath(diff))); + break; + } + } + } + } + if (! validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { + return; + } + + final int WORK_UNIT= 10; + if (pm != null) { + String message= Messages.Patcher_0; + pm.beginTask(message, fDiffs.length*WORK_UNIT); + } + + for (i= 0; i < fDiffs.length; i++) { + + int workTicks= WORK_UNIT; + + FilePatch2 diff= fDiffs[i]; + if (isEnabled(diff)) { + + IPath path= getPath(diff); + if (pm != null) + pm.subTask(path.toString()); + + IFile file= singleFile != null + ? singleFile + : createPath(container, path); + + List failed= new ArrayList(); + + int type= diff.getDiffType(isReversed()); + switch (type) { + case FilePatch2.ADDITION: + // patch it and collect rejected hunks + List result= apply(diff, file, true, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + case FilePatch2.DELETION: + file.delete(true, true, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + case FilePatch2.CHANGE: + // patch it and collect rejected hunks + result= apply(diff, file, false, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + } + + if (isGenerateRejectFile() && failed.size() > 0) { + IPath pp = getRejectFilePath(path); + file= createPath(container, pp); + if (file != null) { + store(getRejected(failed), file, pm); + try { + IMarker marker= file.createMarker(MARKER_TYPE); + marker.setAttribute(IMarker.MESSAGE, Messages.Patcher_1); + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + } catch (CoreException ex) { + // NeedWork + } + } + } + } + + if (pm != null) { + if (pm.isCanceled()) + break; + if (workTicks > 0) + pm.worked(workTicks); + } + } + } + + private IPath getRejectFilePath(IPath path) { + IPath pp= null; + if (path.segmentCount() > 1) { + pp= path.removeLastSegments(1); + pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION); + } else + pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION); + return pp; + } + + List apply(FilePatch2 diff, IFile file, boolean create, List failedHunks) { + FileDiffResult result = getDiffResult(diff); + List lines = LineReader.load(file, create); + result.patch(lines, null); + failedHunks.addAll(result.getFailedHunks()); + if (hasCachedContents(diff)) { + // Used the cached contents since they would have been provided by the user + return getCachedLines(diff); + } else if (!result.hasMatches()) { + // Return null if there were no matches + return null; + } + return result.getLines(); + } + + /* + * Converts the string into bytes and stores them in the given file. + */ + protected void store(String contents, IFile file, IProgressMonitor pm) throws CoreException { + + byte[] bytes; + try { + bytes= contents.getBytes(Utilities.getCharset(file)); + } catch (UnsupportedEncodingException x) { + // uses default encoding + bytes= contents.getBytes(); + } + + store(bytes,file, pm); + } + + protected void store(byte[] bytes, IFile file, IProgressMonitor pm) throws CoreException { + InputStream is= new ByteArrayInputStream(bytes); + try { + if (file.exists()) { + file.setContents(is, false, true, pm); + } else { + file.create(is, false, pm); + } + } finally { + if (is != null) + try { + is.close(); + } catch(IOException ex) { + // silently ignored + } + } + } + + public boolean isPreserveLineDelimeters() { + return true; + } + + public static String getRejected(List failedHunks) { + if (failedHunks.size() <= 0) + return null; + + String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuffer sb= new StringBuffer(); + Iterator iter= failedHunks.iterator(); + while (iter.hasNext()) { + Hunk hunk= (Hunk) iter.next(); + sb.append(hunk.getRejectedDescription()); + sb.append(lineSeparator); + sb.append(hunk.getContent()); + } + return sb.toString(); + } + + /* + * Ensures that a file with the given path exists in + * the given container. Folder are created as necessary. + */ + protected IFile createPath(IContainer container, IPath path) throws CoreException { + if (path.segmentCount() > 1) { + IContainer childContainer; + if (container instanceof IWorkspaceRoot) { + IProject project = ((IWorkspaceRoot)container).getProject(path.segment(0)); + if (!project.exists()) + project.create(null); + if (!project.isOpen()) + project.open(null); + childContainer = project; + } else { + IFolder f= container.getFolder(path.uptoSegment(1)); + if (!f.exists()) + f.create(false, true, null); + childContainer = f; + } + return createPath(childContainer, path.removeFirstSegments(1)); + } + // a leaf + return container.getFile(path); + } + + public IResource getTarget() { + return fTarget; + } + + public void setTarget(IResource target) { + fTarget= target; + } + + + public IFile getTargetFile(FilePatch2 diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + return existsInTarget(path); + } + + /** + * Iterates through all of the resources contained in the Patch Wizard target + * and looks to for a match to the passed in file + * @param path + * @return IFile which matches the passed in path or null if none found + */ + public IFile existsInTarget(IPath path) { + if (fTarget instanceof IFile) { // special case + IFile file= (IFile) fTarget; + if (matches(file.getFullPath(), path)) + return file; + } else if (fTarget instanceof IContainer) { + IContainer c= (IContainer) fTarget; + if (c.exists(path)) + return c.getFile(path); + } + return null; + } + + /** + * Returns true if path completely matches the end of fullpath + * @param fullpath + * @param path + * @return true if path matches, false otherwise + */ + private boolean matches(IPath fullpath, IPath path) { + for (IPath p= fullpath; path.segmentCount()<=p.segmentCount(); p= p.removeFirstSegments(1)) { + if (p.equals(path)) + return true; + } + return false; + } + + public int calculatePrefixSegmentCount() { + //Update prefix count - go through all of the diffs and find the smallest + //path segment contained in all diffs. + int length= 99; + if (fDiffs!=null) + for (int i= 0; i<fDiffs.length; i++) { + FilePatch2 diff= fDiffs[i]; + length= Math.min(length, diff.segmentCount()); + } + return length; + } + + public void addDiff(FilePatch2 newDiff){ + FilePatch2[] temp = new FilePatch2[fDiffs.length + 1]; + System.arraycopy(fDiffs,0, temp, 0, fDiffs.length); + temp[fDiffs.length] = newDiff; + fDiffs = temp; + } + + public void removeDiff(FilePatch2 diffToRemove){ + FilePatch2[] temp = new FilePatch2[fDiffs.length - 1]; + int counter = 0; + for (int i = 0; i < fDiffs.length; i++) { + if (fDiffs[i] != diffToRemove){ + temp[counter++] = fDiffs[i]; + } + } + fDiffs = temp; + } + + public void setEnabled(Object element, boolean enabled) { + if (element instanceof DiffProject) + setEnabledProject((DiffProject) element, enabled); + if (element instanceof FilePatch2) + setEnabledFile((FilePatch2)element, enabled); + if (element instanceof Hunk) + setEnabledHunk((Hunk) element, enabled); + } + + private void setEnabledProject(DiffProject projectDiff, boolean enabled) { + FilePatch2[] diffFiles = projectDiff.getFileDiffs(); + for (int i = 0; i < diffFiles.length; i++) { + setEnabledFile(diffFiles[i], enabled); + } + } + + private void setEnabledFile(FilePatch2 fileDiff, boolean enabled) { + IHunk[] hunks = fileDiff.getHunks(); + for (int i = 0; i < hunks.length; i++) { + setEnabledHunk((Hunk) hunks[i], enabled); + } + } + + private void setEnabledHunk(Hunk hunk, boolean enabled) { + if (enabled) { + disabledElements.remove(hunk); + FilePatch2 file = hunk.getParent(); + disabledElements.remove(file); + DiffProject project = file.getProject(); + if (project != null) + disabledElements.remove(project); + } else { + disabledElements.add(hunk); + FilePatch2 file = hunk.getParent(); + if (disabledElements.containsAll(Arrays.asList(file.getHunks()))) { + disabledElements.add(file); + DiffProject project = file.getProject(); + if (project != null + && disabledElements.containsAll(Arrays.asList(project + .getFileDiffs()))) + disabledElements.add(project); + } + } + } + + public boolean isEnabled(Object element) { + if (disabledElements.contains(element)) + return false; + Object parent = getElementParent(element); + if (parent == null) + return true; + return isEnabled(parent); + } + + protected Object getElementParent(Object element) { + if (element instanceof Hunk) { + Hunk hunk = (Hunk) element; + return hunk.getParent(); + } + return null; + } + + /** + * Calculate the fuzz factor that will allow the most hunks to be matched. + * @param monitor a progress monitor + * @return the fuzz factor or <code>-1</code> if no hunks could be matched + */ + public int guessFuzzFactor(IProgressMonitor monitor) { + try { + monitor.beginTask(Messages.Patcher_2, IProgressMonitor.UNKNOWN); + FilePatch2[] diffs= getDiffs(); + if (diffs==null||diffs.length<=0) + return -1; + int fuzz= -1; + for (int i= 0; i<diffs.length; i++) { + FilePatch2 d= diffs[i]; + IFile file= getTargetFile(d); + if (file != null && file.exists()) { + List lines= LineReader.load(file, false); + FileDiffResult result = getDiffResult(d); + int f = result.calculateFuzz(lines, monitor); + if (f > fuzz) + fuzz = f; + } + } + return fuzz; + } finally { + monitor.done(); + } + } + + public void refresh() { + diffResults.clear(); + refresh(getDiffs()); + } + + public void refresh(FilePatch2[] diffs) { + for (int i = 0; i < diffs.length; i++) { + FilePatch2 diff = diffs[i]; + FileDiffResult result = getDiffResult(diff); + ((WorkspaceFileDiffResult)result).refresh(); + } + } + + public FileDiffResult getDiffResult(FilePatch2 diff) { + FileDiffResult result = (FileDiffResult)diffResults.get(diff); + if (result == null) { + result = new WorkspaceFileDiffResult(diff, getConfiguration()); + diffResults.put(diff, result); + } + return result; + } + + public PatchConfiguration getConfiguration() { + return configuration; + } + + /** + * Return the project that contains this diff or <code>null</code> + * if the patch is not a workspace patch. + * @param diff the diff + * @return the project that contains the diff + */ + public DiffProject getProject(FilePatch2 diff) { + return diff.getProject(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setReversed(boolean reverse) { + if (getConfiguration().isReversed() != reverse) { + getConfiguration().setReversed(reverse); + refresh(); + return true; + } + return false; + } + + public boolean isReversed() { + return getConfiguration().isReversed(); + } + + /** + * Cache the contents for the given file diff. These contents + * will be used for the diff when the patch is applied. When the + * patch is applied, it is assumed that the provided contents + * already have all relevant hunks applied. + * @param diff the file diff + * @param contents the contents for the file diff + */ + public void cacheContents(FilePatch2 diff, byte[] contents) { + contentCache.put(diff, contents); + } + + /** + * Return whether contents have been cached for the + * given file diff. + * @param diff the file diff + * @return whether contents have been cached for the file diff + * @see #cacheContents(FilePatch2, byte[]) + */ + public boolean hasCachedContents(FilePatch2 diff) { + return contentCache.containsKey(diff); + } + + /** + * Return the content lines that are cached for the given + * file diff. + * @param diff the file diff + * @return the content lines that are cached for the file diff + */ + public List getCachedLines(FilePatch2 diff) { + byte[] contents = (byte[])contentCache.get(diff); + if (contents != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(contents))); + return LineReader.readLines(reader); + } + return null; + } + + /** + * Return the contents that are cached for the given diff or + * <code>null</code> if there is no contents cached. + * @param diff the diff + * @return the contents that are cached for the given diff or + * <code>null</code> + */ + public byte[] getCachedContents(FilePatch2 diff) { + return (byte[])contentCache.get(diff); + } + + /** + * Return whether the patcher has any cached contents. + * @return whether the patcher has any cached contents + */ + public boolean hasCachedContents() { + return !contentCache.isEmpty(); + } + + /** + * Clear any cached contents. + */ + public void clearCachedContents() { + contentCache.clear(); + mergedHunks.clear(); + } + + public void setProperty(String key, Object value) { + getConfiguration().setProperty(key, value); + } + + public Object getProperty(String key) { + return getConfiguration().getProperty(key); + } + + public boolean isManuallyMerged(Hunk hunk) { + return mergedHunks.contains(hunk); + } + + public void setManuallyMerged(Hunk hunk, boolean merged) { + if (merged) + mergedHunks.add(hunk); + else + mergedHunks.remove(hunk); + } + + public IProject getTargetProject(FilePatch2 diff) { + DiffProject dp = getProject(diff); + if (dp != null) + return Utilities.getProject(dp); + IResource tr = getTarget(); + if (tr instanceof IWorkspaceRoot) { + IWorkspaceRoot root = (IWorkspaceRoot) tr; + return root.getProject(diff.getPath(isReversed()).segment(0)); + } + return tr.getProject(); + } + + public static Patcher getPatcher(PatchConfiguration configuration) { + return (Patcher)configuration.getProperty(PROP_PATCHER); + } + + public boolean hasRejects() { + for (Iterator iterator = diffResults.values().iterator(); iterator.hasNext();) { + FileDiffResult result = (FileDiffResult) iterator.next(); + if (result.hasRejects()) + return true; + } + return false; + } + + public boolean select(IHunk hunk) { + return isEnabled(hunk); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java new file mode 100644 index 000000000..7414cbef3 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/PreviewPatchPage2.java @@ -0,0 +1,747 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.regex.Pattern; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareUI; +import org.eclipse.compare.internal.ComparePreferencePage; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ICompareUIConstants; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.events.ExpansionAdapter; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.widgets.ExpandableComposite; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; + + +public class PreviewPatchPage2 extends WizardPage { + + protected final static String PREVIEWPATCHPAGE_NAME= "PreviewPatchPage"; //$NON-NLS-1$ + + private static final String EXPAND_PATCH_OPTIONS = "expandPatchOptions"; //$NON-NLS-1$ + private static final String GENERATE_REJECTS = "generateRejects"; //$NON-NLS-1$ + + final WorkspacePatcher fPatcher; + private final CompareConfiguration fConfiguration; + private PatchCompareEditorInput fInput; + + private Combo fStripPrefixSegments; + private Text fFuzzField; + private Label addedRemovedLines; + + private Action fExcludeAction; + private Action fIncludeAction; + private Action fIgnoreWhiteSpace; + private Action fReversePatch; + private Action fMoveAction; + + protected boolean pageRecalculate= true; + + private IDialogSettings settings; + private ExpandableComposite patchOptions; + private Button generateRejects; + private FormToolkit fToolkit; + + public PreviewPatchPage2(WorkspacePatcher patcher, CompareConfiguration configuration) { + super(PREVIEWPATCHPAGE_NAME, PatchMessages.PreviewPatchPage_title, null); + setDescription(PatchMessages.PreviewPatchPage2_8); + Assert.isNotNull(patcher); + Assert.isNotNull(configuration); + this.fPatcher = patcher; + this.fConfiguration = configuration; + this.fConfiguration.addPropertyChangeListener(new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + if (event.getProperty().equals(CompareConfiguration.IGNORE_WHITESPACE)){ + rebuildTree(); + } + } + }); + } + + public void createControl(Composite parent) { + fToolkit = new FormToolkit(parent.getDisplay()); + fToolkit.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); + + final Form form = fToolkit.createForm(parent); + Composite composite = form.getBody(); + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + initializeDialogUnits(parent); + + fInput = new PatchCompareEditorInput(getPatcher(), getCompareConfiguration()) { + protected void fillContextMenu(IMenuManager manager) { + if (isShowAll()) { + manager.add(fIncludeAction); + } + manager.add(fExcludeAction); + manager.add(new Separator()); + manager.add(fMoveAction); + } + }; + + buildPatchOptionsGroup(form); + + // Initialize the input + try { + fInput.run(null); + } catch (InterruptedException e) {//ignore + } catch (InvocationTargetException e) {//ignore + } + + Label label = new Label(composite, SWT.NONE); + label.setText(PatchMessages.PreviewPatchPage2_9); + Control c = fInput.createContents(composite); + initializeActions(); + fInput.contributeDiffViewerToolbarItems(getContributedActions(), getPatcher().isWorkspacePatch()); + fInput.getViewer().addSelectionChangedListener(new ISelectionChangedListener(){ + public void selectionChanged(SelectionChangedEvent event) { + ISelection s = event.getSelection(); + if (s != null && !s.isEmpty()) { + if (s instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) s; + updateActions(ss); + } + } + }}); + + c.setLayoutData(new GridData(GridData.FILL_BOTH)); + + addedRemovedLines = new Label(composite, SWT.NONE); + addedRemovedLines.setLayoutData(new GridData(GridData.FILL_HORIZONTAL + | GridData.VERTICAL_ALIGN_BEGINNING)); + + setControl(composite); + + restoreWidgetValues(); + + Dialog.applyDialogFont(composite); + } + + private void updateActions(IStructuredSelection ss) { + fExcludeAction.setEnabled(false); + fIncludeAction.setEnabled(false); + for (Iterator it = ss.iterator(); it.hasNext();) { + Object element = it.next(); + if (element instanceof PatchDiffNode) { + if (((PatchDiffNode) element).isEnabled()) { + fExcludeAction.setEnabled(true); + } else { + fIncludeAction.setEnabled(true); + } + } + } + } + + /** + * Makes sure that at least one hunk is checked off in the tree before + * allowing the patch to be applied. + */ + private void updateEnablements() { + boolean atLeastOneIsEnabled = false; + if (fInput != null) + atLeastOneIsEnabled = fInput.hasResultToApply(); + setPageComplete(atLeastOneIsEnabled); + } + + private Action[] getContributedActions() { + return new Action[]{ fIgnoreWhiteSpace }; + } + + private void initializeActions() { + + fMoveAction = new Action(PatchMessages.PreviewPatchPage2_RetargetAction, null) { + public void run() { + Shell shell = getShell(); + ISelection selection = fInput.getViewer().getSelection(); + PatchDiffNode node = null; + if (selection instanceof IStructuredSelection) { + IStructuredSelection ss = (IStructuredSelection) selection; + if (ss.getFirstElement() instanceof PatchDiffNode) { + node = (PatchDiffNode) ss.getFirstElement(); + } + } + if (node == null) + return; + final RetargetPatchElementDialog dialog = new RetargetPatchElementDialog(shell, fPatcher, node); + int returnCode = dialog.open(); + if (returnCode == Window.OK) { + // TODO: This could be a problem. We should only rebuild the affected nodes + rebuildTree(); + } + } + }; + fMoveAction .setToolTipText(PatchMessages.PreviewPatchPage2_RetargetTooltip); + fMoveAction.setEnabled(true); + fInput.getViewer().addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection sel= (IStructuredSelection) event.getSelection(); + Object obj= sel.getFirstElement(); + boolean enable = false; + if (obj instanceof PatchProjectDiffNode) { + enable = true; + } else if (obj instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) obj; + enable = node.getDiffResult().getDiffProblem(); + } else if (obj instanceof HunkDiffNode) { + enable = true; + } + fMoveAction.setEnabled(enable); + } + }); + + fExcludeAction = new Action(PatchMessages.PreviewPatchPage2_0) { + public void run() { + ISelection selection = fInput.getViewer().getSelection(); + if (selection instanceof TreeSelection){ + TreeSelection treeSelection = (TreeSelection) selection; + Iterator iter = treeSelection.iterator(); + while (iter.hasNext()){ + Object obj = iter.next(); + if (obj instanceof PatchDiffNode){ + PatchDiffNode node = ((PatchDiffNode) obj); + node.setEnabled(false); + // TODO: This may require a rebuild if matched hunks are shown + } + } + updateActions(treeSelection); + } + fInput.getViewer().refresh(); + } + }; + fExcludeAction.setEnabled(true); + + fIncludeAction = new Action(PatchMessages.PreviewPatchPage2_1) { + public void run() { + ISelection selection = fInput.getViewer().getSelection(); + if (selection instanceof TreeSelection){ + TreeSelection treeSelection = (TreeSelection) selection; + Iterator iter = treeSelection.iterator(); + while (iter.hasNext()){ + Object obj = iter.next(); + if (obj instanceof PatchDiffNode){ + PatchDiffNode node = ((PatchDiffNode) obj); + node.setEnabled(true); + // TODO: This may require a rebuild if matched hunks are shown + } + } + updateActions(treeSelection); + } + fInput.getViewer().refresh(); + } + }; + fIncludeAction.setEnabled(true); + + fIgnoreWhiteSpace = new Action(PatchMessages.PreviewPatchPage2_IgnoreWSAction, CompareUIPlugin.getImageDescriptor(ICompareUIConstants.IGNORE_WHITESPACE_ENABLED)){ + public void run(){ + try { + getContainer().run(false, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(PatchMessages.PreviewPatchPage2_IgnoreWhitespace, IProgressMonitor.UNKNOWN); + if (isChecked() != getPatcher().isIgnoreWhitespace()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_2)) { + if (getPatcher().setIgnoreWhitespace(isChecked())){ + getCompareConfiguration().setProperty(CompareConfiguration.IGNORE_WHITESPACE, new Boolean(isChecked())); + } + } else { + fIgnoreWhiteSpace.setChecked(!isChecked()); + } + } + monitor.done(); + } + }); + } catch (InvocationTargetException e) { //ignore + } catch (InterruptedException e) { //ignore + } + } + }; + fIgnoreWhiteSpace.setChecked(false); + fIgnoreWhiteSpace.setToolTipText(PatchMessages.PreviewPatchPage2_IgnoreWSTooltip); + fIgnoreWhiteSpace.setDisabledImageDescriptor(CompareUIPlugin.getImageDescriptor(ICompareUIConstants.IGNORE_WHITESPACE_DISABLED)); + + fReversePatch = new Action(PatchMessages.PreviewPatchPage_ReversePatch_text){ + public void run(){ + try { + getContainer().run(true, true, new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(PatchMessages.PreviewPatchPage2_CalculateReverse, IProgressMonitor.UNKNOWN); + if (isChecked() != getPatcher().isReversed()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_3)) { + if (getPatcher().setReversed(isChecked())){ + rebuildTree(); + } + } else { + fReversePatch.setChecked(!isChecked()); + } + } + monitor.done(); + } + }); + } catch (InvocationTargetException e) { //ignore + } catch (InterruptedException e) { //ignore + } + + } + + }; + fReversePatch.setChecked(false); + fReversePatch.setToolTipText(PatchMessages.PreviewPatchPage_ReversePatch_text); + } + + public void setVisible(boolean visible) { + super.setVisible(visible); + //Need to handle input and rebuild tree only when becoming visible + if (visible){ + fillSegmentCombo(); + // TODO: We should only do this if the tree needs to be rebuilt + rebuildTree(); + updateEnablements(); + addedRemovedLines.setText(countLines()); + // expand the first tree item i.e. change + getCompareConfiguration().getContainer().getNavigator().selectChange(true); + getContainer().updateButtons(); + getShell().getDefaultButton().setFocus(); + } + } + + private boolean promptToRebuild(final String promptToConfirm){ + final Control ctrl = getControl(); + final boolean[] result = new boolean[] { false }; + if (ctrl != null && !ctrl.isDisposed()){ + Runnable runnable = new Runnable() { + public void run() { + if (!ctrl.isDisposed()) { + // flush any viewers before prompting + try { + fInput.saveChanges(null); + } catch (CoreException e) { + CompareUIPlugin.log(e); + } + result[0] = fInput.confirmRebuild(promptToConfirm); + } + } + }; + if (Display.getCurrent() == null) + ctrl.getDisplay().syncExec(runnable); + else + runnable.run(); + } + return result[0]; + } + + private void rebuildTree(){ + final Control ctrl = getControl(); + if (ctrl != null && !ctrl.isDisposed()){ + Runnable runnable = new Runnable() { + public void run() { + if (!ctrl.isDisposed()) { + fInput.buildTree(); + updateEnablements(); + } + } + }; + if (Display.getCurrent() == null) + ctrl.getDisplay().syncExec(runnable); + else + runnable.run(); + } + } + + private void fillSegmentCombo() { + if (getPatcher().isWorkspacePatch()) { + fStripPrefixSegments.setEnabled(false); + } else { + fStripPrefixSegments.setEnabled(true); + int length= 99; + if (fStripPrefixSegments!=null && pageRecalculate) { + length= getPatcher().calculatePrefixSegmentCount(); + if (length!=99) { + for (int k= 1; k<length; k++) + fStripPrefixSegments.add(Integer.toString(k)); + pageRecalculate= false; + } + } + } + } + /* + * Create the group for setting various patch options + */ + private void buildPatchOptionsGroup(final Form form) { + Composite parent = form.getBody(); + + patchOptions = fToolkit.createExpandableComposite(parent, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT); + patchOptions.setText(PatchMessages.PreviewPatchPage_PatchOptions_title); + patchOptions.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT)); + patchOptions.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 3, 1)); + patchOptions.addExpansionListener(new ExpansionAdapter() { + public void expansionStateChanged(ExpansionEvent e) { + form.layout(); + } + }); + + Composite c = new Composite(patchOptions, SWT.NONE); + patchOptions.setClient(c); + patchOptions.setExpanded(true); + GridLayout gl= new GridLayout(); gl.numColumns= 3; + c.setLayout(gl); + c.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL|GridData.GRAB_HORIZONTAL)); + + // 1st row + createStripSegmentCombo(c); + createShowMatchedToggle(c); + createFuzzFactorChooser(c); + + // 2nd row + createReversePatchToggle(c); + createShowRemovedToggle(c); + createGenerateRejectsToggle(c); + + // register listeners + final WorkspacePatcher patcher= getPatcher(); + if (fStripPrefixSegments!=null) + fStripPrefixSegments.addSelectionListener( + new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (patcher.getStripPrefixSegments() != getStripPrefixSegments()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_4)) { + if (patcher.setStripPrefixSegments(getStripPrefixSegments())) + rebuildTree(); + } + } + } + } + ); + + + fFuzzField.addModifyListener( + new ModifyListener() { + public void modifyText(ModifyEvent e) { + if (patcher.getFuzz() != getFuzzFactor()) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_5)) { + if (patcher.setFuzz(getFuzzFactor())) + rebuildTree(); + } else { + fFuzzField.setText(Integer.toString(patcher.getFuzz())); + } + } + } + }); + } + + private void createFuzzFactorChooser(Composite parent) { + final WorkspacePatcher patcher= getPatcher(); + Composite pair= new Composite(parent, SWT.NONE); + GridLayout gl= new GridLayout(); gl.numColumns= 3; gl.marginHeight= gl.marginWidth= 0; + pair.setLayout(gl); + GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL); + pair.setLayoutData(gd); + + Label l= new Label(pair, SWT.NONE); + l.setText(PatchMessages.PreviewPatchPage_FuzzFactor_text); + l.setToolTipText(PatchMessages.PreviewPatchPage_FuzzFactor_tooltip); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER|GridData.HORIZONTAL_ALIGN_BEGINNING|GridData.GRAB_HORIZONTAL); + l.setLayoutData(gd); + + fFuzzField= new Text(pair, SWT.BORDER); + fFuzzField.setText("0"); //$NON-NLS-1$ + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER | GridData.HORIZONTAL_ALIGN_END); + gd.widthHint= 30; + fFuzzField.setLayoutData(gd); + + Button b= new Button(pair, SWT.PUSH); + b.setText(PatchMessages.PreviewPatchPage_GuessFuzz_text); + b.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (promptToRebuild(PatchMessages.PreviewPatchPage2_6)) { + // Reset the fuzz. We don't use HunkResult.MAXIMUM_FUZZ_FACTOR on purpose here, + // in order to refresh the tree the result of the calculation needs to be different + // than the fuzz set in the configuration (see fFuzzField modify listener). + patcher.setFuzz(-1); + int fuzz= guessFuzzFactor(patcher); + if (fuzz>=0) + fFuzzField.setText(Integer.toString(fuzz)); + } + } + } + ); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER); + int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); + Point minSize = b.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + gd.widthHint = Math.max(widthHint, minSize.x); + b.setLayoutData(gd); + } + + private void createGenerateRejectsToggle(Composite pair) { + generateRejects = new Button(pair, SWT.CHECK); + generateRejects.setText(PatchMessages.HunkMergePage_GenerateRejectFile); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + generateRejects.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getPatcher().setGenerateRejectFile( + generateRejects.getSelection()); + } + }); + generateRejects.setSelection(false); + generateRejects.setLayoutData(gd); + } + + private void createShowRemovedToggle(Composite pair) { + final Button showRemoved = new Button(pair, SWT.CHECK); + showRemoved.setText(PatchMessages.PreviewPatchPage2_7); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + showRemoved.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + fInput.setShowAll(showRemoved.getSelection()); + fInput.updateTree(); + } + }); + showRemoved.setSelection(fInput.isShowAll()); + showRemoved.setLayoutData(gd); + } + + private void createReversePatchToggle(Composite pair) { + final Button reversePatch = new Button(pair, SWT.CHECK); + reversePatch.setText(PatchMessages.PreviewPatchPage_ReversePatch_text); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + reversePatch.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + if (fReversePatch != null) { + fReversePatch.setChecked(reversePatch.getSelection()); + fReversePatch.run(); + if (fReversePatch.isChecked() != reversePatch.getSelection()) { + reversePatch.setSelection(fReversePatch.isChecked()); + } + } + } + }); + reversePatch.setSelection(getPatcher().isReversed()); + reversePatch.setLayoutData(gd); + } + + private void createStripSegmentCombo(Composite parent) { + final WorkspacePatcher patcher= getPatcher(); + + Composite pair= new Composite(parent, SWT.NONE); + GridLayout gl= new GridLayout(); gl.numColumns= 2; gl.marginHeight= gl.marginWidth= 0; + pair.setLayout(gl); + GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL); + pair.setLayoutData(gd); + + Label l= new Label(pair, SWT.NONE); + l.setText(PatchMessages.PreviewPatchPage_IgnoreSegments_text); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER|GridData.HORIZONTAL_ALIGN_BEGINNING); + l.setLayoutData(gd); + + fStripPrefixSegments= new Combo(pair, SWT.DROP_DOWN|SWT.READ_ONLY|SWT.SIMPLE); + int prefixCnt= patcher.getStripPrefixSegments(); + String prefix= Integer.toString(prefixCnt); + fStripPrefixSegments.add(prefix); + fStripPrefixSegments.setText(prefix); + gd= new GridData(GridData.VERTICAL_ALIGN_CENTER|GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.GRAB_HORIZONTAL); + fStripPrefixSegments.setLayoutData(gd); + } + + private void createShowMatchedToggle(Composite parent) { + final Button showMatched = new Button(parent, SWT.CHECK); + showMatched.setText(PatchMessages.PreviewPatchPage2_ShowMatched); + GridData gd = new GridData(GridData.VERTICAL_ALIGN_CENTER + | GridData.HORIZONTAL_ALIGN_BEGINNING + | GridData.GRAB_HORIZONTAL); + showMatched.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + fInput.setShowMatched(showMatched.getSelection()); + rebuildTree(); + } + }); + showMatched.setSelection(fInput.isShowMatched()); + showMatched.setLayoutData(gd); + } + + public int getFuzzFactor() { + int fuzzFactor= 0; + if (fFuzzField!=null) { + String s= fFuzzField.getText(); + try { + fuzzFactor= Integer.parseInt(s); + } catch (NumberFormatException ex) { + // silently ignored + } + } + return fuzzFactor; + } + + public int getStripPrefixSegments() { + int stripPrefixSegments= 0; + if (fStripPrefixSegments!=null) { + String s= fStripPrefixSegments.getText(); + try { + stripPrefixSegments= Integer.parseInt(s); + } catch (NumberFormatException ex) { + // silently ignored + } + } + return stripPrefixSegments; + } + + private int guessFuzzFactor(final WorkspacePatcher patcher) { + final int[] result= new int[] { -1 }; + try { + PlatformUI.getWorkbench().getProgressService().run(true, true, + new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) { + result[0]= patcher.guessFuzzFactor(monitor); + } + } + ); + } catch (InvocationTargetException ex) { + // NeedWork + } catch (InterruptedException ex) { + // NeedWork + } + return result[0]; + } + + public void ensureContentsSaved() { + try { + fInput.saveChanges(new NullProgressMonitor()); + } catch (CoreException e) { + //ignore + } + } + + public WorkspacePatcher getPatcher() { + return fPatcher; + } + + public CompareConfiguration getCompareConfiguration() { + return fConfiguration; + } + + private void restoreWidgetValues() { + IDialogSettings dialogSettings = CompareUI.getPlugin().getDialogSettings(); + settings = dialogSettings.getSection(PREVIEWPATCHPAGE_NAME); + if (settings == null) { + settings = dialogSettings.addNewSection(PREVIEWPATCHPAGE_NAME); + } + if (settings != null) { + if (settings.get(EXPAND_PATCH_OPTIONS) != null) + patchOptions.setExpanded(settings.getBoolean(EXPAND_PATCH_OPTIONS)); + if (settings.get(GENERATE_REJECTS) != null) { + generateRejects.setSelection(settings.getBoolean(GENERATE_REJECTS)); + getPatcher().setGenerateRejectFile(generateRejects.getSelection()); + } + } + } + + void saveWidgetValues() { + settings.put(EXPAND_PATCH_OPTIONS, patchOptions.isExpanded()); + settings.put(GENERATE_REJECTS, generateRejects.getSelection()); + } + + private String countLines() { + int added = 0, removed = 0; + + IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore(); + String addedLinesRegex = store.getString(ComparePreferencePage.ADDED_LINES_REGEX); + String removedLinesRegex = store.getString(ComparePreferencePage.REMOVED_LINES_REGEX); + + if ((addedLinesRegex == null || "".equals(addedLinesRegex)) //$NON-NLS-1$ + && (removedLinesRegex == null || "".equals(removedLinesRegex))) { //$NON-NLS-1$ + + fPatcher.countLines(); + FilePatch2[] fileDiffs = fPatcher.getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + added += fileDiffs[i].getAddedLines(); + removed += fileDiffs[i].getRemovedLines(); + } + + } else { + + Pattern addedPattern = Pattern.compile(addedLinesRegex); + Pattern removedPattern = Pattern.compile(removedLinesRegex); + + FilePatch2[] fileDiffs = fPatcher.getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + IHunk[] hunks = fileDiffs[i].getHunks(); + for (int j = 0; j < hunks.length; j++) { + String[] lines = ((Hunk) hunks[j]).getLines(); + for (int k = 0; k < lines.length; k++) { + String line = lines[k]; + if (addedPattern.matcher(line).find()) + added++; + if (removedPattern.matcher(line).find()) + removed++; + } + } + } + } + + return NLS.bind(PatchMessages.PreviewPatchPage2_AddedRemovedLines, + new String[] { added + "", removed + "" }); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void dispose() { + fToolkit.dispose(); + super.dispose(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java new file mode 100644 index 000000000..3efc37027 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/RetargetPatchElementDialog.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.util.ArrayList; + +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.*; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; +import org.eclipse.ui.model.BaseWorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; + +class RetargetPatchElementDialog extends Dialog { + + private static class RetargetPatchContentProvider extends BaseWorkbenchContentProvider { + private final PatchDiffNode node; + public RetargetPatchContentProvider(PatchDiffNode node) { + this.node = node; + } + public Object[] getChildren(Object element) { + if (element instanceof IWorkspaceRoot) { + // Don't show closed projects + IProject[] allProjects= ((IWorkspaceRoot) element).getProjects(); + ArrayList accessibleProjects= new ArrayList(); + for (int i= 0; i<allProjects.length; i++) { + if (allProjects[i].isOpen()) { + accessibleProjects.add(allProjects[i]); + } + } + return accessibleProjects.toArray(); + } + // When retargeting a diff project, don't support expansion + if (element instanceof IProject && node instanceof PatchProjectDiffNode) { + return new Object[0]; + } + return super.getChildren(element); + } + } + + private final PatchDiffNode fSelectedNode; + private final WorkspacePatcher fPatcher; + private TreeViewer fViewer; + private IResource fSelectedResource; + + public RetargetPatchElementDialog(Shell shell, WorkspacePatcher patcher, PatchDiffNode node) { + super(shell); + Assert.isNotNull(patcher); + Assert.isNotNull(node); + setShellStyle(getShellStyle() | SWT.RESIZE); + this.fPatcher = patcher; + fSelectedNode= node; + } + + protected Control createButtonBar(Composite parent) { + Control control = super.createButtonBar(parent); + Button okButton = this.getButton(IDialogConstants.OK_ID); + okButton.setEnabled(false); + return control; + } + + protected Control createDialogArea(Composite parent) { + Composite composite= (Composite) super.createDialogArea(parent); + + initializeDialogUnits(parent); + + getShell().setText(PatchMessages.PreviewPatchPage_RetargetPatch); + + GridLayout layout= new GridLayout(); + layout.numColumns= 1; + layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); + layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); + composite.setLayout(layout); + final GridData data= new GridData(SWT.FILL, SWT.FILL, true, true); + composite.setLayoutData(data); + + //add controls to composite as necessary + Label label= new Label(composite, SWT.LEFT|SWT.WRAP); + label.setText(getTreeLabel()); + final GridData data2= new GridData(SWT.FILL, SWT.BEGINNING, true, false); + label.setLayoutData(data2); + + fViewer= new TreeViewer(composite, SWT.BORDER); + GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true); + gd.widthHint= 0; + gd.heightHint= 0; + fViewer.getTree().setLayoutData(gd); + fViewer.setContentProvider(new RetargetPatchContentProvider(fSelectedNode)); + fViewer.setLabelProvider(new WorkbenchLabelProvider()); + fViewer.setComparator(new ResourceComparator(ResourceComparator.NAME)); + fViewer.setInput(getViewerInput()); + IResource resource = getInitialSelection(); + if (resource != null) { + fViewer.setSelection(new StructuredSelection(resource)); + fViewer.expandToLevel(resource, 0); + } + setupListeners(); + + Dialog.applyDialogFont(composite); + return parent; + } + + private IResource getViewerInput() { + if (fPatcher.isWorkspacePatch()) + return ResourcesPlugin.getWorkspace().getRoot(); + return fPatcher.getTarget(); + } + + private IResource getInitialSelection() { + if (fSelectedNode instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) fSelectedNode; + return fPatcher.getTargetFile(node.getDiffResult().getDiff()); + } else if (fSelectedNode instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) fSelectedNode; + return fPatcher.getTargetFile(node.getHunkResult().getDiffResult().getDiff()); + } else if (fSelectedNode instanceof PatchProjectDiffNode) { + PatchProjectDiffNode node = (PatchProjectDiffNode) fSelectedNode; + DiffProject diffProject = node.getDiffProject(); + return Utilities.getProject(diffProject); + } + return null; + } + + private String getTreeLabel() { + if (fSelectedNode instanceof PatchProjectDiffNode) { + PatchProjectDiffNode node = (PatchProjectDiffNode) fSelectedNode; + DiffProject project = node.getDiffProject(); + return NLS.bind(PatchMessages.PreviewPatchPage_SelectProject, project.getName()); + } else if (fSelectedNode instanceof PatchFileDiffNode) { + PatchFileDiffNode node = (PatchFileDiffNode) fSelectedNode; + //copy over all hunks to new target resource + FilePatch2 diff = node.getDiffResult().getDiff(); + return NLS.bind(PatchMessages.RetargetPatchElementDialog_0, fPatcher.getPath(diff)); + } else if (fSelectedNode instanceof HunkDiffNode) { + HunkDiffNode node = (HunkDiffNode) fSelectedNode; + Hunk hunk = node.getHunkResult().getHunk(); + return NLS.bind(PatchMessages.RetargetPatchElementDialog_1, fPatcher.getPath(hunk.getParent())); + } + return ""; //$NON-NLS-1$ + } + + protected void okPressed() { + if (fSelectedResource != null){ + if (fSelectedNode instanceof PatchProjectDiffNode && fSelectedResource instanceof IProject) { + PatchProjectDiffNode node = (PatchProjectDiffNode) fSelectedNode; + DiffProject project = node.getDiffProject(); + fPatcher.retargetProject(project, (IProject)fSelectedResource); + } else if (fSelectedNode instanceof PatchFileDiffNode && fSelectedResource instanceof IFile) { + PatchFileDiffNode node = (PatchFileDiffNode) fSelectedNode; + //copy over all hunks to new target resource + FilePatch2 diff = node.getDiffResult().getDiff(); + fPatcher.retargetDiff(diff, (IFile)fSelectedResource); + } else if (fSelectedNode instanceof HunkDiffNode && fSelectedResource instanceof IFile) { + HunkDiffNode node = (HunkDiffNode) fSelectedNode; + fPatcher.retargetHunk(node.getHunkResult().getHunk(), (IFile)fSelectedResource); + } + } + super.okPressed(); + } + + void setupListeners() { + fViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection s= (IStructuredSelection) event.getSelection(); + Object obj= s.getFirstElement(); + if (obj instanceof IResource){ + fSelectedResource = (IResource) obj; + if (fSelectedNode instanceof PatchProjectDiffNode) { + if (fSelectedResource instanceof IProject){ + Button okButton = getButton(IDialogConstants.OK_ID); + okButton.setEnabled(true); + } + } else if (fSelectedNode instanceof PatchFileDiffNode + || fSelectedNode instanceof HunkDiffNode) { + if (fSelectedResource instanceof IFile){ + Button okButton = getButton(IDialogConstants.OK_ID); + okButton.setEnabled(true); + } + } + } + } + }); + + fViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ISelection s= event.getSelection(); + if (s instanceof IStructuredSelection) { + Object item= ((IStructuredSelection) s).getFirstElement(); + if (fViewer.getExpandedState(item)) + fViewer.collapseToLevel(item, 1); + else + fViewer.expandToLevel(item, 1); + } + } + }); + + } + + protected Point getInitialSize() { + final Point size= super.getInitialSize(); + size.x= convertWidthInCharsToPixels(75); + size.y+= convertHeightInCharsToPixels(20); + return size; + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java new file mode 100644 index 000000000..09eb69c83 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/UnmatchedHunkTypedElement.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.eclipse.compare.IContentChangeListener; +import org.eclipse.compare.IContentChangeNotifier; +import org.eclipse.compare.IEditableContent; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.ContentChangeNotifier; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.HunkResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; + +public class UnmatchedHunkTypedElement extends HunkTypedElement implements IContentChangeNotifier, IEditableContent { + + private ContentChangeNotifier changeNotifier; + + public UnmatchedHunkTypedElement(HunkResult result) { + // An unmatched hunk element is always used for the before state and is full context + super(result, false, true); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IContentChangeNotifier#addContentChangeListener(org.eclipse.compare.IContentChangeListener) + */ + public synchronized void addContentChangeListener(IContentChangeListener listener) { + if (changeNotifier == null) + changeNotifier = new ContentChangeNotifier(this); + changeNotifier.addContentChangeListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IContentChangeNotifier#removeContentChangeListener(org.eclipse.compare.IContentChangeListener) + */ + public synchronized void removeContentChangeListener(IContentChangeListener listener) { + if (changeNotifier != null) + changeNotifier.removeContentChangeListener(listener); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContent#isEditable() + */ + public boolean isEditable() { + IFile file = ((WorkspaceFileDiffResult)getHunkResult().getDiffResult()).getTargetFile(); + return file != null && file.isAccessible(); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContent#replace(org.eclipse.compare.ITypedElement, org.eclipse.compare.ITypedElement) + */ + public ITypedElement replace(ITypedElement dest, ITypedElement src) { + // Not supported + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.compare.IEditableContent#setContent(byte[]) + */ + public void setContent(byte[] newContent) { + getPatcher().setManuallyMerged(getHunkResult().getHunk(), true); + getPatcher().cacheContents(getDiff(), newContent); + if (changeNotifier != null) + changeNotifier.fireContentChanged(); + } + + private FilePatch2 getDiff() { + return getHunkResult().getDiffResult().getDiff(); + } + + private Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + /* (non-Javadoc) + * @see org.eclipse.compare.internal.patch.HunkTypedElement#getContents() + */ + public InputStream getContents() throws CoreException { + // If there are cached contents, use them + if (getPatcher().hasCachedContents(getDiff())) + return new ByteArrayInputStream(getPatcher().getCachedContents(getDiff())); + // Otherwise return the after state of the diff result + List lines = getHunkResult().getDiffResult().getAfterLines(); + String content = LineReader.createString(getHunkResult().getDiffResult().isPreserveLineDelimeters(), lines); + byte[] bytes = null; + if (getCharset() != null) + try { + bytes = content.getBytes(getCharset()); + } catch (UnsupportedEncodingException e) { + CompareUIPlugin.log(e); + } + if (bytes == null) + bytes = content.getBytes(); + return new ByteArrayInputStream(bytes); + } + + private PatchConfiguration getConfiguration() { + return getHunkResult().getDiffResult().getConfiguration(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java new file mode 100644 index 000000000..17c2d88a7 --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/Utilities.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; + +import org.eclipse.compare.internal.CompareMessages; +import org.eclipse.compare.internal.CompareUIPlugin; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.patch.ReaderCreator; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +public class Utilities { + + public static String getCharset(Object resource) { + if (resource instanceof IEncodedStorage) { + try { + return ((IEncodedStorage) resource).getCharset(); + } catch (CoreException ex) { + CompareUIPlugin.log(ex); + } + } + return ResourcesPlugin.getEncoding(); + } + + public static IProject getProject(DiffProject diffProject) { + return ResourcesPlugin.getWorkspace().getRoot().getProject( + diffProject.getName()); + } + + public static ReaderCreator getReaderCreator(final IStorage storage) { + return new ReaderCreator() { + public Reader createReader() throws CoreException { + return Utilities.createReader(storage); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.compare.patch.ReaderCreator#canCreateReader() + */ + public boolean canCreateReader() { + if (storage == null + || (storage != null && storage instanceof IFile && !((IFile) storage) + .isAccessible())) { + return false; + } + return true; + } + }; + } + + public static BufferedReader createReader(IStorage storage) + throws CoreException { + if (storage == null + || (storage != null && storage instanceof IFile && !((IFile) storage) + .isAccessible())) { + throw new CoreException(new Status(IStatus.WARNING, + CompareUIPlugin.PLUGIN_ID, + CompareMessages.ReaderCreator_fileIsNotAccessible)); + } + String charset = null; + if (storage instanceof IEncodedStorage) { + IEncodedStorage es = (IEncodedStorage) storage; + charset = es.getCharset(); + } + InputStreamReader in = null; + if (charset != null) { + InputStream contents = storage.getContents(); + try { + in = new InputStreamReader(contents, charset); + } catch (UnsupportedEncodingException e) { + CompareUIPlugin.log(e); + try { + contents.close(); + } catch (IOException e1) { + // Ignore + } + } + } + if (in == null) { + in = new InputStreamReader(storage.getContents()); + } + return new BufferedReader(in); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java new file mode 100644 index 000000000..494dda42c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.util.List; + +import org.eclipse.compare.internal.core.patch.FileDiffResult; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; + +public class WorkspaceFileDiffResult extends FileDiffResult { + + public WorkspaceFileDiffResult(FilePatch2 diff, + PatchConfiguration configuration) { + super(diff, configuration); + } + + protected boolean canCreateTarget(IStorage storage) { + IProject project = getPatcher().getTargetProject(getDiff()); + return project != null && project.isAccessible(); + } + + protected boolean targetExists(IStorage storage) { + IFile file= (IFile)storage; + return file != null && file.isAccessible(); + } + + protected List getLines(IStorage storage, boolean create) { + IFile file= getTargetFile(); + List lines = LineReader.load(file, create); + return lines; + } + + protected Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + public IFile getTargetFile() { + return getPatcher().getTargetFile(getDiff()); + } + + public void refresh() { + refresh(Utilities.getReaderCreator(getTargetFile()), null); + } + + public String getCharset() { + IFile file = getTargetFile(); + try { + if (file != null) + return file.getCharset(); + } catch (CoreException e) { + } + return ResourcesPlugin.getEncoding(); + } +} diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java new file mode 100644 index 000000000..0699d116c --- /dev/null +++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/patch/WorkspacePatcher.java @@ -0,0 +1,382 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.compare.internal.core.Messages; +import org.eclipse.compare.internal.core.patch.DiffProject; +import org.eclipse.compare.internal.core.patch.FilePatch2; +import org.eclipse.compare.internal.core.patch.Hunk; +import org.eclipse.compare.internal.core.patch.PatchReader; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceRuleFactory; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.MultiRule; + +/** + * A Patcher + * - knows how to parse various patch file formats into some in-memory structure, + * - holds onto the parsed data and the options to use when applying the patches, + * - knows how to apply the patches to files and folders. + */ +public class WorkspacePatcher extends Patcher { + + private DiffProject[] fDiffProjects; + private boolean fIsWorkspacePatch= false; + private final Map retargetedDiffs = new HashMap(); + + public WorkspacePatcher() { + // nothing to do + } + + public WorkspacePatcher(IResource target) { + setTarget(target); + } + + protected void patchParsed(PatchReader patchReader) { + super.patchParsed(patchReader); + fDiffProjects = patchReader.getDiffProjects(); + fIsWorkspacePatch = patchReader.isWorkspacePatch(); + } + + public DiffProject[] getDiffProjects() { + return fDiffProjects; + } + + public boolean isWorkspacePatch() { + return fIsWorkspacePatch; + } + + //---- parsing patch files + + public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException { + if (!fIsWorkspacePatch) { + super.applyAll(pm, validator); + } else { + final int WORK_UNIT= 10; + + // get all files to be modified in order to call validateEdit + List list= new ArrayList(); + for (int j= 0; j < fDiffProjects.length; j++) { + DiffProject diffProject= fDiffProjects[j]; + if (Utilities.getProject(diffProject).isAccessible()) + list.addAll(Arrays.asList(getTargetFiles(diffProject))); + } + // validate the files for editing + if (!validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { + return; + } + + FilePatch2[] diffs = getDiffs(); + if (pm != null) { + String message= Messages.WorkspacePatcher_0; + pm.beginTask(message, diffs.length * WORK_UNIT); + } + + for (int i= 0; i < diffs.length; i++) { + + int workTicks= WORK_UNIT; + + FilePatch2 diff= diffs[i]; + if (isAccessible(diff)) { + IFile file= getTargetFile(diff); + IPath path= file.getProjectRelativePath(); + if (pm != null) + pm.subTask(path.toString()); + createPath(file.getProject(), path); + + List failed= new ArrayList(); + + int type= diff.getDiffType(isReversed()); + switch (type) { + case FilePatch2.ADDITION : + // patch it and collect rejected hunks + List result= apply(diff, file, true, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + case FilePatch2.DELETION : + file.delete(true, true, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + case FilePatch2.CHANGE : + // patch it and collect rejected hunks + result= apply(diff, file, false, failed); + if (result != null) + store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + } + + if (isGenerateRejectFile() && failed.size() > 0) { + IPath pp= null; + if (path.segmentCount() > 1) { + pp= path.removeLastSegments(1); + pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION); + } else + pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION); + file= createPath(file.getProject(), pp); + if (file != null) { + store(getRejected(failed), file, pm); + try { + IMarker marker= file.createMarker(MARKER_TYPE); + marker.setAttribute(IMarker.MESSAGE, Messages.WorkspacePatcher_1); + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + } catch (CoreException ex) { + // NeedWork + } + } + } + } + + if (pm != null) { + if (pm.isCanceled()) + break; + if (workTicks > 0) + pm.worked(workTicks); + } + } + } + } + + private boolean isAccessible(FilePatch2 diff) { + return isEnabled(diff) && Utilities.getProject(diff.getProject()).isAccessible(); + } + + /** + * Returns the target files of all the Diffs contained by this + * DiffProject. + * @param project + * @return An array of IFiles that are targeted by the Diffs + */ + public IFile[] getTargetFiles(DiffProject project) { + List files= new ArrayList(); + FilePatch2[] diffs = project.getFileDiffs(); + for (int i = 0; i < diffs.length; i++) { + FilePatch2 diff = diffs[i]; + if (isEnabled(diff)) { + files.add(getTargetFile(diff)); + } + } + return (IFile[]) files.toArray(new IFile[files.size()]); + } + + public IFile getTargetFile(FilePatch2 diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + DiffProject project = getProject(diff); + if (project != null) + return Utilities.getProject(project).getFile(path); + return super.getTargetFile(diff); + } + + private IPath getFullPath(FilePatch2 diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + DiffProject project = getProject(diff); + if (project != null) + return Utilities.getProject(project).getFile(path).getFullPath(); + return getTarget().getFullPath().append(path); + } + + public ISchedulingRule[] getTargetProjects() { + List projects= new ArrayList(); + IResourceRuleFactory ruleFactory= ResourcesPlugin.getWorkspace().getRuleFactory(); + // Determine the appropriate scheduling rules + for (int i= 0; i < fDiffProjects.length; i++) { + IProject tempProject= Utilities.getProject(fDiffProjects[i]); + // The goal here is to lock as little of the workspace as necessary + // but still allow the patcher to obtain the locks it needs. + // As such, we need to get the modify rules from the rule factory for the .project file. A pessimistic + // rule factory will return the root, while others might return just the project. Combining + // this rule with the project will result in the smallest possible locking set. + ISchedulingRule scheduleRule= ruleFactory.modifyRule(tempProject.getFile(IProjectDescription.DESCRIPTION_FILE_NAME)); + MultiRule multiRule= new MultiRule(new ISchedulingRule[] { scheduleRule, tempProject } ); + projects.add(multiRule); + } + + return (ISchedulingRule[]) projects.toArray(new ISchedulingRule[projects.size()]); + } + + public void setDiffProjects(DiffProject[] newProjectArray) { + fDiffProjects = new DiffProject[newProjectArray.length]; + System.arraycopy(newProjectArray,0, fDiffProjects, 0, newProjectArray.length); + } + + public void removeProject(DiffProject project) { + DiffProject[] temp = new DiffProject[fDiffProjects.length - 1]; + int counter = 0; + for (int i = 0; i < fDiffProjects.length; i++) { + if (fDiffProjects[i] != project){ + temp[counter++] = fDiffProjects[i]; + } + } + fDiffProjects = temp; + } + + protected Object getElementParent(Object element) { + if (element instanceof FilePatch2 && fDiffProjects != null) { + FilePatch2 diff = (FilePatch2) element; + for (int i = 0; i < fDiffProjects.length; i++) { + DiffProject project = fDiffProjects[i]; + if (project.contains(diff)) + return project; + } + } + return null; + } + + public boolean isRetargeted(Object object) { + return retargetedDiffs.containsKey(object); + } + + public IPath getOriginalPath(Object object) { + return (IPath)retargetedDiffs.get(object); + } + + public void retargetDiff(FilePatch2 diff, IFile file) { + retargetedDiffs.put(diff, diff.getPath(false)); + IHunk[] hunks = diff.getHunks(); + + if (isWorkspacePatch()){ + //since the diff has no more hunks to apply, remove it from the parent and the patcher + diff.getProject().remove(diff); + } + removeDiff(diff); + FilePatch2 newDiff = getDiffForFile(file); + for (int i = 0; i < hunks.length; i++) { + Hunk hunk = (Hunk) hunks[i]; + newDiff.add(hunk); + } + } + + private FilePatch2 getDiffForFile(IFile file) { + DiffProject diffProject = null; + FilePatch2[] diffsToCheck; + if (isWorkspacePatch()){ + // Check if the diff project already exists for the file + IProject project = file.getProject(); + DiffProject[] diffProjects = getDiffProjects(); + for (int i = 0; i < diffProjects.length; i++) { + if (Utilities.getProject(diffProjects[i]).equals(project)){ + diffProject = diffProjects[i]; + break; + } + } + // If the project doesn't exist yet, create it and add it to the project list + if (diffProject == null){ + diffProject = addDiffProjectForProject(project); + } + diffsToCheck = diffProject.getFileDiffs(); + } else { + diffsToCheck = getDiffs(); + } + // Check to see if a diff already exists for the file + for (int i = 0; i < diffsToCheck.length; i++) { + FilePatch2 fileDiff = diffsToCheck[i]; + if (isDiffForFile(fileDiff, file)) { + return fileDiff; + } + } + + // Create a new diff for the file + IPath path = getDiffPath(file); + FilePatch2 newDiff = new FilePatch2(path, 0, path, 0); + if (diffProject != null){ + diffProject.add(newDiff); + } + addDiff(newDiff); + return newDiff; + } + + private IPath getDiffPath(IFile file) { + DiffProject project = getDiffProject(file.getProject()); + if (project != null) { + return file.getProjectRelativePath(); + } + return file.getFullPath().removeFirstSegments(getTarget().getFullPath().segmentCount()); + } + + private boolean isDiffForFile(FilePatch2 fileDiff, IFile file) { + return getFullPath(fileDiff).equals(file.getFullPath()); + } + + private DiffProject addDiffProjectForProject(IProject project) { + DiffProject[] diffProjects = getDiffProjects(); + DiffProject diffProject = new DiffProject(project.getName()); + DiffProject[] newProjectArray = new DiffProject[diffProjects.length + 1]; + System.arraycopy(diffProjects, 0, newProjectArray, 0, diffProjects.length); + newProjectArray[diffProjects.length] = diffProject; + setDiffProjects(newProjectArray); + return diffProject; + } + + public void retargetHunk(Hunk hunk, IFile file) { + FilePatch2 newDiff = getDiffForFile(file); + newDiff.add(hunk); + } + + public void retargetProject(DiffProject project, IProject targetProject) { + retargetedDiffs.put(project, Utilities.getProject(project).getFullPath()); + FilePatch2[] diffs = project.getFileDiffs(); + DiffProject selectedProject = getDiffProject(targetProject); + if (selectedProject == null) + selectedProject = addDiffProjectForProject(targetProject); + // Copy over the diffs to the new project + for (int i = 0; i < diffs.length; i++) { + selectedProject.add(diffs[i]); + } + // Since the project has been retargeted, remove it from the patcher + removeProject(project); + } + + /** + * Return the diff project for the given project + * or <code>null</code> if the diff project doesn't exist + * or if the patch is not a workspace patch. + * @param project the project + * @return the diff project for the given project + * or <code>null</code> + */ + private DiffProject getDiffProject(IProject project) { + if (!isWorkspacePatch()) + return null; + DiffProject[] projects = getDiffProjects(); + for (int i = 0; i < projects.length; i++) { + if (Utilities.getProject(projects[i]).equals(project)) + return projects[i]; + } + return null; + } + + public int getStripPrefixSegments() { + // Segments are never stripped from a workspace patch + if (isWorkspacePatch()) + return 0; + return super.getStripPrefixSegments(); + } + +} |