/*
* 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.search;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.FilteredList;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.search.ModelWorkspaceSearch;
import org.teiid.designer.core.search.runtime.ResourceObjectRecord;
import org.teiid.designer.ui.UiConstants;
/**
* Provides model object selection functionality, including object name matching as user types.
*
* @since 8.0
*/
public class ModelObjectSelectionPanel extends Composite implements IFinderPanel {
private static final String TITLE = UiConstants.Util.getString("ModelObjectSelectionPanel.title"); //$NON-NLS-1$
private static final String TEXT_ENTRY_LABEL_TEXT = UiConstants.Util.getString("ModelObjectSelectionPanel.textEntryLabel.text"); //$NON-NLS-1$
private static final String UPPER_TABLE_LABEL_TEXT = UiConstants.Util.getString("ModelObjectSelectionPanel.upperTableLabel.text"); //$NON-NLS-1$
private static final String LOWER_TABLE_LABEL_TEXT = UiConstants.Util.getString("ModelObjectSelectionPanel.lowerTableLabel.text"); //$NON-NLS-1$
private HashMap hmUniqueNames;
private String sSelectedObject;
private String sSelectedPath;
private String fUpperListLabel;
private String fLowerListLabel;
ILabelProvider fQualifierRenderer;
private ILabelProvider fFilterRenderer;
private Object[] fElements = new Object[0];
private Table fLowerList;
private Object[] fQualifierElements;
protected FilteredList fFilteredList;
private String fFilter;
Text fFilterText;
private ISelectionStatusValidator fValidator;
private String fEmptyListMessage = ""; //$NON-NLS-1$
private String fEmptySelectionMessage = ""; //$NON-NLS-1$
private Object[] result;
private boolean fIgnoreCase = true;
private boolean fAllowDuplicates = false;
private boolean fMatchEmptyString = true;
private Object[] fSelection = new Object[0];
private Label fMessage;
// a collection of the initially-selected elements
private List initialSelections = new ArrayList();
private IFinderHostDialog fhpHostDialog;
private IStatus stCurrentStatus;
private boolean bUserCancelledDuringLoad = false;
private boolean bEnableProgressCancel = true; // default to TRUE
/**
* @param parent
* @since 4.2
*/
public ModelObjectSelectionPanel( Composite parent,
IFinderHostDialog fhpHostDialog ) {
super(parent, SWT.NONE);
this.fhpHostDialog = fhpHostDialog;
fQualifierRenderer = new FindObjectLabelProvider(FindObjectLabelProvider.CONTAINER);
fFilterRenderer = new FindObjectLabelProvider(FindObjectLabelProvider.OBJECT);
init();
}
public ModelObjectSelectionPanel( Composite parent,
IFinderHostDialog fhpHostDialog,
boolean bEnableProgressCancel ) {
super(parent, SWT.NONE);
this.fhpHostDialog = fhpHostDialog;
this.bEnableProgressCancel = bEnableProgressCancel;
fQualifierRenderer = new FindObjectLabelProvider(FindObjectLabelProvider.CONTAINER);
fFilterRenderer = new FindObjectLabelProvider(FindObjectLabelProvider.OBJECT);
init();
}
private IFinderHostDialog getHost() {
return fhpHostDialog;
}
private void init() {
setUpperListLabel(UPPER_TABLE_LABEL_TEXT);
setLowerListLabel(LOWER_TABLE_LABEL_TEXT);
doLoadObjects();
createControl(this);
}
/**
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
private void doLoadObjects() {
final WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
@Override
public void execute( IProgressMonitor theMonitor ) {
loadObjects(theMonitor);
theMonitor.done();
}
};
ProgressMonitorDialog dlg = null;
try {
dlg = new ProgressMonitorDialog(Display.getCurrent().getActiveShell());
dlg.run(true, bEnableProgressCancel, op);
if (dlg.getProgressMonitor().isCanceled()) {
bUserCancelledDuringLoad = true;
} else {
// finish it all at the end
dlg.getProgressMonitor().worked(1);
}
} catch (InterruptedException e) {
bUserCancelledDuringLoad = true;
} catch (InvocationTargetException e) {
UiConstants.Util.log(e.getTargetException());
}
}
public boolean userCancelledDuringLoad() {
return bUserCancelledDuringLoad;
}
void loadObjects( IProgressMonitor monitor ) {
ModelWorkspaceSearch mwssSearch = new ModelWorkspaceSearch(monitor);
Collection colRecordObjects = mwssSearch.getAllModelObjectRecords();
// create a data structure to relate the names to their URI(s) (could be dupes).
hmUniqueNames = new HashMap();
// the collection will be null if user cancelled the workspace search
if (colRecordObjects == null) {
return;
}
Iterator it = colRecordObjects.iterator();
while (it.hasNext()) {
ResourceObjectRecord ror = (ResourceObjectRecord)it.next();
// skip entries that have no name
if (ror.getName().trim().equals("")) { //$NON-NLS-1$
continue;
}
// add named entries
if (hmUniqueNames.containsKey(ror.getName())) {
// if key already exists, add to its ArrayList
ArrayList aryl = (ArrayList)hmUniqueNames.get(ror.getName());
aryl.add(ror);
} else {
// if key does not exist, create new ArrayList and add ror to it
ArrayList aryl2 = new ArrayList();
aryl2.add(ror);
hmUniqueNames.put(ror.getName(), aryl2);
}
}
// load the dialog
setElements(getElementsFromSource());
}
private Object[] getElementsFromSource() {
// The elements are the first entries in each ArrayList stored as a value
// in the HashMap. They are ResourceObjectRecord s.
ArrayList arylElements = new ArrayList(hmUniqueNames.values().size());
Iterator it = hmUniqueNames.values().iterator();
while (it.hasNext()) {
ArrayList arylTemp = (ArrayList)it.next();
arylElements.add(arylTemp.get(0));
}
return arylElements.toArray();
}
public Object[] getFoldedElements( int index ) {
// 1. get the string in the selected upper table row
String sName = null;
Object[] oArySelection = fFilteredList.getSelection();
if (oArySelection.length > 0) {
sName = ((ResourceObjectRecord)oArySelection[0]).getName();
}
// 2. use the string to get the 'dupes' from the hashmap
ArrayList aryl = (ArrayList)hmUniqueNames.get(sName);
Object[] oAry = new Object[aryl.size()];
Iterator it = aryl.iterator();
int ix = 0;
while (it.hasNext()) {
ResourceObjectRecord rorTemp = (ResourceObjectRecord)it.next();
oAry[ix++] = rorTemp;
}
return oAry;
}
/**
* Sets the upper list label. If the label is <code>null</code> (default), no label is created.
*/
public void setUpperListLabel( String label ) {
fUpperListLabel = label;
}
/**
* Sets the lower list label. If the label is <code>null</code> (default), no label is created.
*/
public void setLowerListLabel( String label ) {
fLowerListLabel = label;
}
/**
* Sets the elements to be displayed.
*
* @param elements the elements to be displayed.
*/
public void setElements( Object[] elements ) {
fElements = elements;
}
/*
* @see Dialog#createDialogArea(Composite)
*/
public Control createControl( Composite parent ) {
Composite contents = parent;
createMessageArea(contents);
createFilterText(contents);
createLabel(contents, fUpperListLabel);
createFilteredList(contents);
createLabel(contents, fLowerListLabel);
createLowerList(contents);
setListElements(fElements);
List initialSelections = getInitialElementSelections(); // jhTODO: from SelectionDialog
if (!initialSelections.isEmpty()) {
Object element = initialSelections.get(0);
setSelection(new Object[] {element});
setLowerSelectedElement(element);
}
return contents;
}
/**
* Returns the initial selection in this selection dialog.
*
* @deprecated use getInitialElementSelections() instead
* @return the list of initial selected elements or null
*/
@Deprecated
protected List getInitialSelections() {
if (initialSelections.isEmpty()) {
return null;
}
return getInitialElementSelections();
}
/**
* Returns the list of initial element selections.
*
* @return List
*/
protected List getInitialElementSelections() {
return initialSelections;
}
/**
* Sets the selection referenced by an array of elements. Empty or null array removes selection. To be called within open().
*
* @param selection the indices of the selection.
*/
protected void setSelection( Object[] selection ) {
Assert.isNotNull(fFilteredList);
fFilteredList.setSelection(selection);
}
/**
* Sets the elements of the list (widget). To be called within open().
*
* @param elements the elements of the list.
*/
protected void setListElements( Object[] elements ) {
Assert.isNotNull(fFilteredList);
fFilteredList.setElements(elements);
}
/**
* Creates the message text widget and sets layout data.
*
* @param composite the parent composite of the message area.
*/
protected Label createMessageArea( Composite composite ) {
Label label = new Label(composite, SWT.NONE);
if (TEXT_ENTRY_LABEL_TEXT != null) {
label.setText(TEXT_ENTRY_LABEL_TEXT);
}
label.setFont(composite.getFont());
GridData data = new GridData();
data.grabExcessVerticalSpace = false;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.BEGINNING;
label.setLayoutData(data);
fMessage = label;
return label;
}
/**
* Creates a label if name was not <code>null</code>.
*
* @param parent the parent composite.
* @param name the name of the label.
* @return returns a label if a name was given, <code>null</code> otherwise.
*/
protected Label createLabel( Composite parent,
String name ) {
if (name == null) return null;
Label label = new Label(parent, SWT.NONE);
label.setText(name);
label.setFont(parent.getFont());
return label;
}
protected Text createFilterText( Composite parent ) {
Text text = new Text(parent, SWT.BORDER);
GridData data = new GridData();
data.grabExcessVerticalSpace = false;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.BEGINNING;
text.setLayoutData(data);
text.setFont(parent.getFont());
text.setText((fFilter == null ? "" : fFilter)); //$NON-NLS-1$
Listener listener = new Listener() {
@Override
public void handleEvent( Event e ) {
fFilteredList.setFilter(fFilterText.getText());
}
};
text.addListener(SWT.Modify, listener);
text.addKeyListener(new KeyListener() {
@Override
public void keyPressed( KeyEvent e ) {
if (e.keyCode == SWT.ARROW_DOWN) fFilteredList.setFocus();
}
@Override
public void keyReleased( KeyEvent e ) {
}
});
fFilterText = text;
return text;
}
/**
* Creates a filtered list.
*
* @param parent the parent composite.
* @return returns the filtered list widget.
*/
protected FilteredList createFilteredList( Composite parent ) {
int flags = SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE;
FilteredList list = new FilteredList(parent, flags, fFilterRenderer, fIgnoreCase, fAllowDuplicates, fMatchEmptyString);
GridData data = new GridData();
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
list.setLayoutData(data);
list.setFont(parent.getFont());
list.setFilter((fFilter == null ? "" : fFilter)); //$NON-NLS-1$
list.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected( SelectionEvent e ) {
handleDefaultSelected();
}
@Override
public void widgetSelected( SelectionEvent e ) {
handleWidgetSelected();
}
});
fFilteredList = list;
return list;
}
void handleWidgetSelected() {
Object[] newSelection = fFilteredList.getSelection();
if (newSelection.length != fSelection.length) {
fSelection = newSelection;
handleSelectionChanged();
} else {
for (int i = 0; i != newSelection.length; i++) {
if (!newSelection[i].equals(fSelection[i])) {
fSelection = newSelection;
handleSelectionChanged();
break;
}
}
}
}
/**
* Creates the list widget and sets layout data.
*
* @param parent the parent composite.
* @return returns the list table widget.
*/
protected Table createLowerList( Composite parent ) {
Table list = new Table(parent, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
list.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent( Event evt ) {
handleLowerSelectionChanged();
}
});
list.addListener(SWT.MouseDoubleClick, new Listener() {
@Override
public void handleEvent( Event evt ) {
handleDefaultSelected();
}
});
list.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed( DisposeEvent e ) {
fQualifierRenderer.dispose();
}
});
GridData data = new GridData();
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
list.setLayoutData(data);
list.setFont(parent.getFont());
fLowerList = list;
return list;
}
public static int convertWidthInCharsToPixels( FontMetrics fontMetrics,
int chars ) {
return fontMetrics.getAverageCharWidth() * chars;
}
/**
* @see SelectionStatusDialog#computeResult()
*/
protected void computeResult() {
Object[] results = new Object[] {getLowerSelectedElement()};
setResult(Arrays.asList(results)); // jhTODO: this method is in SelectionDialog
}
/**
* Set the selections made by the user, or <code>null</code> if the selection was canceled.
*
* @param the list of selected elements, or <code>null</code> if Cancel was pressed
*/
protected void setResult( List newResult ) {
if (newResult == null) {
result = null;
} else {
result = new Object[newResult.size()];
newResult.toArray(result);
}
}
/**
* @see AbstractElementListSelectionDialog#handleDefaultSelected()
*/
protected void handleDefaultSelected() {
handleUpperSelectionChanged();
if (validateCurrentSelection() && (getLowerSelectedElement() != null)) {
getHost().okPressed();
}
}
/**
* @see AbstractElementListSelectionDialog#handleSelectionChanged()
*/
protected void handleSelectionChanged() {
validateCurrentSelection();
handleUpperSelectionChanged();
handleLowerSelectionChanged();
}
/**
* Validates the current selection and updates the status line accordingly.
*/
protected boolean validateCurrentSelection() {
Assert.isNotNull(fFilteredList);
IStatus status;
// must first convert the 'record object' into an EObject
EObject eoSelection = getSelectedEObject();
Object[] elements = new Object[] {eoSelection};
// now validate
if (eoSelection == null) {
status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.ERROR, fEmptySelectionMessage, null);
} else if (elements.length > 0) {
if (fValidator != null) {
status = fValidator.validate(elements);
} else {
status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, "", //$NON-NLS-1$
null);
}
} else {
if (fFilteredList.isEmpty()) {
status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.ERROR, fEmptyListMessage, null);
} else {
status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.ERROR, fEmptySelectionMessage, null);
}
}
stCurrentStatus = status;
getHost().updateTheStatus(stCurrentStatus);
return status.isOK();
}
@Override
public Object[] getResult() {
return new Object[] {getSelectedEObject()};
}
/**
* Returns an array of the currently selected elements. To be called within or after open().
*
* @return returns an array of the currently selected elements.
*/
protected Object[] getSelectedElements() {
Assert.isNotNull(fFilteredList);
return fFilteredList.getSelection();
}
void handleUpperSelectionChanged() {
int index = getSelectionIndex();
fLowerList.removeAll();
if (index < 0) return;
fQualifierElements = getFoldedElements(index);
if (fQualifierElements == null) updateLowerListWidget(new Object[] {});
else updateLowerListWidget(fQualifierElements);
updateOkState();
// capture the selection in the upper list
if (getSelectedElements()[0] != null) {
Object[] oArySelection = fFilteredList.getSelection();
if (oArySelection.length > 0) {
sSelectedObject = ((ResourceObjectRecord)oArySelection[0]).getName();
}
}
}
/**
* Returns an index referring the first current selection. To be called within open().
*
* @return returns the indices of the current selection.
*/
protected int getSelectionIndex() {
Assert.isNotNull(fFilteredList);
return fFilteredList.getSelectionIndex();
}
void handleLowerSelectionChanged() {
updateOkState();
// capture the selection in the lower list
if (getLowerSelectedElement() != null) {
sSelectedPath = ((ResourceObjectRecord)getLowerSelectedElement()).getResourcePath();
}
}
public void updateOkState() {
}
/**
* Selects an element in the lower pane.
*/
protected void setLowerSelectedElement( Object element ) {
if (fQualifierElements == null) return;
// find matching index
int i;
for (i = 0; i != fQualifierElements.length; i++)
if (fQualifierElements[i].equals(element)) break;
// set selection
if (i != fQualifierElements.length) fLowerList.setSelection(i);
}
/**
* Returns the selected element from the lower pane.
*/
protected Object getLowerSelectedElement() {
int index = fLowerList.getSelectionIndex();
if (index >= 0) return fQualifierElements[index];
return null;
}
private void updateLowerListWidget( Object[] elements ) {
int length = elements.length;
String[] qualifiers = new String[length];
for (int i = 0; i != length; i++)
qualifiers[i] = fQualifierRenderer.getText(elements[i]);
TwoArrayQuickSorter sorter = new TwoArrayQuickSorter(isCaseIgnored());
sorter.sort(qualifiers, elements);
for (int i = 0; i != length; i++) {
TableItem item = new TableItem(fLowerList, SWT.NONE);
item.setText(qualifiers[i]);
item.setImage(fQualifierRenderer.getImage(elements[i]));
}
if (fLowerList.getItemCount() > 0) fLowerList.setSelection(0);
}
/**
* Returns if sorting, filtering and folding is case sensitive.
*/
public boolean isCaseIgnored() {
return fIgnoreCase;
}
/**
* Handles empty list by disabling widgets.
*/
protected void handleEmptyList() {
fMessage.setEnabled(false);
fFilterText.setEnabled(false);
fFilteredList.setEnabled(false);
fLowerList.setEnabled(false);
updateOkState();
}
// =================================
// interface: IFinderPanel
// =================================
@Override
public void createButtonsForButtonBar( Composite parent ) {
// no action
}
@Override
public void handleOkPressed() {
}
@Override
public void handleCancelPressed() {
}
@Override
public void updateOKStatus() {
/*
* always update with an OK status, because there is no way to pick the 'wrong' thing
* in this panel.
*/
IStatus fCurrStatus = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, "", //$NON-NLS-1$
null);
getHost().updateTheStatus(fCurrStatus);
}
public EObject getSelectedEObject() {
if (sSelectedObject == null) {
return null;
}
// 1. get the string in the selected upper table row
String sName = sSelectedObject;
// 2. use the string to get the 'dupes' from the hashmap
ArrayList aryl = (ArrayList)hmUniqueNames.get(sName);
Iterator it = aryl.iterator();
ResourceObjectRecord rorSelected = null;
// walk the 'folded' entries, looking for a match with the pathname
// the user selected in the 'qualifier' table (lower table)
while (it.hasNext()) {
ResourceObjectRecord rorTemp = (ResourceObjectRecord)it.next();
if (sSelectedPath.equals(rorTemp.getResourcePath())) {
rorSelected = rorTemp;
break;
}
}
EObject eObj = null;
if(rorSelected!=null) {
try {
URI uri = URI.createURI(rorSelected.getObjectURI());
eObj = ModelerCore.getModelContainer().getEObject(uri, true);
} catch (CoreException ce) {
ModelerCore.Util.log(IStatus.ERROR, ce, ce.getMessage());
}
}
return eObj;
}
@Override
public String getTitle() {
return TITLE;
}
/**
* Sets an optional validator to check if the selection is valid. The validator is invoked whenever the selection changes.
*
* @param validator the validator to validate the selection.
*/
@Override
public void setValidator( ISelectionStatusValidator validator ) {
fValidator = validator;
}
}