blob: fc6c285d89b8ed121f85af352a062734f3dd787e [file] [log] [blame]
/*******************************************************************************
* 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.ltk.internal.ui.refactoring.history;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.resources.IProject;
import org.eclipse.ltk.core.refactoring.RefactoringCore;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy;
import org.eclipse.ltk.core.refactoring.history.RefactoringHistory;
import org.eclipse.ltk.internal.ui.refactoring.Messages;
import org.eclipse.ltk.internal.ui.refactoring.RefactoringUIMessages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
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.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.compare.CompareViewerPane;
import org.eclipse.compare.Splitter;
import org.eclipse.ltk.ui.refactoring.history.IRefactoringHistoryControl;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryContentProvider;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryControlConfiguration;
import org.eclipse.ltk.ui.refactoring.history.RefactoringHistoryLabelProvider;
/**
* Control which is capable of displaying refactoring histories.
*
* @since 3.2
*/
public class RefactoringHistoryControl extends Composite implements IRefactoringHistoryControl {
/** Checkbox tree viewer for the refactoring history */
protected final class RefactoringHistoryTreeViewer extends CheckboxTreeViewer {
/**
* Creates a new refactoring history tree viewer.
*
* @param parent
* the parent control
* @param style
* the style
*/
public RefactoringHistoryTreeViewer(final Composite parent, final int style) {
super(parent, style);
addCheckStateListener(new ICheckStateListener() {
/**
* {@inheritDoc}
*/
public final void checkStateChanged(final CheckStateChangedEvent event) {
reconcileCheckState(event.getElement(), event.getChecked());
handleCheckStateChanged();
}
});
addTreeListener(new ITreeViewerListener() {
/**
* {@inheritDoc}
*/
public final void treeCollapsed(final TreeExpansionEvent event) {
// Do nothing
}
/**
* {@inheritDoc}
*/
public final void treeExpanded(final TreeExpansionEvent event) {
BusyIndicator.showWhile(getDisplay(), new Runnable() {
public final void run() {
final Object element= event.getElement();
if (getGrayed(element)) {
final RefactoringHistory history= RefactoringHistoryControl.this.getInput();
if (history != null)
reconcileCheckState(history);
} else if (getChecked(element)) {
setSubTreeGrayed(element, false);
setSubtreeChecked(element, true);
}
}
});
}
});
}
/**
* Returns the children of the specified element.
*
* @param element
* the element
* @return the children of the element
*/
private Object[] getChildren(final Object element) {
return ((RefactoringHistoryContentProvider) getContentProvider()).getChildren(element);
}
/**
* Reconciles the checkstate of the specified element and dependencies.
*
* @param element
* the changed element
*/
private void reconcileCheckState(final Object element) {
final Object[] children= getChildren(element);
for (int index= 0; index < children.length; index++) {
reconcileCheckState(children[index]);
}
int checkCount= 0;
final Collection collection= getCoveredDescriptors(element);
for (final Iterator iterator= collection.iterator(); iterator.hasNext();) {
final RefactoringDescriptorProxy proxy= (RefactoringDescriptorProxy) iterator.next();
if (fCheckedDescriptors.contains(proxy))
checkCount++;
}
setElementChecked(element, checkCount > 0);
setElementGrayed(element, checkCount != 0 && checkCount != collection.size());
}
/**
* Reconciles the checkstate of the specified element and dependencies.
*
* @param element
* the changed element
* @param checked
* <code>true</code> if the element is checked,
* <code>false</code> otherwise
*/
private void reconcileCheckState(final Object element, final boolean checked) {
if (element instanceof RefactoringHistoryEntry) {
final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) element;
final RefactoringDescriptorProxy proxy= entry.getDescriptor();
if (checked)
fCheckedDescriptors.add(proxy);
else
fCheckedDescriptors.remove(proxy);
} else if (element instanceof RefactoringHistoryNode) {
final Collection collection= getCoveredDescriptors(element);
if (checked)
fCheckedDescriptors.addAll(collection);
else
fCheckedDescriptors.removeAll(collection);
}
RefactoringHistoryControl.this.reconcileCheckState();
}
/**
* Determines whether the specified element is checked.
*
* @param element
* the element
* @param checked
* <code>true</code> to render it checked,
* <code>false</code> otherwise
*/
private void setElementChecked(final Object element, final boolean checked) {
final Widget widget= RefactoringHistoryTreeViewer.this.findItem(element);
if (widget instanceof TreeItem) {
final TreeItem item= (TreeItem) widget;
item.setChecked(checked);
}
}
/**
* Determines whether the specified element is grayed.
*
* @param element
* the element
* @param grayed
* <code>true</code> to render it grayed,
* <code>false</code> otherwise
*/
private void setElementGrayed(final Object element, final boolean grayed) {
final Widget widget= RefactoringHistoryTreeViewer.this.findItem(element);
if (widget instanceof TreeItem) {
final TreeItem item= (TreeItem) widget;
item.setGrayed(grayed);
}
}
/**
* Determines whether the subtree of the specified element is rendered
* grayed.
*
* @param element
* the element specifying the subtree
* @param grayed
* <code>true</code> to render the subtree grayed,
* <code>false</code> otherwise
*/
private void setSubTreeGrayed(final Object element, final boolean grayed) {
setElementGrayed(element, grayed);
final Object[] children= getChildren(element);
for (int index= 0; index < children.length; index++) {
setSubTreeGrayed(children[index], grayed);
}
}
}
/**
* The checked refactoring descriptors (element type:
* <code>RefactoringDescriptorProxy</code>)
*/
private final Set fCheckedDescriptors= new HashSet();
/** The comment field */
private Text fCommentField= null;
/** The refactoring history control configuration to use */
protected final RefactoringHistoryControlConfiguration fControlConfiguration;
/** The history pane */
protected CompareViewerPane fHistoryPane= null;
/** The history viewer */
protected TreeViewer fHistoryViewer= null;
/**
* The selected refactoring descriptors (element type:
* <code>RefactoringDescriptorProxy</code>)
*/
private final Set fSelectedDescriptors= new HashSet();
/** The selection label, or <code>null</code> */
private Label fSelectionLabel= null;
/** The splitter control */
private Splitter fSplitterControl= null;
/**
* Creates a new refactoring history control.
*
* @param parent
* the parent control
* @param configuration
* the refactoring history control configuration to use
*/
public RefactoringHistoryControl(final Composite parent, final RefactoringHistoryControlConfiguration configuration) {
super(parent, SWT.NONE);
Assert.isNotNull(configuration);
fControlConfiguration= configuration;
}
/**
* {@inheritDoc}
*/
public final void addCheckStateListener(final ICheckStateListener listener) {
Assert.isNotNull(listener);
if (fHistoryViewer instanceof RefactoringHistoryTreeViewer) {
final RefactoringHistoryTreeViewer viewer= (RefactoringHistoryTreeViewer) fHistoryViewer;
viewer.addCheckStateListener(listener);
}
}
/**
* {@inheritDoc}
*/
public final void addSelectionChangedListener(final ISelectionChangedListener listener) {
Assert.isNotNull(listener);
if (fHistoryViewer != null)
fHistoryViewer.addSelectionChangedListener(listener);
}
/**
* Creates the button bar at the bottom of the component.
*
* @param parent
* the parent composite
*/
protected void createBottomButtonBar(final Composite parent) {
// Do nothing
}
/**
* {@inheritDoc}
*/
public void createControl() {
RefactoringCore.getHistoryService().connect();
GridLayout layout= new GridLayout(getContainerColumns(), false);
layout.marginHeight= 0;
layout.marginWidth= 0;
layout.horizontalSpacing= 0;
layout.verticalSpacing= 0;
setLayout(layout);
setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
fSplitterControl= new Splitter(this, SWT.VERTICAL);
fSplitterControl.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
createRightButtonBar(this);
final Composite leftPane= new Composite(fSplitterControl, SWT.NONE);
layout= new GridLayout();
layout.marginWidth= 0;
layout.marginHeight= 2;
layout.verticalSpacing= 2;
leftPane.setLayout(layout);
fHistoryPane= new CompareViewerPane(leftPane, SWT.BORDER | SWT.FLAT);
final CLabel label= new CLabel(fHistoryPane, SWT.NONE) {
public final Point computeSize(final int wHint, final int hHint, final boolean changed) {
return super.computeSize(wHint, Math.max(24, hHint), changed);
}
};
fHistoryPane.setTopLeft(label);
fHistoryPane.setText(getHistoryPaneText());
fHistoryPane.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
fHistoryViewer= createHistoryViewer(fHistoryPane);
if (!fControlConfiguration.isTimeDisplayed())
fHistoryViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
fHistoryViewer.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));
fHistoryViewer.setUseHashlookup(true);
fHistoryViewer.setContentProvider(getContentProvider());
fHistoryViewer.setLabelProvider(getLabelProvider());
fHistoryViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public final void selectionChanged(final SelectionChangedEvent event) {
final ISelection selection= event.getSelection();
if (selection instanceof IStructuredSelection)
handleSelectionChanged((IStructuredSelection) selection);
}
});
fHistoryPane.setContent(fHistoryViewer.getControl());
createToolBar(fHistoryPane);
createDetailPane(fSplitterControl);
final MouseAdapter listener= new MouseAdapter() {
public void mouseDoubleClick(final MouseEvent event) {
final Control content= fHistoryPane.getContent();
if (content != null && content.getBounds().contains(event.x, event.y))
return;
final Control control= fHistoryPane.getParent().getParent();
if (control instanceof Splitter)
((Splitter) control).setMaximizedControl(fHistoryPane.getParent());
}
};
addMouseListener(listener);
fHistoryPane.getTopLeft().addMouseListener(listener);
fSplitterControl.setWeights(new int[] { 65, 35});
createBottomButtonBar(this);
Dialog.applyDialogFont(this);
}
/**
* Creates the detail label of the control
*
* @param parent
* the parent composite
*/
protected void createDetailLabel(final Composite parent) {
Assert.isNotNull(parent);
final Label label= new Label(parent, SWT.HORIZONTAL | SWT.LEFT | SWT.WRAP);
label.setText(RefactoringUIMessages.RefactoringHistoryControl_comment_viewer_label);
final GridData data= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
data.horizontalSpan= 1;
label.setLayoutData(data);
}
/**
* Creates the detail pane of the control.
*
* @param parent
* the parent composite
* @return the detail pane
*/
protected Composite createDetailPane(final Composite parent) {
Assert.isNotNull(parent);
final Composite composite= new Composite(parent, SWT.NONE);
final GridLayout layout= new GridLayout(getDetailColumns(), true);
layout.horizontalSpacing= 0;
layout.marginHeight= 0;
layout.marginWidth= 0;
composite.setLayout(layout);
createDetailLabel(composite);
createSelectionLabel(composite);
fCommentField= new Text(composite, SWT.BORDER | SWT.FLAT | SWT.MULTI | SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL);
fCommentField.setText(fControlConfiguration.getCommentCaption());
final GridData data= new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
data.horizontalSpan= getDetailColumns();
fCommentField.setLayoutData(data);
return composite;
}
/**
* Creates the history viewer of the control.
*
* @param parent
* the parent composite
* @return the history viewer
*/
protected TreeViewer createHistoryViewer(final Composite parent) {
Assert.isNotNull(parent);
if (fControlConfiguration.isCheckableViewer())
return new RefactoringHistoryTreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL);
else
return new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL);
}
/**
* Creates the button bar at the right of the component.
*
* @param parent
* the parent composite
*/
protected void createRightButtonBar(final Composite parent) {
// Do nothing
}
/**
* Creates the selection label of the control
*
* @param parent
* the parent composite
*/
protected void createSelectionLabel(final Composite parent) {
Assert.isNotNull(parent);
fSelectionLabel= new Label(parent, SWT.HORIZONTAL | SWT.RIGHT | SWT.WRAP);
final GridData data= new GridData(GridData.HORIZONTAL_ALIGN_END);
data.horizontalSpan= 1;
fSelectionLabel.setLayoutData(data);
}
/**
* Creates the toolbar of the control.
*
* @param parent
* the parent control
*/
protected void createToolBar(final ViewForm parent) {
// Do nothing
}
/**
* {@inheritDoc}
*/
public final void dispose() {
RefactoringCore.getHistoryService().disconnect();
super.dispose();
}
/**
* {@inheritDoc}
*/
public final RefactoringDescriptorProxy[] getCheckedDescriptors() {
if (fHistoryViewer instanceof RefactoringHistoryTreeViewer)
return (RefactoringDescriptorProxy[]) fCheckedDescriptors.toArray(new RefactoringDescriptorProxy[fCheckedDescriptors.size()]);
return getSelectedDescriptors();
}
/**
* Returns the number of columns to use for the container layout.
*
* @return the number of columns
*/
protected int getContainerColumns() {
return 2;
}
/**
* Returns the content provider to use.
*
* @return the content provider
*/
protected RefactoringHistoryContentProvider getContentProvider() {
return fControlConfiguration.getContentProvider();
}
/**
* {@inheritDoc}
*/
public final Control getControl() {
return this;
}
/**
* Returns the refactoring descriptors covered by the specified node.
*
* @param element
* the refactoring history element
* @return the collection of covered descriptors
*/
private Collection getCoveredDescriptors(final Object element) {
Assert.isNotNull(element);
final Set set= new HashSet();
getCoveredDescriptors(element, set);
return set;
}
/**
* Computes the refactoring descriptors covered by the specified node.
*
* @param element
* the refactoring history element
* @param set
* the set of refactoring descriptor proxies
*/
private void getCoveredDescriptors(final Object element, final Set set) {
Assert.isNotNull(element);
Assert.isNotNull(set);
final RefactoringHistoryContentProvider provider= (RefactoringHistoryContentProvider) fHistoryViewer.getContentProvider();
if (provider != null) {
if (element instanceof RefactoringHistoryEntry) {
final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) element;
final RefactoringDescriptorProxy proxy= entry.getDescriptor();
set.add(proxy);
} else {
final Object[] children= provider.getChildren(element);
for (int index= 0; index < children.length; index++) {
final Object child= children[index];
if (child instanceof RefactoringHistoryNode)
getCoveredDescriptors(child, set);
}
}
}
}
/**
* Returns the number of columns to use for the detail pane layout.
*
* @return the number of columns
*/
protected int getDetailColumns() {
return 2;
}
/**
* Returns the text to be displayed in the history pane.
*
* @return the text in the history pane
*/
private String getHistoryPaneText() {
String text= null;
final IProject project= fControlConfiguration.getProject();
if (project != null)
text= Messages.format(fControlConfiguration.getProjectPattern(), project.getName());
else
text= fControlConfiguration.getWorkspaceCaption();
return text;
}
/**
* Returns the input of the refactoring history control.
*
* @return the input, or <code>null</code>
*/
public final RefactoringHistory getInput() {
return (RefactoringHistory) fHistoryViewer.getInput();
}
/**
* Returns the label provider to use.
*
* @return the label provider
*/
protected RefactoringHistoryLabelProvider getLabelProvider() {
return fControlConfiguration.getLabelProvider();
}
/**
* {@inheritDoc}
*/
public final RefactoringDescriptorProxy[] getSelectedDescriptors() {
return (RefactoringDescriptorProxy[]) fSelectedDescriptors.toArray(new RefactoringDescriptorProxy[fSelectedDescriptors.size()]);
}
/**
* Handles the check state changed event.
*/
protected void handleCheckStateChanged() {
if (fSelectionLabel != null) {
final RefactoringHistory history= getInput();
if (history != null) {
final int total= history.getDescriptors().length;
final int checked= fCheckedDescriptors.size();
if (total > 0 && fControlConfiguration.isCheckableViewer())
fSelectionLabel.setText(Messages.format(RefactoringUIMessages.RefactoringHistoryControl_selection_pattern, new String[] { String.valueOf(checked), String.valueOf(total)}));
else
fSelectionLabel.setText(RefactoringUIMessages.RefactoringHistoryControl_no_selection);
}
}
}
/**
* Handles the selection changed event.
*
* @param selection
* the new selection
*/
protected void handleSelectionChanged(final IStructuredSelection selection) {
Assert.isNotNull(selection);
fSelectedDescriptors.clear();
final Object[] elements= selection.toArray();
for (int index= 0; index < elements.length; index++) {
final Object element= elements[index];
if (element instanceof RefactoringHistoryEntry) {
final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) element;
final RefactoringDescriptorProxy proxy= entry.getDescriptor();
fSelectedDescriptors.add(proxy);
} else if (element instanceof RefactoringHistoryNode) {
final RefactoringHistoryNode node= (RefactoringHistoryNode) element;
fSelectedDescriptors.addAll(getCoveredDescriptors(node));
}
}
if (elements.length == 1 && elements[0] instanceof RefactoringHistoryEntry) {
final RefactoringHistoryEntry entry= (RefactoringHistoryEntry) elements[0];
final RefactoringDescriptorProxy proxy= entry.getDescriptor();
final Job job= new UIJob(RefactoringUIMessages.RefactoringHistoryControl_resolving_information) {
public final IStatus runInUIThread(final IProgressMonitor monitor) {
final RefactoringDescriptor descriptor= proxy.requestDescriptor(monitor);
if (descriptor != null) {
String comment= descriptor.getComment();
if ("".equals(comment)) //$NON-NLS-1$
comment= RefactoringUIMessages.RefactoringHistoryControl_no_comment;
fCommentField.setText(comment);
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
fCommentField.setText(fControlConfiguration.getCommentCaption());
}
/**
* Reconciles the check state of the control.
*/
public void reconcileCheckState() {
final RefactoringHistory history= getInput();
if (history != null && fHistoryViewer instanceof RefactoringHistoryTreeViewer)
((RefactoringHistoryTreeViewer) fHistoryViewer).reconcileCheckState(history);
}
/**
* {@inheritDoc}
*/
public final void removeCheckStateListener(final ICheckStateListener listener) {
Assert.isNotNull(listener);
Assert.isNotNull(listener);
if (fHistoryViewer instanceof RefactoringHistoryTreeViewer) {
final RefactoringHistoryTreeViewer viewer= (RefactoringHistoryTreeViewer) fHistoryViewer;
viewer.removeCheckStateListener(listener);
}
}
/**
* {@inheritDoc}
*/
public final void removeSelectionChangedListener(final ISelectionChangedListener listener) {
Assert.isNotNull(listener);
if (fHistoryViewer != null)
fHistoryViewer.removeSelectionChangedListener(listener);
}
/**
* {@inheritDoc}
*/
public final void setCheckedDescriptors(final RefactoringDescriptorProxy[] descriptors) {
Assert.isNotNull(descriptors);
if (fHistoryViewer instanceof RefactoringHistoryTreeViewer) {
fCheckedDescriptors.clear();
fCheckedDescriptors.addAll(Arrays.asList(descriptors));
final RefactoringHistoryTreeViewer viewer= (RefactoringHistoryTreeViewer) fHistoryViewer;
final RefactoringHistoryNode[] nodes= new RefactoringHistoryNode[descriptors.length];
for (int index= 0; index < descriptors.length; index++)
nodes[index]= new RefactoringHistoryEntry(null, descriptors[index]);
final RefactoringHistory history= RefactoringHistoryControl.this.getInput();
if (history != null)
viewer.reconcileCheckState(history);
handleCheckStateChanged();
} else
setSelectedDescriptors(descriptors);
}
/**
* Sets the expanded state of the viewer.
*/
protected void setExpandedState() {
final RefactoringHistoryContentProvider provider= (RefactoringHistoryContentProvider) fHistoryViewer.getContentProvider();
if (provider != null) {
handleCheckStateChanged();
final Object[] roots= provider.getRootElements();
if (roots != null) {
for (int index= 0; index < roots.length; index++) {
if (!(roots[index] instanceof RefactoringHistoryEntry)) {
fHistoryViewer.setExpandedState(roots[index], true);
return;
}
}
}
}
}
/**
* {@inheritDoc}
*/
public void setInput(final RefactoringHistory history) {
fHistoryViewer.setInput(history);
fSelectedDescriptors.clear();
fCheckedDescriptors.clear();
if (history != null) {
final RefactoringHistoryContentProvider provider= (RefactoringHistoryContentProvider) fHistoryViewer.getContentProvider();
if (provider != null) {
provider.inputChanged(fHistoryViewer, null, history);
setExpandedState();
}
}
}
/**
* {@inheritDoc}
*/
public final void setSelectedDescriptors(final RefactoringDescriptorProxy[] descriptors) {
Assert.isNotNull(descriptors);
if (fHistoryViewer != null) {
fSelectedDescriptors.clear();
fSelectedDescriptors.addAll(Arrays.asList(descriptors));
final RefactoringHistoryNode[] nodes= new RefactoringHistoryNode[descriptors.length];
for (int index= 0; index < descriptors.length; index++)
nodes[index]= new RefactoringHistoryEntry(null, descriptors[index]);
fHistoryViewer.setSelection(new StructuredSelection(nodes));
}
}
}