/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
package org.eclipse.compare.structuremergeviewer; | |
import java.util.Iterator; | |
import java.util.ResourceBundle; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.events.DisposeEvent; | |
import org.eclipse.swt.events.FocusListener; | |
import org.eclipse.swt.graphics.Image; | |
import org.eclipse.swt.widgets.*; | |
import org.eclipse.jface.util.*; | |
import org.eclipse.jface.action.*; | |
import org.eclipse.jface.viewers.*; | |
import org.eclipse.jface.preference.IPreferenceStore; | |
import org.eclipse.compare.internal.*; | |
import org.eclipse.compare.*; | |
/** | |
* A tree viewer that works on objects implementing | |
* the <code>IDiffContainer</code> and <code>IDiffElement</code> interfaces. | |
* <p> | |
* This class may be instantiated; it is not intended to be subclassed outside | |
* this package. | |
* </p> | |
* | |
* @see IDiffContainer | |
* @see IDiffElement | |
*/ | |
public class DiffTreeViewer extends TreeViewer { | |
static class DiffViewerSorter 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; | |
} | |
} | |
class DiffViewerContentProvider implements ITreeContentProvider { | |
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { | |
} | |
public boolean isDeleted(Object element) { | |
return false; | |
} | |
public void dispose() { | |
inputChanged(DiffTreeViewer.this, getInput(), null); | |
} | |
public Object getParent(Object element) { | |
if (element instanceof IDiffElement) | |
return ((IDiffElement)element).getParent(); | |
return null; | |
} | |
public final boolean hasChildren(Object element) { | |
if (element instanceof IDiffContainer) | |
return ((IDiffContainer)element).hasChildren(); | |
return false; | |
} | |
public final Object[] getChildren(Object element) { | |
if (element instanceof IDiffContainer) | |
return ((IDiffContainer)element).getChildren(); | |
return new Object[0]; | |
} | |
public Object[] getElements(Object element) { | |
return getChildren(element); | |
} | |
} | |
/* | |
* Takes care of swapping left and right if fLeftIsLocal | |
* is true. | |
*/ | |
class DiffViewerLabelProvider extends LabelProvider { | |
public String getText(Object element) { | |
if (element instanceof IDiffElement) | |
return ((IDiffElement)element).getName(); | |
return Utilities.getString(fBundle, "defaultLabel"); //$NON-NLS-1$ | |
} | |
public Image getImage(Object element) { | |
if (element instanceof IDiffElement) { | |
IDiffElement input= (IDiffElement) element; | |
int kind= input.getKind(); | |
if (fLeftIsLocal) { | |
switch (kind & Differencer.DIRECTION_MASK) { | |
case Differencer.LEFT: | |
kind= (kind &~ Differencer.LEFT) | Differencer.RIGHT; | |
break; | |
case Differencer.RIGHT: | |
kind= (kind &~ Differencer.RIGHT) | Differencer.LEFT; | |
break; | |
} | |
} | |
return fCompareConfiguration.getImage(input.getImage(), kind); | |
} | |
return null; | |
} | |
} | |
static class FilterSame extends ViewerFilter { | |
public boolean select(Viewer viewer, Object parentElement, Object element) { | |
if (element instanceof IDiffElement) | |
return (((IDiffElement)element).getKind() & Differencer.PSEUDO_CONFLICT) == 0; | |
return true; | |
} | |
public boolean isFilterProperty(Object element, Object property) { | |
return false; | |
} | |
} | |
private ResourceBundle fBundle; | |
private CompareConfiguration fCompareConfiguration; | |
/* package */ boolean fLeftIsLocal; | |
private ViewerFilter fViewerFilter; | |
private IPropertyChangeListener fPropertyChangeListener; | |
//private IPropertyChangeListener fPreferenceChangeListener; | |
private Action fCopyLeftToRightAction; | |
private Action fCopyRightToLeftAction; | |
private Action fNextAction; | |
private Action fPreviousAction; | |
private Action fEmptyMenuAction; | |
private Action fExpandAllAction; | |
/** | |
* Creates a new viewer for the given SWT tree control with the specified configuration. | |
* | |
* @param tree the tree control | |
* @param configuration the configuration for this viewer | |
*/ | |
public DiffTreeViewer(Tree tree, CompareConfiguration configuration) { | |
super(tree); | |
initialize(configuration); | |
} | |
/** | |
* Creates a new viewer under the given SWT parent and with the specified configuration. | |
* | |
* @param parent the SWT control under which to create the viewer | |
* @param configuration the configuration for this viewer | |
*/ | |
public DiffTreeViewer(Composite parent, CompareConfiguration configuration) { | |
super(new Tree(parent, SWT.MULTI)); | |
initialize(configuration); | |
} | |
private void initialize(CompareConfiguration configuration) { | |
Control tree= getControl(); | |
INavigatable nav= new INavigatable() { | |
public boolean gotoDifference(boolean next) { | |
return internalNavigate(next); | |
} | |
}; | |
tree.setData(INavigatable.NAVIGATOR_PROPERTY, nav); | |
fLeftIsLocal= Utilities.getBoolean(configuration, "LEFT_IS_LOCAL", false); //$NON-NLS-1$ | |
tree.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle()); | |
Composite parent= tree.getParent(); | |
fBundle= ResourceBundle.getBundle("org.eclipse.compare.structuremergeviewer.DiffTreeViewerResources"); //$NON-NLS-1$ | |
// register for notification with the CompareConfiguration | |
fCompareConfiguration= configuration; | |
if (fCompareConfiguration != null) { | |
fPropertyChangeListener= new IPropertyChangeListener() { | |
public void propertyChange(PropertyChangeEvent event) { | |
DiffTreeViewer.this.propertyChange(event); | |
} | |
}; | |
fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener); | |
} | |
// register for notification with the Compare plugin's PreferenceStore | |
/* | |
fPreferenceChangeListener= new IPropertyChangeListener() { | |
public void propertyChange(PropertyChangeEvent event) { | |
if (event.getProperty().equals(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS)) | |
syncShowPseudoConflictFilter(); | |
} | |
}; | |
IPreferenceStore ps= CompareUIPlugin.getDefault().getPreferenceStore(); | |
if (ps != null) | |
ps.addPropertyChangeListener(fPreferenceChangeListener); | |
*/ | |
setContentProvider(new DiffViewerContentProvider()); | |
setLabelProvider(new DiffViewerLabelProvider()); | |
addSelectionChangedListener( | |
new ISelectionChangedListener() { | |
public void selectionChanged(SelectionChangedEvent se) { | |
updateActions(); | |
} | |
} | |
); | |
// syncShowPseudoConflictFilter(); | |
setSorter(new DiffViewerSorter()); | |
ToolBarManager tbm= CompareViewerSwitchingPane.getToolBarManager(parent); | |
if (tbm != null) { | |
tbm.removeAll(); | |
tbm.add(new Separator("merge")); //$NON-NLS-1$ | |
tbm.add(new Separator("modes")); //$NON-NLS-1$ | |
tbm.add(new Separator("navigation")); //$NON-NLS-1$ | |
createToolItems(tbm); | |
updateActions(); | |
tbm.update(true); | |
} | |
MenuManager mm= new MenuManager(); | |
mm.setRemoveAllWhenShown(true); | |
mm.addMenuListener( | |
new IMenuListener() { | |
public void menuAboutToShow(IMenuManager mm) { | |
fillContextMenu(mm); | |
if (mm.isEmpty()) { | |
if (fEmptyMenuAction == null) { | |
fEmptyMenuAction= | |
new Action(Utilities.getString(fBundle, "emptyMenuItem")) {}; //$NON-NLS-1$ | |
fEmptyMenuAction.setEnabled(false); | |
} | |
mm.add(fEmptyMenuAction); | |
} | |
} | |
} | |
); | |
tree.setMenu(mm.createContextMenu(tree)); | |
} | |
/** | |
* Returns the viewer's name. | |
* | |
* @return the viewer's name | |
*/ | |
public String getTitle() { | |
String title= Utilities.getString(fBundle, "title", null); //$NON-NLS-1$ | |
if (title == null) | |
title= Utilities.getString("DiffTreeViewer.title"); //$NON-NLS-1$ | |
return title; | |
} | |
/** | |
* Returns the resource bundle. | |
* | |
* @return the viewer's resource bundle | |
*/ | |
protected ResourceBundle getBundle() { | |
return fBundle; | |
} | |
/** | |
* Returns the compare configuration of this viewer. | |
* | |
* @return the compare configuration of this viewer | |
*/ | |
public CompareConfiguration getCompareConfiguration() { | |
return fCompareConfiguration; | |
} | |
/** | |
* Called on the viewer disposal. | |
* Unregisters from the compare configuration. | |
* Clients may extend if they have to do additional cleanup. | |
*/ | |
protected void handleDispose(DisposeEvent event) { | |
/* | |
if (fPreferenceChangeListener != null) { | |
IPreferenceStore ps= CompareUIPlugin.getDefault().getPreferenceStore(); | |
if (ps != null) | |
ps.removePropertyChangeListener(fPreferenceChangeListener); | |
fPreferenceChangeListener= null; | |
} | |
*/ | |
if (fCompareConfiguration != null) { | |
if (fPropertyChangeListener != null) | |
fCompareConfiguration.removePropertyChangeListener(fPropertyChangeListener); | |
fCompareConfiguration= null; | |
} | |
fPropertyChangeListener= null; | |
super.handleDispose(event); | |
} | |
/** | |
* Tracks property changes of the configuration object. | |
* Clients may extend to track their own property changes. | |
*/ | |
protected void propertyChange(PropertyChangeEvent event) { | |
} | |
protected void inputChanged(Object in, Object oldInput) { | |
super.inputChanged(in, oldInput); | |
if (in != oldInput) { | |
initialSelection(); | |
updateActions(); | |
} | |
} | |
/** | |
* This hook method is called from within <code>inputChanged</code> | |
* after a new input has been set but before any controls are updated. | |
* This default implementation calls <code>navigate(true)<code> | |
* to select and expand the first leaf node. | |
* Clients can override this method and are free to decide whether | |
* they want to call the inherited method. | |
*/ | |
protected void initialSelection() { | |
navigate(true); | |
} | |
/** | |
* Overridden to avoid expanding <code>DiffNode</code>s that shouldn't expand | |
* (i.e. where the <code>dontExpand</code> method returns <code>true</code>). | |
*/ | |
protected void internalExpandToLevel(Widget node, int level) { | |
Object data= node.getData(); | |
if (dontExpand(data)) | |
return; | |
super.internalExpandToLevel(node, level); | |
} | |
protected boolean dontExpand(Object o) { | |
return o instanceof DiffNode && ((DiffNode)o).dontExpand(); | |
} | |
//---- merge action support | |
/** | |
* This factory method is called after the viewer's controls have been created. | |
* It installs four actions in the given <code>ToolBarManager</code>. Two actions | |
* allow for copying one side of a <code>DiffNode</code> to the other side. | |
* Two other actions are for navigating from one node to the next (previous). | |
* <p> | |
* Clients can override this method and are free to decide whether they want to call | |
* the inherited method. | |
* | |
* @param toolbarManager the toolbar manager for which to add the actions | |
*/ | |
protected void createToolItems(ToolBarManager toolbarManager) { | |
// fCopyLeftToRightAction= new Action() { | |
// public void run() { | |
// copySelected(true); | |
// } | |
// }; | |
// Utilities.initAction(fCopyLeftToRightAction, fBundle, "action.TakeLeft."); | |
// toolbarManager.appendToGroup("merge", fCopyLeftToRightAction); | |
// fCopyRightToLeftAction= new Action() { | |
// public void run() { | |
// copySelected(false); | |
// } | |
// }; | |
// Utilities.initAction(fCopyRightToLeftAction, fBundle, "action.TakeRight."); | |
// toolbarManager.appendToGroup("merge", fCopyRightToLeftAction); | |
// fNextAction= new Action() { | |
// public void run() { | |
// navigate(true); | |
// } | |
// }; | |
// Utilities.initAction(fNextAction, fBundle, "action.NextDiff."); //$NON-NLS-1$ | |
// toolbarManager.appendToGroup("navigation", fNextAction); //$NON-NLS-1$ | |
// fPreviousAction= new Action() { | |
// public void run() { | |
// navigate(false); | |
// } | |
// }; | |
// Utilities.initAction(fPreviousAction, fBundle, "action.PrevDiff."); //$NON-NLS-1$ | |
// toolbarManager.appendToGroup("navigation", fPreviousAction); //$NON-NLS-1$ | |
} | |
/** | |
* This method is called to add actions to the viewer's context menu. | |
* It installs actions for expanding tree nodes, copying one side of a <code>DiffNode</code> to the other side. | |
* Clients can override this method and are free to decide whether they want to call | |
* the inherited method. | |
* | |
* @param manager the menu manager for which to add the actions | |
*/ | |
protected void fillContextMenu(IMenuManager manager) { | |
if (fExpandAllAction == null) { | |
fExpandAllAction= new Action() { | |
public void run() { | |
expandSelection(); | |
} | |
}; | |
Utilities.initAction(fExpandAllAction, fBundle, "action.ExpandAll."); //$NON-NLS-1$ | |
} | |
ISelection selection= getSelection(); | |
fExpandAllAction.setEnabled(selection != null && !selection.isEmpty()); | |
manager.add(fExpandAllAction); | |
if (fCopyLeftToRightAction != null) | |
manager.add(fCopyLeftToRightAction); | |
if (fCopyRightToLeftAction != null) | |
manager.add(fCopyRightToLeftAction); | |
} | |
/** | |
* Expands to infinity all items in the selection. | |
*/ | |
protected void expandSelection() { | |
ISelection selection= getSelection(); | |
if (selection instanceof IStructuredSelection) { | |
Iterator elements= ((IStructuredSelection)selection).iterator(); | |
while (elements.hasNext()) { | |
Object next= elements.next(); | |
expandToLevel(next, ALL_LEVELS); | |
} | |
} | |
} | |
/** | |
* Copies one side of all <code>DiffNode</code>s in the current selection to the other side. | |
* Called from the (internal) actions for copying the sides of a <code>DiffNode</code>. | |
* Clients may override. | |
* | |
* @param leftToRight if <code>true</code> the left side is copied to the right side. | |
* If <code>false</code> the right side is copied to the left side | |
*/ | |
protected void copySelected(boolean leftToRight) { | |
ISelection selection= getSelection(); | |
if (selection instanceof IStructuredSelection) { | |
Iterator e= ((IStructuredSelection) selection).iterator(); | |
while (e.hasNext()) { | |
Object element= e.next(); | |
if (element instanceof ICompareInput) | |
copyOne((ICompareInput) element, leftToRight); | |
} | |
} | |
} | |
/** | |
* Called to copy one side of the given node to the other. | |
* This default implementation delegates the call to <code>ICompareInput.copy(...)</code>. | |
* Clients may override. | |
* | |
* @param leftToRight if <code>true</code> the left side is copied to the right side. | |
* If <code>false</code> the right side is copied to the left side | |
*/ | |
protected void copyOne(ICompareInput node, boolean leftToRight) { | |
node.copy(leftToRight); | |
// update node's image | |
update(new Object[] { node }, null); | |
} | |
/** | |
* Selects the next (or previous) node of the current selection. | |
* If there is no current selection the first (last) node in the tree is selected. | |
* Wraps around at end or beginning. | |
* Clients may override. | |
* | |
* @param next if <code>true</code> the next node is selected, otherwise the previous node | |
*/ | |
protected void navigate(boolean next) { | |
internalNavigate(next); | |
} | |
//---- private | |
/** | |
* Selects the next (or previous) node of the current selection. | |
* If there is no current selection the first (last) node in the tree is selected. | |
* Wraps around at end or beginning. | |
* Clients may override. | |
* | |
* @param next if <code>true</code> the next node is selected, otherwise the previous node | |
* @return <code>true</code> if at end (or beginning) | |
*/ | |
private boolean internalNavigate(boolean next) { | |
Control c= getControl(); | |
if (!(c instanceof Tree)) | |
return false; | |
Tree tree= (Tree) c; | |
TreeItem item= null; | |
TreeItem children[]= tree.getSelection(); | |
if (children != null && children.length > 0) | |
item= children[0]; | |
if (item == null) { | |
children= tree.getItems(); | |
if (children != null && children.length > 0) { | |
item= children[0]; | |
if (item != null && item.getItemCount() <= 0) { | |
internalSetSelection(item); | |
return false; | |
} | |
} | |
} | |
while (true) { | |
item= findNextPrev(item, next); | |
if (item == null) | |
break; | |
if (item.getItemCount() <= 0) | |
break; | |
} | |
if (item != null) { | |
internalSetSelection(item); | |
return false; | |
} | |
return true; | |
} | |
private TreeItem findNextPrev(TreeItem item, boolean next) { | |
if (item == null) | |
return null; | |
TreeItem children[]= null; | |
if (!next) { | |
TreeItem parent= item.getParentItem(); | |
if (parent != null) | |
children= parent.getItems(); | |
else | |
children= item.getParent().getItems(); | |
if (children != null && children.length > 0) { | |
// goto previous child | |
int index= 0; | |
for (; index < children.length; index++) | |
if (children[index] == item) | |
break; | |
if (index > 0) { | |
item= children[index-1]; | |
while (true) { | |
createChildren(item); | |
int n= item.getItemCount(); | |
if (n <= 0) | |
break; | |
item.setExpanded(true); | |
item= item.getItems()[n-1]; | |
} | |
// previous | |
return item; | |
} | |
} | |
// go up | |
return parent; | |
} else { | |
item.setExpanded(true); | |
createChildren(item); | |
if (item.getItemCount() > 0) { | |
// has children: go down | |
children= item.getItems(); | |
return children[0]; | |
} | |
while (item != null) { | |
children= null; | |
TreeItem parent= item.getParentItem(); | |
if (parent != null) | |
children= parent.getItems(); | |
else | |
children= item.getParent().getItems(); | |
if (children != null && children.length > 0) { | |
// goto next child | |
int index= 0; | |
for (; index < children.length; index++) | |
if (children[index] == item) | |
break; | |
if (index < children.length-1) { | |
// next | |
return children[index+1]; | |
} | |
} | |
// go up | |
item= parent; | |
} | |
} | |
return item; | |
} | |
private void internalSetSelection(TreeItem ti) { | |
if (ti != null) { | |
Object data= ti.getData(); | |
if (data != null) | |
setSelection(new StructuredSelection(data), true); | |
} | |
} | |
/* | |
private void syncShowPseudoConflictFilter() { | |
IPreferenceStore ps= CompareUIPlugin.getDefault().getPreferenceStore(); | |
boolean showPseudoConflicts= ps.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS); | |
Control tree= getControl(); | |
if (!tree.isDisposed()) { | |
if (showPseudoConflicts) { | |
if (fViewerFilter != null) { | |
removeFilter(fViewerFilter); | |
} | |
} else { | |
if (fViewerFilter == null) | |
fViewerFilter= new FilterSame(); | |
addFilter(fViewerFilter); | |
} | |
} | |
} | |
*/ | |
private final boolean isEditable(Object element, boolean left) { | |
if (element instanceof ICompareInput) { | |
ICompareInput diff= (ICompareInput) element; | |
Object side= left ? diff.getLeft() : diff.getRight(); | |
if (side == null && diff instanceof IDiffElement) { | |
IDiffContainer container= ((IDiffElement)diff).getParent(); | |
if (container instanceof ICompareInput) { | |
ICompareInput parent= (ICompareInput) container; | |
side= left ? parent.getLeft() : parent.getRight(); | |
} | |
} | |
if (side instanceof IEditableContent) | |
return ((IEditableContent) side).isEditable(); | |
} | |
return false; | |
} | |
private void updateActions() { | |
int leftToRight= 0; | |
int rightToLeft= 0; | |
ISelection selection= getSelection(); | |
if (selection instanceof IStructuredSelection) { | |
IStructuredSelection ss= (IStructuredSelection) selection; | |
Iterator e= ss.iterator(); | |
while (e.hasNext()) { | |
Object element= e.next(); | |
if (element instanceof ICompareInput) { | |
if (isEditable(element, false)) | |
leftToRight++; | |
if (isEditable(element, true)) | |
rightToLeft++; | |
if (leftToRight > 0 && rightToLeft > 0) | |
break; | |
} | |
} | |
if (fExpandAllAction != null) | |
fExpandAllAction.setEnabled(selection.isEmpty()); | |
} | |
if (fCopyLeftToRightAction != null) | |
fCopyLeftToRightAction.setEnabled(leftToRight > 0); | |
if (fCopyRightToLeftAction != null) | |
fCopyRightToLeftAction.setEnabled(rightToLeft > 0); | |
} | |
} | |