/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.preferencepage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.GroovyMethodSuggestion;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.GroovyPropertySuggestion;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.GroovySuggestionDeclaringType;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.IBaseGroovySuggestion;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.IGroovySuggestion;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.InferencingSuggestionsManager;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.InferencingSuggestionsManager.ProjectSuggestions;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.OperationManager;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.ui.IProjectUIControl;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.ui.ISelectionHandler;
import org.codehaus.groovy.eclipse.dsl.inferencing.suggestions.ui.ProjectDropDownControl;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreePathContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
/**
* @author Nieraj Singh
* @created Apr 19, 2011
*/
public class GroovySuggestionsTable {
private static final String DEACTIVATE_REMOVE_EDIT_OR_ADD_A_TYPE_SUGGESTION = "Deactivate, remove, edit, or add a type suggestion:";
private Map<ButtonTypes, Button> selectionButtons;
private List<IProject> projects;
private IProjectUIControl selector;
private SuggestionsViewer viewer;
enum ButtonTypes {
EDIT("Edit..."),
ADD("Add..."),
REMOVE("Remove"),
SELECT_ALL("Select All"),
DESELECT_ALL("Deselect All"),
COLLAPSE_ALL("Collapse All"),
EXPAND_ALL("Expand All");
//
private final String label;
private ButtonTypes(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
enum ColumnTypes implements ITreeViewerColumn {
SUGGESTIONS("Suggestions", 60);
private final String label;
private final int weight;
private ColumnTypes(String label, int weight) {
this.label = label;
this.weight = weight;
}
public String getName() {
return label;
}
public int getWidth() {
return weight;
}
}
public GroovySuggestionsTable(List<IProject> projects) {
this.projects = projects != null ? projects : new ArrayList<IProject>();
}
public Composite createTable(Composite parent) {
Composite subparent = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).applyTo(subparent);
GridDataFactory.fillDefaults().grab(true, true).applyTo(parent);
createProjectArea(subparent);
return createViewerArea(subparent);
}
protected void createProjectArea(Composite parent) {
Composite subparent = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).applyTo(subparent);
GridDataFactory.fillDefaults().grab(false, false).applyTo(parent);
ISelectionHandler handler = new ISelectionHandler() {
public void selectionChanged(IProject project) {
setViewerInput(project);
}
};
selector = ProjectDropDownControl.getProjectSelectionControl(projects, parent.getShell(), subparent, handler);
if (selector != null) {
selector.createControls();
// Check if there is a project that was previously edited. Set that as the selection
IProject previouslyModifiedProject = InferencingSuggestionsManager.getInstance().getlastModifiedProject();
if (previouslyModifiedProject != null) {
selector.setProject(previouslyModifiedProject);
}
}
}
protected String getViewerLabel() {
return DEACTIVATE_REMOVE_EDIT_OR_ADD_A_TYPE_SUGGESTION;
}
public IProject getSelectedProject() {
if (selector == null) {
return null;
}
return selector.getProject();
}
protected Composite createViewerArea(Composite parent) {
String label = getViewerLabel();
if (label != null && label.length() > 0) {
Label viewerLabel = new Label(parent, SWT.READ_ONLY);
viewerLabel.setText(label);
GridDataFactory.fillDefaults().grab(false, false).align(SWT.FILL, SWT.CENTER).applyTo(viewerLabel);
}
Composite subparent = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).applyTo(subparent);
GridDataFactory.fillDefaults().grab(true, true).applyTo(parent);
createTableViewer(subparent);
createOperationButtonArea(subparent);
return subparent;
}
protected void collapseAll() {
viewer.getTreeViewer().collapseAll();
}
protected void expandAll() {
viewer.getTreeViewer().expandAll();
}
protected void uncheckAll() {
setCheckStateAll(false);
}
protected void checkAll() {
setCheckStateAll(true);
}
protected void setCheckStateAll(boolean checkState) {
IProject project = getSelectedProject();
ProjectSuggestions suggestions = InferencingSuggestionsManager.getInstance().getSuggestions(project);
if (suggestions != null) {
Collection<GroovySuggestionDeclaringType> declaringTypes = suggestions.getDeclaringTypes();
for (GroovySuggestionDeclaringType declaringType : declaringTypes) {
// Set active state in the model
setActiveState(declaringType, checkState);
refresh();
// update the viewer check state
setCheckState(declaringType);
}
}
}
protected ITreeViewerColumn[] getColumns() {
return ColumnTypes.values();
}
protected void createTableViewer(Composite parent) {
viewer = new SuggestionsViewer(getColumns(), ColumnTypes.SUGGESTIONS);
viewer.createControls(parent);
final CheckboxTreeViewer treeViewer = viewer.getTreeViewer();
treeViewer.addCheckStateListener(new ICheckStateListener() {
public void checkStateChanged(CheckStateChangedEvent event) {
Object obj = event.getElement();
setActiveState(obj, event.getChecked());
}
});
treeViewer.addTreeListener(new ITreeViewerListener() {
public void treeExpanded(TreeExpansionEvent event) {
setCheckState(event.getElement());
}
public void treeCollapsed(TreeExpansionEvent event) {
// do nothing
}
});
treeViewer.setLabelProvider(new ViewerLabelProvider());
treeViewer.setContentProvider(new ViewerContentProvider());
treeViewer.setComparator(new SuggestionViewerSorter());
setViewerListeners(treeViewer);
setViewerInput(getSelectedProject());
}
protected void setActiveState(Object viewerElement, boolean checkState) {
if (viewerElement instanceof GroovySuggestionDeclaringType) {
GroovySuggestionDeclaringType declaringType = (GroovySuggestionDeclaringType) viewerElement;
List<IGroovySuggestion> suggestions = declaringType.getSuggestions();
for (IGroovySuggestion suggestion : suggestions) {
suggestion.changeActiveState(checkState);
}
} else if (viewerElement instanceof IGroovySuggestion) {
IGroovySuggestion suggestion = (IGroovySuggestion) viewerElement;
suggestion.changeActiveState(checkState);
}
}
/**
* Sets check state in the viewer for all visible elements. Collapsed
* elements will get the state set when expanded.
*/
protected void setCheckState(Object viewerElement) {
// Only set the child state as the parent state is derived from the
// child and automatically set by the container check box viewer which
// updates the parent state.
// For example, if one child of the parent is checked, the parent state
// is automatically set by the viewer
// Parent check state does not require manual setting.
if (viewerElement instanceof GroovySuggestionDeclaringType) {
GroovySuggestionDeclaringType declaringType = (GroovySuggestionDeclaringType) viewerElement;
List<IGroovySuggestion> suggestions = declaringType.getSuggestions();
for (Iterator<IGroovySuggestion> it = suggestions.iterator(); it.hasNext();) {
IGroovySuggestion suggestion = it.next();
boolean isSuggestionActive = suggestion.isActive();
viewer.getTreeViewer().setChecked(suggestion, isSuggestionActive);
}
} else if (viewerElement instanceof IGroovySuggestion) {
IGroovySuggestion suggestion = (IGroovySuggestion) viewerElement;
viewer.getTreeViewer().setChecked(suggestion, suggestion.isActive());
}
}
protected void setViewerListeners(TreeViewer tree) {
tree.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
if (event.getSelection() instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
handleSelectionButtonEnablement(selection.toList());
}
}
});
}
protected void handleSelectionButtonEnablement(List<Object> selectedObjects) {
if (projects == null || projects.isEmpty()) {
selectionButtons.get(ButtonTypes.ADD).setEnabled(false);
selectionButtons.get(ButtonTypes.EDIT).setEnabled(false);
selectionButtons.get(ButtonTypes.REMOVE).setEnabled(false);
} else if (selectedObjects == null || selectedObjects.isEmpty()) {
selectionButtons.get(ButtonTypes.ADD).setEnabled(true);
selectionButtons.get(ButtonTypes.EDIT).setEnabled(false);
selectionButtons.get(ButtonTypes.REMOVE).setEnabled(false);
} else if (selectedObjects.size() == 1) {
Object selectedObj = selectedObjects.get(0);
if (selectedObj instanceof GroovySuggestionDeclaringType) {
selectionButtons.get(ButtonTypes.ADD).setEnabled(true);
selectionButtons.get(ButtonTypes.EDIT).setEnabled(false);
selectionButtons.get(ButtonTypes.REMOVE).setEnabled(true);
} else if (selectedObj instanceof IGroovySuggestion) {
selectionButtons.get(ButtonTypes.ADD).setEnabled(true);
selectionButtons.get(ButtonTypes.EDIT).setEnabled(true);
selectionButtons.get(ButtonTypes.REMOVE).setEnabled(true);
}
} else {
selectionButtons.get(ButtonTypes.ADD).setEnabled(false);
selectionButtons.get(ButtonTypes.EDIT).setEnabled(false);
selectionButtons.get(ButtonTypes.REMOVE).setEnabled(true);
}
}
/**
* Dialogue can only be opened on ONE selection or no selections. It will
* not be opened on multiple selections.
*/
protected void editSuggestion() {
if (getSelections().size() > 1) {
return;
} else {
if (getSelections().size() == 1) {
Object selectedObj = getSelections().get(0);
if (selectedObj instanceof IBaseGroovySuggestion) {
// This edits an existing suggestion. Retain active state.
IBaseGroovySuggestion existingSuggestion = (IBaseGroovySuggestion) selectedObj;
IGroovySuggestion editedSuggestion = new OperationManager()
.editGroovySuggestion(getSelectedProject(), existingSuggestion, getShell());
if (editedSuggestion != null) {
// Refresh first before setting the check state of the
// new
// suggestion
refresh();
// Update the check state of the new element
setCheckState(editedSuggestion);
}
}
}
}
}
protected Shell getShell() {
return viewer != null ? viewer.getTreeViewer().getTree().getShell() : null;
}
protected void addSuggestion() {
Object selectedObj = getSelections().size() == 1 ? getSelections().get(0) : null;
IBaseGroovySuggestion contextSuggestion =
selectedObj instanceof IBaseGroovySuggestion ? (IBaseGroovySuggestion) selectedObj : null;
IGroovySuggestion suggestion =
new OperationManager().addGroovySuggestion(getSelectedProject(), contextSuggestion, getShell());
if (suggestion != null) {
// Refresh first before setting the check state of the
// new
// suggestion
refresh();
// Update the check state of the new element
setCheckState(suggestion);
}
}
protected void handleButtonSelection(ButtonTypes button) {
if (button != null) {
switch (button) {
case ADD:
addSuggestion();
break;
case EDIT:
editSuggestion();
break;
case COLLAPSE_ALL:
collapseAll();
break;
case DESELECT_ALL:
uncheckAll();
break;
case REMOVE:
handleRemove();
break;
case SELECT_ALL:
checkAll();
break;
case EXPAND_ALL:
expandAll();
break;
}
}
}
protected void handleRemove() {
List<Object> selections = getSelections();
List<IBaseGroovySuggestion> suggestionsToRemove = new ArrayList<IBaseGroovySuggestion>(selections.size());
for (Object selection : selections) {
if (selection instanceof IBaseGroovySuggestion) {
suggestionsToRemove.add((IBaseGroovySuggestion) selection);
}
}
new OperationManager().removeGroovySuggestion(getSelectedProject(), suggestionsToRemove);
refresh();
}
/**
* Resets the viewer input with all the suggestions for the given project in
* expanded form. Should only be called when changing projects or displaying
* a list of suggestions for an initial selected project
*/
protected void setViewerInput(IProject project) {
if (isViewerDisposed() || project == null) {
return;
}
ProjectSuggestions suggestions = InferencingSuggestionsManager.getInstance().getSuggestions(project);
if (suggestions == null) {
return;
}
Collection<GroovySuggestionDeclaringType> declaringTypes = suggestions.getDeclaringTypes();
if (declaringTypes == null) {
return;
}
viewer.getTreeViewer().setInput(declaringTypes);
refresh();
// Set check state of each element after refresh
for (GroovySuggestionDeclaringType declaringType : declaringTypes) {
setCheckState(declaringType);
}
}
protected void refresh() {
viewer.getTreeViewer().refresh(true);
expandAll();
}
protected boolean isViewerDisposed() {
if (viewer == null || viewer.getTreeViewer() == null || viewer.getTreeViewer().getTree().isDisposed()) {
return true;
}
return false;
}
protected static class ViewerContentProvider implements ITreePathContentProvider {
public Object[] getElements(Object inputElement) {
if (inputElement instanceof Collection) {
List<Object> suggestedTypes = new ArrayList<Object>();
Collection<?> topLevel = (Collection<?>) inputElement;
for (Object possibleTypeSuggestion : topLevel) {
if (possibleTypeSuggestion instanceof GroovySuggestionDeclaringType) {
suggestedTypes.add(possibleTypeSuggestion);
}
}
return suggestedTypes.toArray();
}
return null;
}
public Object[] getChildren(TreePath path) {
Object lastElement = path.getLastSegment();
if (lastElement instanceof GroovySuggestionDeclaringType) {
GroovySuggestionDeclaringType treeElement = (GroovySuggestionDeclaringType) lastElement;
List<IGroovySuggestion> properties = treeElement.getSuggestions();
if (properties != null) {
return properties.toArray();
}
}
return null;
}
public TreePath[] getParents(Object element) {
return new TreePath[] {};
}
public boolean hasChildren(TreePath path) {
return getChildren(path) != null;
}
public void dispose() {
// nothing for now
}
public void inputChanged(Viewer viewer, Object e1, Object e2) {
// nothing for now
}
}
/**
* Always returns a non-null selection list. May be empty if no selections
* are present.
*/
protected List<Object> getSelections() {
if (viewer.getTreeViewer().getSelection() instanceof IStructuredSelection) {
return ((IStructuredSelection) viewer.getTreeViewer().getSelection()).toList();
}
return Collections.EMPTY_LIST;
}
protected static class ViewerLabelProvider extends ColumnLabelProvider {
public void update(ViewerCell cell) {
Object element = cell.getElement();
int index = cell.getColumnIndex();
cell.setText(getColumnText(element, index));
cell.setImage(getColumnImage(element, index));
cell.setFont(getFont(element));
}
public Image getColumnImage(Object element, int index) {
return null;
}
public Font getFont(Object element) {
return super.getFont(element);
}
public String getColumnText(Object element, int index) {
ColumnTypes[] values = ColumnTypes.values();
if (index < values.length) {
ColumnTypes colType = values[index];
String text = null;
switch (colType) {
case SUGGESTIONS:
text = getDisplayString(element);
break;
}
return text;
}
return null;
}
}
protected static String getDisplayString(Object element) {
String text = null;
if (element instanceof GroovySuggestionDeclaringType) {
return ((GroovySuggestionDeclaringType) element).getName();
} else if (element instanceof IGroovySuggestion) {
ISuggestionLabel suggestionLabel =
new SuggestionLabelFactory().getSuggestionLabel((IGroovySuggestion) element);
if (suggestionLabel != null) {
text = suggestionLabel.getLabel();
}
}
return text;
}
protected void createOperationButtonArea(Composite parent) {
Composite buttons = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().align(GridData.CENTER, GridData.BEGINNING).applyTo(buttons);
GridLayoutFactory.fillDefaults().applyTo(buttons);
ButtonTypes[] types = ButtonTypes.values();
selectionButtons = new HashMap<GroovySuggestionsTable.ButtonTypes, Button>();
for (ButtonTypes type : types) {
Button button = createSelectionButton(buttons, type);
if (button != null) {
selectionButtons.put(type, button);
}
}
handleSelectionButtonEnablement(getSelections());
}
protected Button createSelectionButton(Composite parent, final ButtonTypes type) {
if (type == null) {
return null;
}
Button button = new Button(parent, SWT.PUSH);
button.setText(type.getLabel());
button.setData(type);
Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
int widthHint = 0;
GridDataFactory.fillDefaults().hint(Math.max(widthHint, minSize.x), SWT.DEFAULT).applyTo(button);
button.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
Object item = e.getSource();
if (item instanceof Button) {
Object widgetData = ((Button) item).getData();
if (widgetData instanceof ButtonTypes) {
handleButtonSelection((ButtonTypes) widgetData);
}
}
}
});
return button;
}
public static class SuggestionViewerSorter extends TreeViewerSorter {
protected String getCompareString(TreeColumn column, Object rowItem) {
ColumnTypes type = getColumnType(column);
String text = null;
if (type != null) {
switch (type) {
case SUGGESTIONS:
text = getDisplayString(rowItem);
break;
}
}
return text;
}
public int compare(Viewer viewer, Object e1, Object e2) {
Tree tree = ((TreeViewer) viewer).getTree();
TreeColumn sortColumn = tree.getSortColumn();
ColumnTypes type = getColumnType(sortColumn);
int sortDirection = 1;
if (type != null) {
switch (type) {
case SUGGESTIONS:
if (e1 instanceof GroovyPropertySuggestion) {
if (e2 instanceof GroovyPropertySuggestion) {
sortDirection = super.compare(viewer, e1, e2);
} else {
// Groovy Properties have higher sort order
sortDirection = sortDirection == SWT.UP ? -1 : 1;
}
} else if (e1 instanceof GroovyMethodSuggestion) {
if (e2 instanceof GroovyMethodSuggestion) {
sortDirection = super.compare(viewer, e1, e2);
} else {
// Groovy Methods have lower sort order
sortDirection = sortDirection == SWT.UP ? 1 : -1;
}
} else {
sortDirection = super.compare(viewer, e1, e2);
}
return sortDirection;
}
}
return super.compare(viewer, e1, e2);
}
protected ColumnTypes getColumnType(TreeColumn column) {
String columnName = column.getText();
for (ColumnTypes type : ColumnTypes.values()) {
if (type.getName().equals(columnName)) {
return type;
}
}
return null;
}
}
public abstract static class TreeViewerSorter extends ViewerComparator {
public int compare(Viewer viewer, Object e1, Object e2) {
if (viewer instanceof TreeViewer) {
Tree tree = ((TreeViewer) viewer).getTree();
TreeColumn sortColumn = tree.getSortColumn();
int sortDirection = tree.getSortDirection();
if (sortColumn != null) {
String compareText1 = getCompareString(sortColumn, e1);
String compareText2 = getCompareString(sortColumn, e2);
if (compareText1 != null) {
if (compareText2 != null) {
return sortDirection == SWT.UP ? compareText1.compareToIgnoreCase(compareText2)
: compareText2.compareToIgnoreCase(compareText1);
} else {
return sortDirection == SWT.UP ? -1 : 1;
}
} else if (compareText2 != null) {
return sortDirection == SWT.UP ? 1 : -1;
}
}
}
return super.compare(viewer, e1, e2);
}
abstract protected String getCompareString(TreeColumn column, Object rowItem);
}
}