blob: a48ca12f84a71cd5927238769ea0d96fb592feb5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2016 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* 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:
* Edgar Mueller - Fix for Bug 493074
******************************************************************************/
/*******************************************************************************
*
* Copyright (c) 2000, 2008 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.emf.emfstore.internal.client.ui.views.emfstorebrowser.views;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
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.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.FilteredList;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
/**
* This is a copy of the org.eclipse.ui.dialogs.ElementListSelectionDialog
* with its class hierarchy flattened. This is class is meant as a replacement
* in order to avoid a dependency to org.eclipse.ui due to e4.
*
*/
public abstract class ElementListSelectionDialog extends TrayDialog {
/**
* Serial version UID for RAP.
*/
public static final long serialVersionUID = 1L;
private FilteredList fFilteredList;
private MessageLine fStatusLine;
private IStatus fLastStatus;
private Image fImage;
private ILabelProvider fRenderer;
private boolean fIgnoreCase = true;
private boolean fIsMultipleSelection;
private boolean fMatchEmptyString = true;
private boolean fAllowDuplicates = true;
private Label fMessage;
private Text fFilterText;
private ISelectionStatusValidator fValidator;
private String fFilter;
private String fEmptyListMessage = ""; //$NON-NLS-1$
private String fEmptySelectionMessage = ""; //$NON-NLS-1$
private int fWidth = 60;
private int fHeight = 18;
private boolean fStatusLineAboveButtons;
private Object[] fSelection = new Object[0];
// the final collection of selected elements, or null if this dialog was
// canceled
private Object[] result;
// a collection of the initially-selected elements
private List<Object> initialSelections = new ArrayList<Object>();
// title of dialog
private String title;
// message to show user
private String message = ""; //$NON-NLS-1$
// dialog bounds strategy (since 3.2)
private int dialogBoundsStrategy = Dialog.DIALOG_PERSISTLOCATION | Dialog.DIALOG_PERSISTSIZE;
// dialog settings for storing bounds (since 3.2)
private IDialogSettings dialogBoundsSettings;
/**
* Constructor.
*
* @param shell
* the parent {@link Shell}
*/
protected ElementListSelectionDialog(Shell shell) {
super(shell);
}
/**
* Controls whether status line appears to the left of the buttons (default)
* or above them.
*
* @param aboveButtons if <code>true</code> status line is placed above buttons; if <code>false</code> to the right
*/
public void setStatusLineAboveButtons(boolean aboveButtons) {
fStatusLineAboveButtons = aboveButtons;
}
/*
* (non-Javadoc) Method declared in Window.
*/
@Override
protected void configureShell(Shell shell) {
super.configureShell(shell);
if (title != null) {
shell.setText(title);
if (fImage != null) {
shell.setImage(fImage);
}
}
}
/*
* (non-Javadoc) Method declared on Dialog.
*/
@Override
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent,
IDialogConstants.OK_ID,
"Ok", //$NON-NLS-1$
true);
createButton(parent,
IDialogConstants.CANCEL_ID,
"Cancel", //$NON-NLS-1$
false);
}
/*
* @see Dialog#createButtonBar(Composite)
*/
@Override
protected Control createButtonBar(Composite parent) {
final Font font = parent.getFont();
final Composite composite = new Composite(parent, SWT.NULL);
final GridLayout layout = new GridLayout();
if (!fStatusLineAboveButtons) {
layout.numColumns = 2;
}
layout.marginHeight = 0;
layout.marginLeft = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.marginWidth = 0;
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
composite.setFont(font);
if (!fStatusLineAboveButtons && isHelpAvailable()) {
createHelpControl(composite);
}
fStatusLine = new MessageLine(composite);
fStatusLine.setAlignment(SWT.LEFT);
final GridData statusData = new GridData(GridData.FILL_HORIZONTAL);
fStatusLine.setErrorStatus(null);
fStatusLine.setFont(font);
if (fStatusLineAboveButtons && isHelpAvailable()) {
statusData.horizontalSpan = 2;
createHelpControl(composite);
}
fStatusLine.setLayoutData(statusData);
/*
* Create the rest of the button bar, but tell it not to
* create a help button (we've already created it).
*/
final boolean helpAvailable = isHelpAvailable();
setHelpAvailable(false);
super.createButtonBar(composite);
setHelpAvailable(helpAvailable);
return composite;
}
/**
* Creates the message area for this dialog.
* <p>
* This method is provided to allow subclasses to decide where the message will appear on the screen.
* </p>
*
* @param composite
* the parent composite
* @return the message label
*/
protected Label createMessageArea(Composite composite) {
final Label label = new Label(composite, SWT.NONE);
if (message != null) {
label.setText(message);
}
label.setFont(composite.getFont());
final 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;
}
/**
* Returns the list of initial element selections.
*
* @return List
*/
protected List<Object> getInitialElementSelections() {
return initialSelections;
}
/**
* Returns the message for this dialog.
*
* @return the message for this dialog
*/
protected String getMessage() {
return message;
}
/**
* Returns the ok button.
*
* @return the ok button or <code>null</code> if the button is not created
* yet.
*/
public Button getOkButton() {
return getButton(IDialogConstants.OK_ID);
}
/**
* Returns the list of selections made by the user, or <code>null</code> if the selection was canceled.
*
* @return the array of selected elements, or <code>null</code> if Cancel
* was pressed
*/
public Object[] getResult() {
return result;
}
/**
* Sets the initial selection in this selection dialog to the given
* elements.
*
* @param selectedElements
* the array of elements to select
*/
public void setInitialSelections(Object[] selectedElements) {
initialSelections = new ArrayList<Object>(selectedElements.length);
for (int i = 0; i < selectedElements.length; i++) {
initialSelections.add(selectedElements[i]);
}
}
/**
* Sets the initial selection in this selection dialog to the given
* elements.
*
* @param selectedElements
* the List of elements to select
*/
public void setInitialElementSelections(List<Object> selectedElements) {
initialSelections = selectedElements;
}
/**
* Sets the message for this dialog.
*
* @param message
* the message
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Set the selections made by the user, or <code>null</code> if the
* selection was canceled.
*
* @param newResult
* list of selected elements, or <code>null</code> if Cancel
* was pressed
*/
protected void setResult(List<Object> newResult) {
if (newResult == null) {
result = null;
} else {
result = new Object[newResult.size()];
newResult.toArray(result);
}
}
/**
* Set the selections made by the user, or <code>null</code> if the
* selection was canceled.
* <p>
* The selections may accessed using <code>getResult</code>.
* </p>
*
* @param newResult -
* the new values
* @since 2.0
*/
protected void setSelectionResult(Object[] newResult) {
result = newResult;
}
/**
* Sets the title for this dialog.
*
* @param title
* the title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Set the dialog settings that should be used to save the bounds of this
* dialog. This method is provided so that clients that directly use
* SelectionDialogs without subclassing them may specify how the bounds of
* the dialog are to be saved.
*
* @param settings
* the {@link IDialogSettings} that should be used to store the
* bounds of the dialog
*
* @param strategy
* the integer constant specifying how the bounds are saved.
* Specified using {@link Dialog#DIALOG_PERSISTLOCATION} and {@link Dialog#DIALOG_PERSISTSIZE}.
*
* @since 3.2
*
* @see Dialog#getDialogBoundsStrategy()
* @see Dialog#getDialogBoundsSettings()
*/
public void setDialogBoundsSettings(IDialogSettings settings, int strategy) {
dialogBoundsStrategy = strategy;
dialogBoundsSettings = settings;
}
/**
* Gets the dialog settings that should be used for remembering the bounds
* of the dialog, according to the dialog bounds strategy. Overridden to
* provide the dialog settings that were set using {@link #setDialogBoundsSettings(IDialogSettings, int)}.
*
* @return the dialog settings used to store the dialog's location and/or
* size, or <code>null</code> if the dialog's bounds should not be
* stored.
*
* @since 3.2
*
* @see Dialog#getDialogBoundsStrategy()
* @see #setDialogBoundsSettings(IDialogSettings, int)
*/
@Override
protected IDialogSettings getDialogBoundsSettings() {
return dialogBoundsSettings;
}
/**
* Get the integer constant that describes the strategy for persisting the
* dialog bounds. Overridden to provide the dialog bounds strategy that was
* set using {@link #setDialogBoundsSettings(IDialogSettings, int)}.
*
* @return the constant describing the strategy for persisting the dialog
* bounds.
*
* @since 3.2
* @see Dialog#DIALOG_PERSISTLOCATION
* @see Dialog#DIALOG_PERSISTSIZE
* @see Dialog#getDialogBoundsSettings()
* @see #setDialogBoundsSettings(IDialogSettings, int)
*/
@Override
protected int getDialogBoundsStrategy() {
return dialogBoundsStrategy;
}
/**
* Sets the image for this dialog.
*
* @param image the image.
*/
public void setImage(Image image) {
fImage = image;
}
/**
* Returns the first element from the list of results. Returns <code>null</code> if no element has been selected.
*
* @return the first result element if one exists. Otherwise <code>null</code> is
* returned.
*/
public Object getFirstResult() {
final Object[] result = getResult();
if (result == null || result.length == 0) {
return null;
}
return result[0];
}
/**
* Sets a result element at the given position.
*
* @param position
* the position of the result
* @param element
* the result element
*/
protected void setResult(int position, Object element) {
final Object[] result = getResult();
result[position] = element;
setResult(Arrays.asList(result));
}
/**
* Update the dialog's status line to reflect the given status. It is safe to call
* this method before the dialog has been opened.
*
* @param status
* the current status
*/
protected void updateStatus(IStatus status) {
fLastStatus = status;
if (fStatusLine != null && !fStatusLine.isDisposed()) {
updateButtonsEnableState(status);
fStatusLine.setErrorStatus(status);
}
}
/**
* Update the status of the OK button to reflect the given status. Subclasses
* may override this method to update additional buttons.
*
* @param status
* the current status
*/
protected void updateButtonsEnableState(IStatus status) {
final Button okButton = getOkButton();
if (okButton != null && !okButton.isDisposed()) {
okButton.setEnabled(!status.matches(IStatus.ERROR));
}
}
/*
* @see Dialog#okPressed()
*/
@Override
protected void okPressed() {
computeResult();
super.okPressed();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.dialogs.Dialog#isResizable()
*/
@Override
protected boolean isResizable() {
return true;
}
/**
* Handles default selection (double click).
* By default, the OK button is pressed.
*/
protected void handleDefaultSelected() {
if (validateCurrentSelection()) {
buttonPressed(IDialogConstants.OK_ID);
}
}
/**
* Specifies if sorting, filtering and folding is case sensitive.
*
* @param ignoreCase
* whether casing should be ignored
*/
public void setIgnoreCase(boolean ignoreCase) {
fIgnoreCase = ignoreCase;
}
/**
* Returns if sorting, filtering and folding is case sensitive.
*
* @return boolean
*/
public boolean isCaseIgnored() {
return fIgnoreCase;
}
/**
* Specifies whether everything or nothing should be filtered on
* empty filter string.
*
* @param matchEmptyString boolean
*/
public void setMatchEmptyString(boolean matchEmptyString) {
fMatchEmptyString = matchEmptyString;
}
/**
* Specifies if multiple selection is allowed.
*
* @param isMultipleSelectionAllowed
* whether multiple selection is allowed
*/
public void setMultipleSelection(boolean isMultipleSelectionAllowed) {
fIsMultipleSelection = isMultipleSelectionAllowed;
}
/**
* Specifies whether duplicate entries are displayed or not.
*
* @param allowDuplicates
* whether duplicates are allowed
*/
public void setAllowDuplicates(boolean allowDuplicates) {
fAllowDuplicates = allowDuplicates;
}
/**
* Sets the list size in unit of characters.
*
* @param width the width of the list.
* @param height the height of the list.
*/
public void setSize(int width, int height) {
fWidth = width;
fHeight = height;
}
/**
* Sets the message to be displayed if the list is empty.
*
* @param message the message to be displayed.
*/
public void setEmptyListMessage(String message) {
fEmptyListMessage = message;
}
/**
* Sets the message to be displayed if the selection is empty.
*
* @param message the message to be displayed.
*/
public void setEmptySelectionMessage(String message) {
fEmptySelectionMessage = message;
}
/**
* 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.
*/
public void setValidator(ISelectionStatusValidator validator) {
fValidator = validator;
}
/**
* 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);
}
/**
* Sets the filter pattern.
*
* @param filter the filter pattern.
*/
public void setFilter(String filter) {
if (fFilterText == null) {
fFilter = filter;
} else {
fFilterText.setText(filter);
}
}
/**
* Returns the current filter pattern.
*
* @return returns the current filter pattern or {@code null} if filter was not set.
*/
public String getFilter() {
if (fFilteredList == null) {
return fFilter;
}
return fFilteredList.getFilter();
}
/**
* Returns the indices referring the current selection.
* To be called within open().
*
* @return returns the indices of the current selection.
*/
protected int[] getSelectionIndices() {
Assert.isNotNull(fFilteredList);
return fFilteredList.getSelectionIndices();
}
/**
* 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();
}
/**
* 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);
}
/**
* 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();
}
/**
* Returns all elements which are folded together to one entry in the list.
*
* @param index the index selecting the entry in the list.
* @return returns an array of elements folded together.
*/
public Object[] getFoldedElements(int index) {
Assert.isNotNull(fFilteredList);
return fFilteredList.getFoldedElements(index);
}
/**
* Handles a selection changed event.
* By default, the current selection is validated.
*/
protected void handleSelectionChanged() {
validateCurrentSelection();
}
/**
* Validates the current selection and updates the status line
* accordingly.
*
* @return boolean <code>true</code> if the current selection is
* valid.
*/
protected boolean validateCurrentSelection() {
Assert.isNotNull(fFilteredList);
IStatus status;
final Object[] elements = getSelectedElements();
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);
}
}
updateStatus(status);
return status.isOK();
}
/*
* @see Dialog#cancelPressed
*/
@Override
protected void cancelPressed() {
setResult(null);
super.cancelPressed();
}
/**
* Creates a filtered list.
*
* @param parent the parent composite.
* @return returns the filtered list widget.
*/
protected FilteredList createFilteredList(Composite parent) {
final int flags = SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL
| (fIsMultipleSelection ? SWT.MULTI : SWT.SINGLE);
final FilteredList list = new FilteredList(parent, flags, fRenderer,
fIgnoreCase, fAllowDuplicates, fMatchEmptyString);
final GridData data = new GridData();
data.widthHint = convertWidthInCharsToPixels(fWidth);
data.heightHint = convertHeightInCharsToPixels(fHeight);
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() {
public void widgetDefaultSelected(SelectionEvent e) {
handleDefaultSelected();
}
public void widgetSelected(SelectionEvent e) {
handleWidgetSelected();
}
});
fFilteredList = list;
return list;
}
// 3515
private void handleWidgetSelected() {
final 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;
}
}
}
}
private Text createFilterText(Composite parent) {
final Text text = new Text(parent, SWT.BORDER);
final 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$
final Listener listener = new Listener() {
public void handleEvent(Event e) {
fFilteredList.setFilter(fFilterText.getText());
}
};
text.addListener(SWT.Modify, listener);
// RAP [rh] missing Key events
// text.addKeyListener(new KeyListener() {
// public void keyPressed(KeyEvent e) {
// if (e.keyCode == SWT.ARROW_DOWN) {
// fFilteredList.setFocus();
// }
// }
//
// public void keyReleased(KeyEvent e) {
// }
// });
fFilterText = text;
return text;
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.window.Window#open()
*/
@Override
public int open() {
super.open();
return getReturnCode();
}
private void accessSuperCreate() {
super.create();
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.window.Window#create()
*/
@Override
public void create() {
BusyIndicator.showWhile(null, new Runnable() {
public void run() {
accessSuperCreate();
Assert.isNotNull(fFilteredList);
if (fFilteredList.isEmpty()) {
handleEmptyList();
} else {
validateCurrentSelection();
fFilterText.selectAll();
fFilterText.setFocus();
}
}
});
if (fLastStatus != null) {
updateStatus(fLastStatus);
}
}
/**
* Handles empty list by disabling widgets.
*/
protected void handleEmptyList() {
fMessage.setEnabled(false);
fFilterText.setEnabled(false);
fFilteredList.setEnabled(false);
updateOkState();
}
/**
* Update the enablement of the OK button based on whether or not there
* is a selection.
*
*/
protected void updateOkState() {
final Button okButton = getOkButton();
if (okButton != null) {
okButton.setEnabled(getSelectedElements().length != 0);
}
}
/**
* Gets the optional validator used to check if the selection is valid.
* The validator is invoked whenever the selection changes.
*
* @return the validator to validate the selection, or <code>null</code> if no validator has been set.
*
* @since 1.4
*/
protected ISelectionStatusValidator getValidator() {
return fValidator;
}
private Object[] fElements;
/**
* Creates a list selection dialog.
*
* @param parent the parent widget.
* @param renderer the label renderer.
*/
public ElementListSelectionDialog(Shell parent, ILabelProvider renderer) {
super(parent);
fRenderer = renderer;
}
/**
* Sets the elements of the list.
*
* @param elements the elements of the list.
*/
public void setElements(Object[] elements) {
fElements = elements;
}
/**
* Compute the result and return it.
*/
protected void computeResult() {
setResult(Arrays.asList(getSelectedElements()));
}
/*
* @see Dialog#createDialogArea(Composite)
*/
@Override
protected Control createDialogArea(Composite parent) {
final Composite contents = (Composite) super.createDialogArea(parent);
createMessageArea(contents);
createFilterText(contents);
createFilteredList(contents);
setListElements(fElements);
setSelection(getInitialElementSelections().toArray());
return contents;
}
/**
* Returns the filtered list.
*
* @return the {@link FilteredList}
*/
public FilteredList getFilteredList() {
return fFilteredList;
}
}