/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
package org.eclipse.compare; | |
import java.util.HashMap; | |
import java.util.ResourceBundle; | |
import java.util.Date; | |
import java.util.List; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.HashSet; | |
import java.util.Calendar; | |
import java.io.InputStream; | |
import java.text.*; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.events.*; | |
import org.eclipse.swt.graphics.Image; | |
import org.eclipse.swt.graphics.Point; | |
import org.eclipse.swt.layout.GridData; | |
import org.eclipse.swt.widgets.Widget; | |
import org.eclipse.swt.widgets.Control; | |
import org.eclipse.swt.widgets.Composite; | |
import org.eclipse.swt.widgets.Tree; | |
import org.eclipse.swt.widgets.TreeItem; | |
import org.eclipse.swt.widgets.Shell; | |
import org.eclipse.swt.widgets.Display; | |
import org.eclipse.swt.widgets.Table; | |
import org.eclipse.swt.widgets.TableItem; | |
import org.eclipse.swt.widgets.Item; | |
import org.eclipse.swt.widgets.Button; | |
import org.eclipse.jface.resource.ImageDescriptor; | |
import org.eclipse.jface.viewers.*; | |
import org.eclipse.jface.util.Assert; | |
import org.eclipse.jface.dialogs.*; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IFileState; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.compare.internal.*; | |
import org.eclipse.compare.structuremergeviewer.*; | |
import org.eclipse.compare.*; | |
/** | |
* A dialog where one input element can be compared against | |
* a list of historic variants (editions) of the same input element. | |
* The dialog can be used to implement functions like "Compare/Replace with Version" or | |
* "Compare/Replace from Local History" on workspace resources. | |
* <p> | |
* In addition it is possible to specify a subsection of the input element | |
* (e.g. a method in a Java source file) by means of a "path". | |
* In this case the dialog compares only the subsection (as specified by the path) | |
* with the corresponding subsection in the list of editions. | |
* Only those editions are shown where the subsection differs from the same subsection in | |
* another edition thereby minimizing the number of presented variants. | |
* This functionality can be used to implement "Replace from Local History" | |
* for the Java language. | |
* <p> | |
* Subsections of an input element are determined by first finding an | |
* <code>IStructureCreator</code> for the input's type. | |
* Then the method <code>locate</code> is used to extract the subsection. | |
* <p> | |
* Each edition (variant in the list of variants) must implement the <code>IModificationDate</code> interface | |
* so that the dialog can sort the editions and present them in a tree structure where every | |
* node corresponds one day. | |
* <p> | |
* The functionality is surfaced in a single function <code>selectEdition</code>. | |
* <p> | |
* Clients may instantiate this class; it is not intended to be subclassed. | |
* </p> | |
* | |
* @see IModificationDate | |
* @see ITypedElement | |
*/ | |
public class EditionSelectionDialog extends ResizableDialog { | |
/** | |
* An item in an underlying edition. | |
*/ | |
private static class Pair { | |
private ITypedElement fEdition; | |
private ITypedElement fItem; | |
private String fContent; | |
private IStructureCreator fStructureCreator; | |
private boolean fHasError= false; | |
Pair(IStructureCreator structureCreator, ITypedElement edition, ITypedElement item) { | |
fStructureCreator= structureCreator; | |
fEdition= edition; | |
fItem= item; | |
} | |
Pair(IStructureCreator structureCreator, ITypedElement edition) { | |
this(structureCreator, edition, edition); | |
} | |
ITypedElement getEdition() { | |
return fEdition; | |
} | |
ITypedElement getItem() { | |
return fItem; | |
} | |
/** | |
* The content is lazily loaded | |
*/ | |
private String getContent() { | |
if (fContent == null) { | |
if (fStructureCreator != null) | |
fContent= fStructureCreator.getContents(fItem, false); | |
else { | |
if (fItem instanceof IStreamContentAccessor) { | |
IStreamContentAccessor sca= (IStreamContentAccessor) fItem; | |
try { | |
InputStream is= sca.getContents(); | |
if (is != null) | |
fContent= Utilities.readString(is); | |
} catch (CoreException ex) { | |
} | |
} | |
} | |
if (fContent == null) | |
fContent= ""; //$NON-NLS-1$ | |
} | |
return fContent; | |
} | |
/** | |
* Compares content of item. | |
*/ | |
public boolean equals(Object other) { | |
if (other != null && other.getClass() == getClass()) { | |
if (getContent().equals(((Pair)other).getContent())) | |
return true; | |
} | |
return super.equals(other); | |
} | |
} | |
// Configuration options | |
private CompareConfiguration fCompareConfiguration; | |
/** use a side-by-side compare viewer */ | |
private boolean fCompare= true; | |
/** show target on right hand side */ | |
private boolean fTargetIsRight= false; | |
/** hide entries which have identical content */ | |
private boolean fHideIdentical= true; | |
/** add mode if true, otherwise replace mode */ | |
private boolean fAddMode= false; | |
/** compare mode if true, otherwise replace/add mode */ | |
private boolean fCompareMode= false; | |
/** perform structure compare on editions */ | |
private boolean fStructureCompare= false; | |
/** | |
* Maps from members to their corresponding editions. | |
* Has only a single entry if dialog is used in "Replace" (and not "Add") mode. | |
*/ | |
private HashMap fMemberEditions; | |
/** The editions of the current selected member */ | |
private List fCurrentEditions; | |
private Thread fThread; | |
private Pair fTargetPair; | |
/** The selected edition in the edition viewer */ | |
private ITypedElement fSelectedItem; | |
private String fTitleArg; | |
private Image fTitleImage; | |
// SWT controls | |
private CompareViewerSwitchingPane fContentPane; | |
private Button fCommitButton; | |
private Table fMemberTable; | |
private CompareViewerPane fMemberPane; | |
private Tree fEditionTree; | |
private CompareViewerPane fEditionPane; | |
private Image fDateImage; | |
private Image fTimeImage; | |
private CompareViewerSwitchingPane fStructuredComparePane; | |
/** | |
* Creates a new modal, resizable dialog. | |
* Various titles, icons, and labels are configured from the given resource bundle. | |
* The following resource keys are used: | |
* <pre> | |
* key type description | |
* title String dialog title | |
* width Integer initial width of dialog | |
* height Integer initial height of dialog | |
* treeTitleFormat MessageFormat pane title for edition tree; arg 0 is the target | |
* dateIcon String icon for node in edition tree; path relative to plugin | |
* timeIcon String icon for leaf in edition tree; path relative to plugin | |
* todayFormat MessageFormat format string if date is todays date; arg 0 is date | |
* yesterdayFormat MessageFormat format string if date is yesterdays date; arg 0 is date | |
* dayFormat MessageFormat format string if date is any other date; arg 0 is date | |
* editionLabel String label for editions side of compare viewer; arg 0 is the date | |
* targetLabel String label for target side of compare viewer | |
* buttonLabel String label for OK button; default is IDialogConstants.OK_LABEL | |
* </pre> | |
* | |
* @param parent if not <code>null</code> the new dialog stays on top of this parent shell | |
* @param bundle <code>ResourceBundle</code> to configure the dialog | |
*/ | |
public EditionSelectionDialog(Shell parent, ResourceBundle bundle) { | |
super(parent, bundle); | |
fCompareConfiguration= new CompareConfiguration(); | |
fCompareConfiguration.setLeftEditable(false); | |
fCompareConfiguration.setRightEditable(false); | |
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 void setEditionTitleArgument(String titleArgument) { | |
fTitleArg= titleArgument; | |
} | |
public void setEditionTitleImage(Image titleImage) { | |
fTitleImage= titleImage; | |
} | |
/** | |
* Select the previous edition (presenting a UI). | |
* | |
* @param target the input object against which the editions are compared; must not be <code>null</code> | |
* @param editions the list of editions (element type: <code>ITypedElement</code>s) | |
* @param path If <code>null</code> dialog shows full input; if non <code>null</code> it extracts a subsection | |
* @return returns the selected edition or <code>null</code> if error occurred. | |
* The returned <code>ITypedElement</code> is one of the original editions | |
* if <code>path</code> was <code>null</code>; otherwise | |
* it is an <code>ITypedElement</code> returned from <code>IStructureCreator.locate(path, item)</code> | |
*/ | |
public ITypedElement selectPreviousEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath) { | |
Assert.isNotNull(target); | |
fTargetPair= new Pair(null, target); | |
// sort input editions | |
final int count= inputEditions.length; | |
final IModificationDate[] editions= new IModificationDate[count]; | |
for (int i= 0; i < count; i++) | |
editions[i]= (IModificationDate) inputEditions[i]; | |
if (count > 1) | |
internalSort(editions, 0, count-1); | |
// find StructureCreator if ppath is not null | |
IStructureCreator structureCreator= null; | |
if (ppath != null) { | |
String type= target.getType(); | |
IStructureCreatorDescriptor scd= CompareUIPlugin.getStructureCreator(type); | |
if (scd != null) | |
structureCreator= scd.createStructureCreator(); | |
} | |
if (fAddMode) { | |
// does not work in add mode | |
return null; | |
} | |
if (structureCreator != null) { | |
Pair pair= createPair(structureCreator, ppath, target); | |
if (pair != null) | |
fTargetPair= pair; | |
else | |
ppath= null; // couldn't extract item because of error | |
} | |
// from front (newest) to back (oldest) | |
for (int i= 0; i < count; i++) { | |
ITypedElement edition= (ITypedElement) editions[i]; | |
Pair pair= null; | |
if (structureCreator != null && ppath != null) { | |
// extract sub element from edition | |
pair= createPair(structureCreator, ppath, edition); | |
} else { | |
pair= new Pair(null, edition); | |
} | |
if (pair != null && pair.fHasError) | |
return null; | |
if (pair != null && !fTargetPair.equals(pair)) { | |
return pair.fItem; | |
} | |
} | |
// nothing found | |
return null; | |
} | |
/** | |
* Presents this modal dialog with the functionality described in the class comment above. | |
* | |
* @param target the input object against which the editions are compared; must not be <code>null</code> | |
* @param editions the list of editions (element type: <code>ITypedElement</code>s) | |
* @param path If <code>null</code> dialog shows full input; if non <code>null</code> it extracts a subsection | |
* @return returns the selected edition or <code>null</code> if dialog was cancelled. | |
* The returned <code>ITypedElement</code> is one of the original editions | |
* if <code>path</code> was <code>null</code>; otherwise | |
* it is an <code>ITypedElement</code> returned from <code>IStructureCreator.locate(path, item)</code> | |
*/ | |
public ITypedElement selectEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath) { | |
Assert.isNotNull(target); | |
fTargetPair= new Pair(null, target); | |
// sort input editions | |
final int count= inputEditions.length; | |
final IModificationDate[] editions= new IModificationDate[count]; | |
for (int i= 0; i < count; i++) | |
editions[i]= (IModificationDate) inputEditions[i]; | |
if (count > 1) | |
internalSort(editions, 0, count-1); | |
// find StructureCreator if ppath is not null | |
IStructureCreator structureCreator= null; | |
if (ppath != null) { | |
String type= target.getType(); | |
IStructureCreatorDescriptor scd= CompareUIPlugin.getStructureCreator(type); | |
if (scd != null) | |
structureCreator= scd.createStructureCreator(); | |
} | |
if (!fAddMode) { | |
// replace mode | |
if (structureCreator != null) { | |
Pair pair= createPair(structureCreator, ppath, target); | |
if (pair != null) | |
fTargetPair= pair; | |
else | |
ppath= null; // couldn't extract item because of error | |
} | |
// set the left and right labels for the compare viewer | |
String targetLabel= getTargetLabel(target, fTargetPair.getItem()); | |
if (fTargetIsRight) | |
fCompareConfiguration.setRightLabel(targetLabel); | |
else | |
fCompareConfiguration.setLeftLabel(targetLabel); | |
if (structureCreator != null && ppath != null) { // extract sub element | |
final IStructureCreator sc= structureCreator; | |
final Object path= ppath; | |
// construct the comparer thread | |
// and perform the background extract | |
fThread= new Thread() { | |
public void run() { | |
// from front (newest) to back (oldest) | |
for (int i= 0; i < count; i++) { | |
if (fEditionTree == null || fEditionTree.isDisposed()) | |
break; | |
ITypedElement edition= (ITypedElement) editions[i]; | |
// extract sub element from edition | |
Pair pair= createPair(sc, path, edition); | |
if (pair != null) | |
sendPair(pair); | |
} | |
sendPair(null); | |
} | |
}; | |
} else { | |
// create tree widget | |
create(); | |
// from front (newest) to back (oldest) | |
for (int i= 0; i < count; i++) | |
addMemberEdition(new Pair(null, (ITypedElement) editions[i])); | |
} | |
} else { | |
// add mode | |
final Object container= ppath; | |
Assert.isNotNull(container); | |
if (structureCreator == null) | |
return null; // error | |
// extract all elements of container | |
final HashSet current= new HashSet(); | |
IStructureComparator sco= structureCreator.locate(container, target); | |
if (sco != null) { | |
Object[] children= sco.getChildren(); | |
if (children != null) | |
for (int i= 0; i < children.length; i++) | |
current.add(children[i]); | |
} | |
final IStructureCreator sc= structureCreator; | |
// construct the comparer thread | |
// and perform the background extract | |
fThread= new Thread() { | |
public void run() { | |
// from front (newest) to back (oldest) | |
for (int i= 0; i < count; i++) { | |
if (fEditionTree == null || fEditionTree.isDisposed()) | |
break; | |
ITypedElement edition= (ITypedElement) editions[i]; | |
IStructureComparator sco2= sc.locate(container, edition); | |
if (sco2 != null) { | |
Object[] children= sco2.getChildren(); | |
if (children != null) { | |
for (int i2= 0; i2 < children.length; i2++) { | |
ITypedElement child= (ITypedElement) children[i2]; | |
if (!current.contains(child)) | |
sendPair(new Pair(sc, edition, child)); | |
} | |
} | |
} | |
} | |
sendPair(null); | |
} | |
}; | |
} | |
open(); | |
if (getReturnCode() == OK) | |
return fSelectedItem; | |
return null; | |
} | |
private Pair createPair(IStructureCreator sc, Object path, ITypedElement input) { | |
IStructureComparator scmp= sc.locate(path, input); | |
if (scmp == null && sc.getStructure(input) == null) { // parse error | |
Pair p= new Pair(sc, input); | |
p.fHasError= true; | |
return p; | |
} | |
if (scmp instanceof ITypedElement) | |
return new Pair(sc, input, (ITypedElement) scmp); | |
return null; | |
} | |
/** | |
* Controls whether identical entries are shown or not (default). | |
* This method must be called before <code>selectEdition</code>. | |
* | |
* @param hide if true identical entries are hidden; otherwise they are shown. | |
*/ | |
public void setHideIdenticalEntries(boolean hide) { | |
fHideIdentical= hide; | |
} | |
/** | |
* Controls whether workspace target is on the left (the default) or right hand side. | |
* | |
* @param isRight if true target is shown on right hand side. | |
*/ | |
public void setTargetIsRight(boolean isRight) { | |
fTargetIsRight= isRight; | |
} | |
/** | |
* Controls whether the EditionSelectionDialog is in 'add' mode | |
* or 'replace' mode (the default). | |
* | |
* @param addMode if true dialog is in 'add' mode. | |
*/ | |
public void setAddMode(boolean addMode) { | |
fAddMode= addMode; | |
} | |
/** | |
* Controls whether the EditionSelectionDialog is in 'compare' mode | |
* or 'add/replace' (the default) mode. | |
* | |
* @param addMode if true dialog is in 'add' mode. | |
*/ | |
public void setCompareMode(boolean compareMode) { | |
fCompareMode= compareMode; | |
fStructureCompare= fCompareMode && !fAddMode; | |
} | |
/** | |
* Returns the input target that has been specified with the most recent call | |
* to <code>selectEdition</code>. If a not <code>null</code> path was specified this method | |
* returns a subsection of this target (<code>IStructureCreator.locate(path, target)</code>) | |
* instead of the input target. | |
* <p> | |
* For example if the <code>target</code> is a Java compilation unit and <code>path</code> specifies | |
* a method, the value returned from <code>getTarget</code> will be the method not the compilation unit. | |
* | |
* @return the last specified target or a subsection thereof. | |
*/ | |
public ITypedElement getTarget() { | |
return fTargetPair.getItem(); | |
} | |
/** | |
* Returns a label for identifying the target side of a compare viewer. | |
* This implementation extracts the value for the key "targetLabel" from the resource bundle | |
* and passes it as the format argument to <code>MessageFormat.format</code>. | |
* The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) | |
* is the name of the given input element. | |
* <p> | |
* Subclasses may override to create their own label. | |
* </p> | |
* | |
* @param target the target element for which a label must be returned | |
* @param item if a path has been specified in <code>selectEdition</code> a sub element of the given target; otherwise the same as target | |
* @return a label the target side of a compare viewer | |
*/ | |
protected String getTargetLabel(ITypedElement target, ITypedElement item) { | |
String format= null; | |
if (target instanceof ResourceNode) | |
format= Utilities.getString(fBundle, "workspaceTargetLabel", null); //$NON-NLS-1$ | |
if (format == null) | |
format= Utilities.getString(fBundle, "targetLabel"); //$NON-NLS-1$ | |
if (format == null) | |
format= "x{0}"; //$NON-NLS-1$ | |
return MessageFormat.format(format, new Object[] { target.getName() }); | |
} | |
/** | |
* Returns a label for identifying the edition side of a compare viewer. | |
* This implementation extracts the value for the key "editionLabel" from the resource bundle | |
* and passes it as the format argument to <code>MessageFormat.format</code>. | |
* The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) | |
* is the formatted modification date of the given input element. | |
* <p> | |
* Subclasses may override to create their own label. | |
* </p> | |
* | |
* @param selectedEdition the selected edition for which a label must be returned | |
* @param item if a path has been specified in <code>selectEdition</code> a sub element of the given selectedEdition; otherwise the same as selectedEdition | |
* @return a label the edition side of a compare viewer | |
*/ | |
protected String getEditionLabel(ITypedElement selectedEdition, ITypedElement item) { | |
String format= null; | |
if (selectedEdition instanceof ResourceNode) | |
format= Utilities.getString(fBundle, "workspaceEditionLabel", null); //$NON-NLS-1$ | |
else if (selectedEdition instanceof HistoryItem) | |
format= Utilities.getString(fBundle, "historyEditionLabel", null); //$NON-NLS-1$ | |
if (format == null) | |
format= Utilities.getString(fBundle, "editionLabel"); //$NON-NLS-1$ | |
if (format == null) | |
format= "x{0}"; //$NON-NLS-1$ | |
String date= ""; //$NON-NLS-1$ | |
if (selectedEdition instanceof IModificationDate) { | |
long modDate= ((IModificationDate)selectedEdition).getModificationDate(); | |
date= DateFormat.getDateTimeInstance().format(new Date(modDate)); | |
} | |
return MessageFormat.format(format, new Object[] { date }); | |
} | |
protected String getShortEditionLabel(ITypedElement edition, ITypedElement item, Date date) { | |
String format= null; | |
if (edition instanceof ResourceNode) | |
format= Utilities.getString(fBundle, "workspaceTreeFormat", null); //$NON-NLS-1$ | |
if (format == null) | |
format= Utilities.getString(fBundle, "treeFormat", null); //$NON-NLS-1$ | |
if (format == null) | |
format= "x{0}"; //$NON-NLS-1$ | |
String ds= DateFormat.getTimeInstance().format(date); | |
return MessageFormat.format(format, new Object[] { ds }); | |
} | |
/** | |
* Returns an image for identifying the edition side of a compare viewer. | |
* This implementation extracts the value for the key "editionLabel" from the resource bundle | |
* and passes it as the format argument to <code>MessageFormat.format</code>. | |
* The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) | |
* is the formatted modification date of the given input element. | |
* <p> | |
* Subclasses may override to create their own label. | |
* </p> | |
* | |
* @param selectedEdition the selected edition for which a label must be returned | |
* @param item if a path has been specified in <code>selectEdition</code> a sub element of the given selectedEdition; otherwise the same as selectedEdition | |
* @return a label the edition side of a compare viewer | |
*/ | |
protected Image getEditionImage(ITypedElement selectedEdition, ITypedElement item) { | |
if (selectedEdition instanceof ResourceNode) | |
return selectedEdition.getImage(); | |
if (selectedEdition instanceof HistoryItem) | |
return fTimeImage; | |
return null; | |
} | |
/* (non Javadoc) | |
* Creates SWT control tree. | |
*/ | |
protected synchronized Control createDialogArea(Composite parent) { | |
getShell().setText(Utilities.getString(fBundle, "title")); //$NON-NLS-1$ | |
Splitter vsplitter= new Splitter(parent, SWT.VERTICAL); | |
vsplitter.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | |
| GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); | |
vsplitter.addDisposeListener( | |
new DisposeListener() { | |
public void widgetDisposed(DisposeEvent e) { | |
if (fDateImage != null) | |
fDateImage.dispose(); | |
if (fTimeImage != null) | |
fTimeImage.dispose(); | |
} | |
} | |
); | |
if (fAddMode) { | |
// we need two panes: the left for the elements, the right one for the editions | |
Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); | |
fMemberPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); | |
fMemberPane.setText(Utilities.getString(fBundle, "memberPaneTitle")); //$NON-NLS-1$ | |
fMemberTable= new Table(fMemberPane, SWT.H_SCROLL + SWT.V_SCROLL); | |
fMemberTable.addSelectionListener( | |
new SelectionAdapter() { | |
public void widgetSelected(SelectionEvent e) { | |
handleMemberSelect(e.item); | |
} | |
} | |
); | |
fMemberPane.setContent(fMemberTable); | |
fEditionPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); | |
} else { | |
if (fStructureCompare) { | |
// we need two panes: the left for the elements, the right one for the structured diff | |
Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); | |
fEditionPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); | |
fStructuredComparePane= new CompareViewerSwitchingPane(hsplitter, SWT.BORDER | SWT.FLAT, true) { | |
protected Viewer getViewer(Viewer oldViewer, Object input) { | |
if (input instanceof ICompareInput) | |
return CompareUIPlugin.findStructureViewer(oldViewer, (ICompareInput)input, this, fCompareConfiguration); | |
return null; | |
} | |
}; | |
fStructuredComparePane.addSelectionChangedListener( | |
new ISelectionChangedListener() { | |
public void selectionChanged(SelectionChangedEvent e) { | |
feedInput2(e.getSelection()); | |
} | |
} | |
); | |
} else { | |
// only a single pane showing the editions | |
fEditionPane= new CompareViewerPane(vsplitter, SWT.BORDER | SWT.FLAT); | |
} | |
if (fTitleArg == null) | |
fTitleArg= fTargetPair.getItem().getName(); | |
String titleFormat= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ | |
String title= MessageFormat.format(titleFormat, new String[] { fTitleArg }); | |
fEditionPane.setText(title); | |
if (fTitleImage != null) | |
fEditionPane.setImage(fTitleImage); | |
} | |
fEditionTree= new Tree(fEditionPane, SWT.H_SCROLL + SWT.V_SCROLL); | |
fEditionTree.addSelectionListener( | |
new SelectionAdapter() { | |
// public void widgetDefaultSelected(SelectionEvent e) { | |
// handleDefaultSelected(); | |
// } | |
public void widgetSelected(SelectionEvent e) { | |
feedInput(e.item); | |
} | |
} | |
); | |
fEditionPane.setContent(fEditionTree); | |
// now start the thread (and forget about it) | |
if (fThread != null) { | |
fThread.start(); | |
fThread= null; | |
} | |
fContentPane= new CompareViewerSwitchingPane(vsplitter, SWT.BORDER | SWT.FLAT) { | |
protected Viewer getViewer(Viewer oldViewer, Object input) { | |
return CompareUIPlugin.findContentViewer(oldViewer, input, this, fCompareConfiguration); | |
} | |
}; | |
vsplitter.setWeights(new int[] { 30, 70 }); | |
return vsplitter; | |
} | |
/* (non-Javadoc) | |
* Method declared on Dialog. | |
*/ | |
protected void createButtonsForButtonBar(Composite parent) { | |
String buttonLabel= Utilities.getString(fBundle, "buttonLabel", IDialogConstants.OK_LABEL); //$NON-NLS-1$ | |
if (fCompareMode) { | |
// only a 'Done' button | |
createButton(parent, IDialogConstants.CANCEL_ID, buttonLabel, false); | |
} else { | |
// a 'Cancel' and a 'Add/Replace' button | |
fCommitButton= createButton(parent, IDialogConstants.OK_ID, buttonLabel, true); | |
fCommitButton.setEnabled(false); | |
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); | |
} | |
} | |
/** | |
* Overidden to disable dismiss on double click in compare mode. | |
*/ | |
protected void okPressed() { | |
if (fCompareMode) | |
; // don't dismiss dialog | |
else | |
super.okPressed(); | |
} | |
//---- private stuff ---------------------------------------------------------------------------------------- | |
/** | |
* Asynchroneously sends a Pair (or null) to the UI thread. | |
*/ | |
private void sendPair(final Pair pair) { | |
if (fEditionTree != null && !fEditionTree.isDisposed()) { | |
Display display= fEditionTree.getDisplay(); | |
display.asyncExec( | |
new Runnable() { | |
public void run() { | |
addMemberEdition(pair); | |
} | |
} | |
); | |
} | |
} | |
// private void handleDefaultSelected() { | |
// if (fSelectedItem != null) | |
// okPressed(); | |
// } | |
private static void internalSort(IModificationDate[] keys, int left, int right) { | |
int original_left= left; | |
int original_right= right; | |
IModificationDate mid= keys[(left + right) / 2]; | |
do { | |
while (keys[left].getModificationDate() > mid.getModificationDate()) | |
left++; | |
while (mid.getModificationDate() > keys[right].getModificationDate()) | |
right--; | |
if (left <= right) { | |
IModificationDate 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); | |
} | |
/** | |
* Adds the given Pair to the member editions. | |
* If HIDE_IDENTICAL is true the new Pair is only added if its contents | |
* is different from the preceeding Pair. | |
* If the argument is <code>null</code> the message "No Editions found" is shown | |
* in the member or edition viewer. | |
*/ | |
private void addMemberEdition(Pair pair) { | |
if (pair == null) { // end of list of pairs | |
if (fMemberTable != null) { | |
if (!fMemberTable.isDisposed() && fMemberTable.getItemCount() == 0) { | |
TableItem ti= new TableItem(fMemberTable, SWT.NONE); | |
ti.setText(Utilities.getString(fBundle, "noAdditionalMembersMessage")); //$NON-NLS-1$ | |
} | |
return; | |
} | |
if (fEditionTree != null && !fEditionTree.isDisposed() && fEditionTree.getItemCount() == 0) { | |
TreeItem ti= new TreeItem(fEditionTree, SWT.NONE); | |
ti.setText(Utilities.getString(fBundle, "notFoundInLocalHistoryMessage")); //$NON-NLS-1$ | |
} | |
return; | |
} | |
if (fMemberEditions == null) | |
fMemberEditions= new HashMap(); | |
ITypedElement item= pair.getItem(); | |
List editions= (List) fMemberEditions.get(item); | |
if (editions == null) { | |
editions= new ArrayList(); | |
fMemberEditions.put(item, editions); | |
if (fMemberTable != null && !fMemberTable.isDisposed()) { | |
ITypedElement te= (ITypedElement)item; | |
String name= te.getName(); | |
// find position | |
TableItem[] items= fMemberTable.getItems(); | |
int where= items.length; | |
for (int i= 0; i < where; i++) { | |
String n= items[i].getText(); | |
if (n.compareTo(name) > 0) { | |
where= i; | |
break; | |
} | |
} | |
TableItem ti= new TableItem(fMemberTable, where, SWT.NULL); | |
ti.setImage(te.getImage()); | |
ti.setText(name); | |
ti.setData(editions); | |
} | |
} | |
if (fHideIdentical) { | |
Pair last= fTargetPair; | |
int size= editions.size(); | |
if (size > 0) | |
last= (Pair) editions.get(size-1); | |
if (last != null && last.equals(pair)) | |
return; // don't add since the new one is equal to old | |
} | |
editions.add(pair); | |
if (!fAddMode || editions == fCurrentEditions) | |
addEdition(pair); | |
} | |
/** | |
* Returns the number of s since Jan 1st, 1970. | |
* The given date is converted to GMT and daylight saving is taken into account too. | |
*/ | |
private long dayNumber(long date) { | |
int ONE_DAY_MS= 24*60*60 * 1000; // one day in milli seconds | |
Calendar calendar= Calendar.getInstance(); | |
long localTimeOffset= calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); | |
return (date + localTimeOffset) / ONE_DAY_MS; | |
} | |
/** | |
* Adds the given Pair to the edition tree. | |
* It takes care of creating tree nodes for different dates. | |
*/ | |
private void addEdition(Pair pair) { | |
if (fEditionTree == null || fEditionTree.isDisposed()) | |
return; | |
// find last day | |
TreeItem[] days= fEditionTree.getItems(); | |
TreeItem lastDay= null; | |
if (days.length > 0) | |
lastDay= days[days.length-1]; | |
boolean first= lastDay == null; | |
ITypedElement edition= pair.getEdition(); | |
ITypedElement item= pair.getItem(); | |
long ldate= ((IModificationDate)edition).getModificationDate(); | |
long day= dayNumber(ldate); | |
Date date= new Date(ldate); | |
if (lastDay == null || day != dayNumber(((Date)lastDay.getData()).getTime())) { | |
lastDay= new TreeItem(fEditionTree, SWT.NONE); | |
lastDay.setImage(fDateImage); | |
String df= DateFormat.getDateInstance().format(date); | |
long today= dayNumber(System.currentTimeMillis()); | |
String formatKey; | |
if (day == today) | |
formatKey= "todayFormat"; //$NON-NLS-1$ | |
else if (day == today-1) | |
formatKey= "yesterdayFormat"; //$NON-NLS-1$ | |
else | |
formatKey= "dayFormat"; //$NON-NLS-1$ | |
String pattern= Utilities.getString(fBundle, formatKey); | |
if (pattern != null) | |
df= MessageFormat.format(pattern, new String[] { df }); | |
lastDay.setText(df); | |
lastDay.setData(date); | |
} | |
TreeItem ti= new TreeItem(lastDay, SWT.NONE); | |
ti.setImage(getEditionImage(edition, item)); | |
String s= getShortEditionLabel(edition, item, date); | |
if (pair.fHasError) { | |
String pattern= Utilities.getString(fBundle, "parseErrorFormat"); //$NON-NLS-1$ | |
s= MessageFormat.format(pattern, new String[] { s } ); | |
} | |
ti.setText(s); | |
ti.setData(pair); | |
if (first) { | |
fEditionTree.setSelection(new TreeItem[] {ti}); | |
if (!fAddMode) | |
fEditionTree.setFocus(); | |
feedInput(ti); | |
} | |
if (first) // expand first node | |
lastDay.setExpanded(true); | |
} | |
/** | |
* Feeds selection from member viewer to edition viewer. | |
*/ | |
private void handleMemberSelect(Widget w) { | |
Object data= w.getData(); | |
if (data instanceof List) { | |
List editions= (List) data; | |
if (editions != fCurrentEditions) { | |
fCurrentEditions= editions; | |
fEditionTree.removeAll(); | |
String pattern= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ | |
String title= MessageFormat.format(pattern, new Object[] { ((Item)w).getText() }); | |
fEditionPane.setText(title); | |
Iterator iter= editions.iterator(); | |
while (iter.hasNext()) { | |
Object item= iter.next(); | |
if (item instanceof Pair) | |
addEdition((Pair) item); | |
} | |
} | |
} | |
} | |
private void setInput(Object input) { | |
if (!fCompare && input instanceof ICompareInput) { | |
ICompareInput ci= (ICompareInput) input; | |
if (fTargetIsRight) | |
input= ci.getLeft(); | |
else | |
input= ci.getRight(); | |
} | |
fContentPane.setInput(input); | |
if (fStructuredComparePane != null) | |
fStructuredComparePane.setInput(input); | |
} | |
/* | |
* Feeds selection from edition viewer to content (and structure) viewer. | |
*/ | |
private void feedInput(Widget w) { | |
Object input= w.getData(); | |
boolean isOK= false; | |
if (input instanceof Pair) { | |
Pair pair= (Pair) input; | |
fSelectedItem= pair.getItem(); | |
isOK= !pair.fHasError; | |
ITypedElement edition= pair.getEdition(); | |
String editionLabel= getEditionLabel(edition, fSelectedItem); | |
Image editionImage= getEditionImage(edition, fSelectedItem); | |
if (fAddMode) { | |
setInput(fSelectedItem); | |
fContentPane.setText(editionLabel); | |
fContentPane.setImage(editionImage); | |
} else { | |
if (fTargetIsRight) { | |
fCompareConfiguration.setLeftLabel(editionLabel); | |
fCompareConfiguration.setLeftImage(editionImage); | |
setInput(new DiffNode(fSelectedItem, fTargetPair.getItem())); | |
} else { | |
fCompareConfiguration.setRightLabel(editionLabel); | |
fCompareConfiguration.setRightImage(editionImage); | |
setInput(new DiffNode(fTargetPair.getItem(), fSelectedItem)); | |
} | |
} | |
} else { | |
fSelectedItem= null; | |
setInput(null); | |
} | |
if (fCommitButton != null) | |
fCommitButton.setEnabled(isOK && fSelectedItem != null && fTargetPair.getItem() != fSelectedItem); | |
} | |
/* | |
* Feeds selection from structure viewer to content viewer. | |
*/ | |
private void feedInput2(ISelection sel) { | |
if (sel instanceof IStructuredSelection) { | |
IStructuredSelection ss= (IStructuredSelection) sel; | |
if (ss.size() == 1) | |
fContentPane.setInput(ss.getFirstElement()); | |
} | |
} | |
} |