/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.ui.viewsupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.viewers.deferred.AbstractConcurrentModel;
import org.eclipse.jface.viewers.deferred.DeferredContentProvider;
import org.eclipse.jface.viewers.deferred.IConcurrentModelListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.ListDialog;
import org.teiid.core.designer.util.I18nUtil;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.workspace.WorkspaceResourceFinderUtil;
import org.teiid.designer.ui.UiConstants;
import org.teiid.designer.ui.common.eventsupport.SelectionUtilities;
import org.teiid.designer.ui.filter.StructuredViewerTextFilterer;
/**
* ModelObjectListDialog is a SelectionDialog for displaying and selecting a list of EObjects.
*
* @since 8.0
*/
public class ModelObjectListDialog extends ListDialog implements IFilter.IConstants, ISelectionChangedListener {
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
/** Properties key prefix. */
private static final String PREFIX = I18nUtil.getPropertyPrefix(ModelObjectListDialog.class);
private static final String MESSAGE_ID = PREFIX + "selectMessage"; //$NON-NLS-1$
private static final String BUTTON_NULL = UiConstants.Util.getString(PREFIX + "button.nullValue"); //$NON-NLS-1$
private static final String GROUP_SELECTED = UiConstants.Util.getString(PREFIX + "groupSelected"); //$NON-NLS-1$
private static final String LABEL_NAME = UiConstants.Util.getString(PREFIX + "labelName"); //$NON-NLS-1$
private static final String LABEL_PATH = UiConstants.Util.getString(PREFIX + "labelPath"); //$NON-NLS-1$
private static final String LOADING_TABLE_MSG = UiConstants.Util.getString(PREFIX + "loadingTable"); //$NON-NLS-1$
static final Object[] LOADING_TABLE_ARRAY = new Object[] {LOADING_TABLE_MSG};
/** An empty array signifying that the property should be set to <code>null</code>. */
static final Object[] NULL_VALUE = new Object[0];
/** Cached OK button needed for enabling/disabling. */
private Button btnOk;
/** Checkbox allowing value to be set to null. */
private Button btnNullValue;
private CLabel nameLabel;
private CLabel pathLabel;
/** Property indicating that the null value checkbox should be shown. */
private boolean showNullValueAssigner = true;
/** Indicates if the null value checkbox is selected. */
private boolean nullValue = false;
private boolean virtual;
private StructuredViewerTextFilterer filter;
private IFilter contentFilter = PASSING_FILTER;
/**
* Construct an instance of ModelObjectListDialog.
*
* @param parent
*/
public ModelObjectListDialog( Shell parent,
ILabelProvider labelProvider ) {
this(parent, labelProvider, true, false);
}
/**
* Constructs a <code>ModelObjectListDialog</code>.
*
* @param theParent the parent container
* @param theLabelProvider the list label provider
* @param theShowNullValueAssignerFlag the flag indicating if the set to null checkbox should be shown
* @since 4.2
*/
public ModelObjectListDialog( Shell theParent,
ILabelProvider theLabelProvider,
boolean theShowNullValueAssignerFlag,
boolean virtual ) {
super(theParent);
this.virtual = virtual;
ILabelProvider cached = new TextCachingLabelProvider(theLabelProvider);
if (virtual) {
// set an empty provider for now:
setContentProvider(new IStructuredContentProvider() {
@Override
public Object[] getElements( Object inputElement ) {
return NULL_VALUE;
}
@Override
public void dispose() {
}
@Override
public void inputChanged( Viewer viewer,
Object oldInput,
Object newInput ) {
}
});
} else {
// not virtual:
setContentProvider(new ContentProvider(cached));
} // endif
setLabelProvider(cached);
setShowNullValueAssigner(theShowNullValueAssignerFlag);
setShellStyle(getShellStyle() | SWT.RESIZE);
}
public void setFeatureName( String name ) {
setTitle(name);
setMessage(UiConstants.Util.getString(MESSAGE_ID, name));
}
/**
* Shows/hides the set value to <code>null</code> checkbox.
*
* @param theShowFlag the flag indicating if the checkbox should be shown
* @since 4.2
*/
public void setShowNullValueAssigner( boolean theShowFlag ) {
this.showNullValueAssigner = theShowFlag;
}
/**
* Indicates if the set value to <code>null</code> checkbox is being shown.
*
* @return <code>true</code>if being shown; <code>false</code> otherwise.
* @since 4.2
*/
public boolean isShowNullValueAssigner() {
return this.showNullValueAssigner;
}
@Override
protected int getTableStyle() {
int tableStyle = super.getTableStyle();
if (virtual) {
tableStyle |= SWT.VIRTUAL; // add virtual attribute
} // endif
return tableStyle;
}
/**
* Returns an empty array if the set to <code>null</code> checkbox is selected.
*
* @see org.eclipse.ui.dialogs.SelectionDialog#getResult()
* @since 4.2
*/
@Override
public Object[] getResult() {
if (this.nullValue) {
return NULL_VALUE;
}
return super.getResult();
}
@Override
protected Label createMessageArea( Composite composite ) {
Label l = super.createMessageArea(composite);
Control filtCtrl = filter.addControl(composite);
filtCtrl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
return l;
}
/* (non-Javadoc)
* @see org.eclipse.jface.window.Window#createContents(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Control createDialogArea( Composite parent ) {
// get the filter ready to be added (in createMessageArea, above):
filter = new StructuredViewerTextFilterer(StructuredViewerTextFilterer.DEFAULT_PROMPT,
StructuredViewerTextFilterer.DEFAULT_CLEAR);
Composite composite = (Composite)super.createDialogArea(parent);
// only create checkbox if property is set
if (isShowNullValueAssigner()) {
btnNullValue = new Button(composite, SWT.CHECK);
btnNullValue.setText(BUTTON_NULL);
btnNullValue.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent theEvent ) {
handleNullValueSelected();
}
});
}
Group selectedInfoGroup = new Group(composite, SWT.NONE);
selectedInfoGroup.setText(GROUP_SELECTED);
GridLayout gridLayout = new GridLayout(2, false);
gridLayout.verticalSpacing = 1;
gridLayout.marginHeight = 2;
selectedInfoGroup.setLayout(gridLayout);
selectedInfoGroup.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
Label l = new Label(selectedInfoGroup, SWT.NONE);
l.setText(LABEL_NAME);
nameLabel = new CLabel(selectedInfoGroup, SWT.NONE);
nameLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
l = new Label(selectedInfoGroup, SWT.NONE);
l.setText(LABEL_PATH);
pathLabel = new CLabel(selectedInfoGroup, SWT.NONE);
pathLabel.setFont(composite.getFont());
pathLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// initialize path label text
List initialSelections = getInitialElementSelections();
if ((initialSelections != null) && !initialSelections.isEmpty() && (initialSelections.get(0) instanceof EObject)) {
EObject e = (EObject)initialSelections.get(0);
nameLabel.setText(constructName(e));
pathLabel.setText(constructPath(e));
}
final TableViewer tableViewer = getTableViewer();
ILabelProvider labelProvider = (ILabelProvider)tableViewer.getLabelProvider(); // this should be the Cached one
filter.setLabelProvider(labelProvider);
tableViewer.addSelectionChangedListener(this);
if (virtual) {
// set up virtual stuff:
// replace the input object with our own ConcurrentModel:
Object input = tableViewer.getInput();
Object selected;
if (initialSelections != null && !initialSelections.isEmpty()) {
selected = initialSelections.get(0);
} else {
selected = null;
} // endif
ContentProvider content = new ContentProvider(input, selected, labelProvider);
tableViewer.setInput(content);
// replace the (temporary) contentProvider with a deferred provider
// and our simple string sorter:
final DeferredContentProvider dcp = new DeferredContentProvider(content);
tableViewer.setContentProvider(dcp);
// attach filter to this:
filter.attachToVirtualViewer(tableViewer, dcp, true);
// set up selector if needed:
if (selected != null) {
final StructuredSelection ss = new StructuredSelection(selected);
final Timer t = new Timer(true);
TimerTask tt = new TimerTask() {
@Override
public void run() {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
if (tableViewer.getTable().isDisposed() || !tableViewer.getSelection().isEmpty()) {
// success, or the user changed selection
t.cancel();
} else {
// try again:
tableViewer.setSelection(ss, true);
} // endif
}
}); // endanon asyncExec
}
}; // endanon TimerTask
// schedule to run in 1/2 second, and every 1/2 second thereafter until it succeeds:
t.schedule(tt, 500, 500);
} // endif -- selected not null
} else {
// not virtual, do things the standard way:
filter.attachToViewer(tableViewer, true);
tableViewer.setSorter(new ViewerSorter());
} // endif
return composite;
}
/**
* @see org.eclipse.jface.dialogs.Dialog#createButton(org.eclipse.swt.widgets.Composite, int, java.lang.String, boolean)
* @since 4.2
*/
@Override
protected Button createButton( Composite theParent,
int theId,
String theLabel,
boolean theDefaultButton ) {
Button btn = super.createButton(theParent, theId, theLabel, theDefaultButton);
// cache OK button to enable/disable initially based on table selection
if (theId == IDialogConstants.OK_ID) {
this.btnOk = btn;
this.btnOk.setEnabled(!getTableViewer().getSelection().isEmpty());
}
return btn;
}
/**
* Handler for when the set value to <code>null</code> checkbox is selected/deselected.
*
* @since 4.2
*/
void handleNullValueSelected() {
if (isShowNullValueAssigner()) {
boolean selected = this.btnNullValue.getSelection();
getTableViewer().getTable().setEnabled(!selected);
this.btnOk.setEnabled(selected);
this.nullValue = selected;
if (selected) {
this.nameLabel.setText(EMPTY_STRING);
this.pathLabel.setText(EMPTY_STRING);
} else {
selectionChanged(new SelectionChangedEvent(getTableViewer(), getTableViewer().getSelection()));
}
}
}
private String constructPath( EObject e ) {
IPath result = ModelerCore.getModelEditor().getModelRelativePathIncludingModel(e);
return result.toString();
}
private String constructName( EObject e ) {
return ModelerCore.getModelEditor().getName(e);
}
/**
* @return The filter used to determine which objects will be included in the displayed list; Never null.
* @since 4.3
*/
public IFilter getContentFilter() {
return this.contentFilter;
}
/**
* @param filter The filter used to determine which objects will be included in the displayed list; A null value will apply a
* filter that passes all objects.
* @since 4.3
*/
public void setContentFilter( final IFilter filter ) {
this.contentFilter = (filter == null ? PASSING_FILTER : filter);
}
// ==========================================================
// ISelectionChangedListener methods
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
*/
@Override
public void selectionChanged( SelectionChangedEvent event ) {
// set path label text
Object obj = SelectionUtilities.getSelectedObject(event.getSelection());
if (obj instanceof EObject) {
EObject eObj = (EObject)obj;
// something is selected:
nameLabel.setText(constructName(eObj));
pathLabel.setText(constructPath(eObj));
// set OK enabled state
this.btnOk.setEnabled(true);
} else if (obj != null) {
nameLabel.setText(obj.toString());
pathLabel.setText(EMPTY_STRING);
this.btnOk.setEnabled(true);
} else {
this.btnOk.setEnabled(false);
}
}
/**
* This class is a content provider in regular and virtual models.
*/
private class ContentProvider extends AbstractConcurrentModel implements IStructuredContentProvider, Comparator {
// Instance variables:
private Map elementsCache = new HashMap();
final Object input;
private final ILabelProvider labelProvider;
boolean isRunningUpdate;
private final Object selected;
public ContentProvider( ILabelProvider labelProvider ) {
this(null, null, labelProvider);
}
public ContentProvider( Object input,
Object selected,
ILabelProvider labelProvider ) {
this.input = input;
this.selected = selected;
this.labelProvider = labelProvider;
}
@Override
public void inputChanged( Viewer viewer,
Object oldInput,
Object newInput ) {
}
@Override
public void dispose() {
}
@Override
public Object[] getElements( Object inputElement ) {
// bail out if no input
if (inputElement == null) {
return null;
}
// cache items:
Object[] rv = (Object[])elementsCache.get(inputElement);
if (rv == null) {
// need to create:
Object[] inputElements = null;
// get the input as an object array
if (inputElement instanceof Object[]) {
inputElements = (Object[])inputElement;
} else if (inputElement instanceof Collection) {
inputElements = processInputs((Collection)inputElement).toArray();
} else {
return null;
}
List resourcesInModelContainer = null;
// get the set of workspaces resources that are in open projects
try {
resourcesInModelContainer = ModelerCore.getModelContainer().getResources();
} catch (CoreException ce) {
ModelerCore.Util.log(ce);
}
HashSet hsEmfResources = null;
if(resourcesInModelContainer!=null) {
hsEmfResources = new HashSet(resourcesInModelContainer);
} else {
hsEmfResources = new HashSet();
}
ArrayList arylResultElements = new ArrayList(inputElements.length);
// qualify the elements against the list of resources in open projects
for (int i = 0; i < inputElements.length; i++) {
Object inputObject = inputElements[i];
if (inputObject instanceof EObject) {
EObject eoTemp = (EObject)inputObject;
Resource res = eoTemp.eResource();
// handle the 'proxy' case
if (res == null && eoTemp.eIsProxy()) {
URI uri = ((EObjectImpl)eoTemp).eProxyURI();
if (resourceSetContains(uri, hsEmfResources)) {
arylResultElements.add(eoTemp);
}
} else
// add if global resource type
if (res != null && res.getURI() != null && res.getURI().toString() != null
&& WorkspaceResourceFinderUtil.isGlobalResource(res.getURI().toString())) {
arylResultElements.add(eoTemp);
} else
// add if in open project
if (hsEmfResources.contains(res)) {
arylResultElements.add(eoTemp);
}
} else {
arylResultElements.add(inputObject);
}
}
// return the result as an object array
rv = arylResultElements.toArray();
// save in cache:
elementsCache.put(inputElement, rv);
} // endif
return rv;
}
private Collection processInputs( Collection inputs ) {
Collection result = new ArrayList(inputs.size());
final IFilter filter = getContentFilter();
for (Iterator iter = inputs.iterator(); iter.hasNext();) {
final Object obj = iter.next();
if (filter.passes(obj)) {
result.add(obj);
}
}
return result;
}
private boolean resourceSetContains( URI uri,
HashSet hsResources ) {
Iterator it = hsResources.iterator();
while (it.hasNext()) {
Resource res = (Resource)it.next();
if (res.getURI().equals(uri)) {
// System.out.println("[ModelObjectListDialog.resourceSetContains] About to return TRUE for: " + uri.path() ); //$NON-NLS-1$
return true;
}
}
// System.out.println("[ModelObjectListDialog.resourceSetContains] About to return FALSE for: " + uri.path() ); //$NON-NLS-1$ return false;
return false;
}
// implementation of AbstractConcurrentModel methods:
@Override
public void requestUpdate( final IConcurrentModelListener listener ) {
// Note: the running flag may be more trouble than it's worth.
// erratic typing can cause the filtered results to not be correct.
if (!isRunningUpdate) {
isRunningUpdate = true;
listener.setContents(LOADING_TABLE_ARRAY);
Thread runThread = new Thread("ModelObjectListDialog content update") { //$NON-NLS-1$
@Override
public void run() {
Object[] elements = getElements(input);
if (!getTableViewer().getTable().isDisposed()) {
listener.setContents(elements);
} // endif
isRunningUpdate = false;
}
};
runThread.setPriority(Thread.NORM_PRIORITY - 1);
runThread.start();
} // endif
}
// implementation of Comparator methods:
@Override
public int compare( Object o1,
Object o2 ) {
// percolate selected to top:
if (o1 == selected) {
// selected is always smaller than o2:
return -1;
} else if (o2 == selected) {
// selected is always smaller than o1:
return 1;
} // endif
String s1 = labelProvider.getText(o1);
String s2 = labelProvider.getText(o2);
return s1.compareToIgnoreCase(s2);
}
} // endclass ContentProvider
}