blob: 0d2ed3f64bb1d0068e4719f137dbf343a4953d87 [file] [log] [blame]
package org.eclipse.equinox.internal.p2.ui.dialogs;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.equinox.internal.p2.ui.ProvUIMessages;
import org.eclipse.equinox.internal.provisional.p2.ui.ProvisioningOperationRunner;
import org.eclipse.equinox.internal.provisional.p2.ui.dialogs.IViewMenuProvider;
import org.eclipse.equinox.internal.provisional.p2.ui.query.QueriedElement;
import org.eclipse.equinox.internal.provisional.p2.ui.query.QueryableMetadataRepositoryManager;
import org.eclipse.equinox.internal.provisional.p2.ui.viewers.DeferredQueryContentListener;
import org.eclipse.equinox.internal.provisional.p2.ui.viewers.DeferredQueryContentProvider;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.ControlEnableState;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* FilteredTree extension that creates a check box tree,
* provides a hook for menu creation, and forces synchronous
* fetching of the tree when the first
* filtering is performed.
*
* @since 3.4
*
*/
public class DeferredFetchFilteredTree extends FilteredTree {
private static final String WAIT_STRING = ProvUIMessages.DeferredFetchFilteredTree_RetrievingList;
ToolBar toolBar;
MenuManager menuManager;
ToolItem viewMenuButton;
Display display;
PatternFilter patternFilter;
IViewMenuProvider viewMenuProvider;
DeferredQueryContentProvider contentProvider;
boolean useCheckBoxTree = false;
InputSchedulingRule filterRule;
String savedFilterText;
Job loadJob;
WorkbenchJob filterJob;
ControlEnableState enableState;
Object viewerInput;
ArrayList checkState = new ArrayList();
class InputSchedulingRule implements ISchedulingRule {
Object input;
InputSchedulingRule(Object input) {
this.input = input;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
public boolean isConflicting(ISchedulingRule rule) {
if (rule instanceof InputSchedulingRule) {
InputSchedulingRule other = (InputSchedulingRule) rule;
if (input == null)
return other.getInput() == null;
return input.equals(other.getInput());
}
return false;
}
Object getInput() {
return input;
}
}
public DeferredFetchFilteredTree(Composite parent, int treeStyle, PatternFilter filter, final IViewMenuProvider viewMenuProvider, Display display, boolean useCheckBoxViewer) {
super(parent);
this.display = display;
this.viewMenuProvider = viewMenuProvider;
this.patternFilter = filter;
this.useCheckBoxTree = useCheckBoxViewer;
init(treeStyle, filter);
}
/*
* Overridden to see if filter controls were created.
* If they were not created, we need to create the view menu
* independently.
* (non-Javadoc)
* @see org.eclipse.ui.dialogs.FilteredTree#createControl(org.eclipse.swt.widgets.Composite, int)
*/
protected void createControl(Composite composite, int treeStyle) {
super.createControl(composite, treeStyle);
if (!showFilterControls && viewMenuProvider != null) {
createViewMenu(composite);
}
}
protected TreeViewer doCreateTreeViewer(Composite composite, int style) {
if (useCheckBoxTree) {
final ContainerCheckedTreeViewer v = new ContainerCheckedTreeViewer(composite, style);
v.addCheckStateListener(new ICheckStateListener() {
public void checkStateChanged(CheckStateChangedEvent event) {
// We use an additive check state cache so we need to remove
// previously checked items if the user unchecked them.
if (!event.getChecked() && checkState != null) {
Iterator iter = checkState.iterator();
ArrayList toRemove = new ArrayList(1);
while (iter.hasNext()) {
Object element = iter.next();
if (v.getComparer().equals(element, event.getElement())) {
toRemove.add(element);
// Do not break out of the loop. We may have duplicate equal
// elements in the cache. Since the cache is additive, we want
// to be sure we've gotten everything.
}
}
checkState.removeAll(toRemove);
}
}
});
return v;
}
return super.doCreateTreeViewer(composite, style);
}
protected Composite createFilterControls(Composite filterParent) {
super.createFilterControls(filterParent);
Object layout = filterParent.getLayout();
if (layout instanceof GridLayout) {
((GridLayout) layout).numColumns++;
}
if (viewMenuProvider != null)
createViewMenu(filterParent);
filterParent.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
cancelLoadJob();
}
});
return filterParent;
}
private void createViewMenu(Composite filterParent) {
toolBar = new ToolBar(filterParent, SWT.FLAT);
viewMenuButton = new ToolItem(toolBar, SWT.PUSH, 0);
viewMenuButton.setImage(JFaceResources.getImage(PopupDialog.POPUP_IMG_MENU));
viewMenuButton.setToolTipText(ProvUIMessages.AvailableIUGroup_ViewByToolTipText);
viewMenuButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
showViewMenu();
}
});
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183
toolBar.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
showViewMenu();
}
});
}
void showViewMenu() {
if (menuManager == null) {
menuManager = new MenuManager();
viewMenuProvider.fillViewMenu(menuManager);
}
Menu menu = menuManager.createContextMenu(getShell());
Rectangle bounds = toolBar.getBounds();
Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
topLeft = toolBar.getParent().toDisplay(topLeft);
menu.setLocation(topLeft.x, topLeft.y);
menu.setVisible(true);
}
public void contentProviderSet(final DeferredQueryContentProvider deferredProvider) {
this.contentProvider = deferredProvider;
deferredProvider.addListener(new DeferredQueryContentListener() {
public void inputChanged(Viewer v, Object oldInput, Object newInput) {
if (newInput == null)
return;
// Store the input because it's not reset in the viewer until
// after this listener is run.
viewerInput = newInput;
// Reset the state for remembering check marks
checkState = new ArrayList();
// Cancel the load and filter jobs and null out the scheduling rule
// so that a new one will be created on the new input when needed.
filterRule = null;
cancelLoadJob();
cancelAndResetFilterJob();
contentProvider.setSynchronous(false);
if (showFilterControls && filterText != null && !filterText.isDisposed()) {
// We cancelled the load and if it was in progress the filter
// would have been disabled.
restoreAfterLoading(getInitialText());
}
}
});
}
/*
* Overridden to hook a listener on the job and set the deferred content provider
* to synchronous mode before a filter is done.
* @see org.eclipse.ui.dialogs.FilteredTree#doCreateRefreshJob()
*/
protected WorkbenchJob doCreateRefreshJob() {
// See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=229735
// Ideally we would not have to copy the filtering job, but this
// gives us the most precise control over how and when to preserve
// the check mark state. We have modified the superclass job so
// that everything is expanded rather than recursively expanding
// the tree and checking for the stop time. This simplifies the
// restoration of the correct checkmarks.
filterJob = new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$
public IStatus runInUIThread(IProgressMonitor monitor) {
if (treeViewer.getControl().isDisposed()) {
return Status.CANCEL_STATUS;
}
String text = getFilterString();
if (text == null) {
return Status.OK_STATUS;
}
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
boolean initial = initialText != null && initialText.equals(text);
if (initial) {
patternFilter.setPattern(null);
} else if (text != null) {
patternFilter.setPattern(text);
}
Control redrawFalseControl = treeComposite != null ? treeComposite : treeViewer.getControl();
try {
// don't want the user to see updates that will be made to
// the tree
// we are setting redraw(false) on the composite to avoid
// dancing scrollbar
redrawFalseControl.setRedraw(false);
treeViewer.getTree().setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
rememberLeafCheckState();
treeViewer.refresh(true);
// The superclass did a recursive expand so it could be more responsive to subsequent
// typing. We are expanding all so that we know everything is realized when we go to
// restore the check state afterward.
treeViewer.expandAll();
if (text.length() > 0 && !initial) {
// enabled toolbar - there is text to clear
// and the list is currently being filtered
updateToolbar(true);
} else {
// disabled toolbar - there is no text to clear
// and the list is currently not filtered
updateToolbar(false);
}
} finally {
// done updating the tree - set redraw back to true
TreeItem[] items = getViewer().getTree().getItems();
if (items.length > 0 && getViewer().getTree().getSelectionCount() == 0) {
treeViewer.getTree().setTopItem(items[0]);
}
restoreLeafCheckState();
redrawFalseControl.setRedraw(true);
treeViewer.getTree().setCursor(null);
}
return Status.OK_STATUS;
}
};
filterJob.addJobChangeListener(new JobChangeAdapter() {
public void aboutToRun(final IJobChangeEvent event) {
final boolean[] shouldLoad = new boolean[1];
shouldLoad[0] = false;
display.syncExec(new Runnable() {
public void run() {
if (filterText != null && !filterText.isDisposed()) {
String text = getFilterString();
// If we are about to filter and there is
// actually filtering to do, force a load
// of the input and set the content
// provider to synchronous mode. We want the
// load job to complete before continuing with filtering.
if (text == null || (initialText != null && initialText.equals(text)))
return;
if (!contentProvider.getSynchronous() && loadJob == null) {
if (filterText != null && !filterText.isDisposed()) {
disableWhileLoading();
shouldLoad[0] = true;
}
}
}
}
});
if (shouldLoad[0]) {
event.getJob().sleep();
scheduleLoadJob();
}
}
public void done(IJobChangeEvent event) {
// To be safe, we always reset the scheduling
// rule because the input may have changed since the last run.
event.getJob().setRule(getFilterJobSchedulingRule());
}
});
filterJob.setRule(getFilterJobSchedulingRule());
return filterJob;
}
void disableWhileLoading() {
// We already disabled.
if (enableState != null)
return;
// TODO Knowledge of our client's parent structure is cheating
// but for now our only usage is in one particular widget tree and
// we want to disable at the right place.
if (parent != null && !parent.isDisposed()) {
enableState = ControlEnableState.disable(parent.getParent());
}
if (filterText != null && !filterText.isDisposed()) {
filterText.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
getViewer().getTree().setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
savedFilterText = filterText.getText();
filterText.setText(WAIT_STRING);
}
}
void restoreAfterLoading(String filterTextToRestore) {
// If the filter text was previously disabled, reset the text to
// the previous filter text.
if (filterText != null && !filterText.isDisposed() && !filterText.isEnabled()) {
filterText.setText(filterTextToRestore);
filterText.setCursor(null);
getViewer().getTree().setCursor(null);
filterText.setSelection(filterTextToRestore.length(), filterTextToRestore.length());
}
// Now enable all of the controls
if (enableState != null && parent != null && !parent.isDisposed()) {
enableState.restore();
enableState = null;
}
// Now set the focus back to the filter text
if (filterText != null && !filterText.isDisposed())
filterText.setFocus();
}
InputSchedulingRule getFilterJobSchedulingRule() {
if (filterRule == null) {
filterRule = new InputSchedulingRule(viewerInput);
}
return filterRule;
}
void scheduleLoadJob() {
if (loadJob != null)
return;
loadJob = new Job(WAIT_STRING) {
protected IStatus run(IProgressMonitor monitor) {
if (this.getRule() instanceof InputSchedulingRule) {
Object input = ((InputSchedulingRule) this.getRule()).getInput();
if (input instanceof QueriedElement)
if (((QueriedElement) input).getQueryable() instanceof QueryableMetadataRepositoryManager) {
QueryableMetadataRepositoryManager q = (QueryableMetadataRepositoryManager) ((QueriedElement) input).getQueryable();
q.loadAll(monitor);
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
}
}
return Status.OK_STATUS;
}
};
loadJob.addJobChangeListener(new JobChangeAdapter() {
public void done(IJobChangeEvent event) {
if (event.getResult().isOK()) {
contentProvider.setSynchronous(true);
display.asyncExec(new Runnable() {
public void run() {
// We have just loaded all content. Trigger a viewer expand.
// This really only need be done before filtering, but it can
// be slow the very first time, so we may as well do it while
// the user is already waiting rather than after they expect
// things to be responsive.
if (getViewer() != null && !getViewer().getTree().isDisposed()) {
getViewer().expandAll();
}
restoreAfterLoading(savedFilterText);
}
});
if (filterJob != null)
filterJob.wakeUp();
}
loadJob = null;
}
});
loadJob.setSystem(true);
loadJob.setUser(false);
loadJob.setRule(getFilterJobSchedulingRule());
// Telling the operation runner about it ensures that listeners know we are running
// a provisioning-related job.
ProvisioningOperationRunner.manageJob(loadJob);
loadJob.schedule();
}
void cancelLoadJob() {
if (loadJob != null) {
loadJob.cancel();
loadJob = null;
}
}
void cancelAndResetFilterJob() {
if (filterJob != null) {
filterJob.cancel();
// callers have likely reset the filtering rule.
// We can't reset it here because we don't know that
// the job actually stopped, so we do it in the
// done() handler.
}
}
protected void textChanged() {
// Don't refilter if we are merely resetting the filter back
// to what it was before loading repositories
if (filterText.getText().trim().equals(WAIT_STRING))
return;
super.textChanged();
}
void rememberLeafCheckState() {
if (!useCheckBoxTree)
return;
ContainerCheckedTreeViewer v = (ContainerCheckedTreeViewer) getViewer();
Object[] checked = v.getCheckedElements();
if (checkState == null)
checkState = new ArrayList(checked.length);
for (int i = 0; i < checked.length; i++)
if (!v.getGrayed(checked[i]))
checkState.add(checked[i]);
}
void restoreLeafCheckState() {
if (!useCheckBoxTree)
return;
ContainerCheckedTreeViewer v = (ContainerCheckedTreeViewer) getViewer();
if (v == null || v.getTree().isDisposed())
return;
if (checkState == null)
return;
v.setCheckedElements(new Object[0]);
v.setGrayedElements(new Object[0]);
// Now we are only going to set the check state of the leaf nodes
// and rely on our container checked code to update the parents properly.
Iterator iter = checkState.iterator();
Object element = null;
while (iter.hasNext()) {
element = iter.next();
if (!v.isExpandable(element)) {
// setChecked does an internal expand
v.setChecked(element, true);
}
}
// We are only firing one event, knowing that this is enough for our listeners.
if (element != null)
v.fireCheckStateChanged(element, true);
}
}