/*******************************************************************************
* Copyright (c) 2010 Neil Bartlett.
* 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:
* Neil Bartlett - initial API and implementation
*******************************************************************************/
package bndtools.editor.pkgpatterns;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.bndtools.utils.collections.CollectionUtils;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.SectionPart;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.ResourceTransfer;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Constants;
import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.build.model.clauses.HeaderClause;
import bndtools.Plugin;
import bndtools.editor.common.PackageDropAdapter;
public abstract class PkgPatternsListPart<C extends HeaderClause> extends SectionPart implements PropertyChangeListener {
protected static final String PROP_SELECTION = "selection";
private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);
private final String propertyName;
private final IBaseLabelProvider labelProvider;
protected ArrayList<C> clauses = new ArrayList<C>();
private IManagedForm managedForm;
private TableViewer viewer;
private BndEditModel model;
private List<C> selection;
private final Image imgAnalyse = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "/icons/cog_go.png").createImage();
private final Image imgInsert = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "/icons/table_row_insert.png").createImage();
private final Image imgUp = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "/icons/arrow_up.png").createImage();
private final Image imgDown = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "/icons/arrow_down.png").createImage();
protected final String title;
public PkgPatternsListPart(Composite parent, FormToolkit toolkit, int style, String propertyName, String title, IBaseLabelProvider labelProvider) {
super(parent, toolkit, style);
this.propertyName = propertyName;
this.title = title;
this.labelProvider = labelProvider;
Section section = getSection();
section.setText(title);
createSection(section, toolkit);
}
protected void createSection(Section section, FormToolkit toolkit) {
Composite composite = toolkit.createComposite(section);
section.setClient(composite);
ToolBar toolbar = new ToolBar(section, SWT.FLAT);
section.setTextClient(toolbar);
final ToolItem addItem = new ToolItem(toolbar, SWT.PUSH);
addItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_ADD));
addItem.setToolTipText("Add");
final ToolItem insertItem = new ToolItem(toolbar, SWT.PUSH);
insertItem.setImage(imgInsert);
insertItem.setToolTipText("Insert");
insertItem.setEnabled(false);
final ToolItem removeItem = new ToolItem(toolbar, SWT.PUSH);
removeItem.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE));
removeItem.setDisabledImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE_DISABLED));
removeItem.setToolTipText("Remove");
removeItem.setEnabled(false);
Table table = toolkit.createTable(composite, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER);
viewer = new TableViewer(table);
viewer.setUseHashlookup(false);
viewer.setContentProvider(new ArrayContentProvider());
viewer.setLabelProvider(labelProvider);
toolbar = new ToolBar(composite, SWT.FLAT | SWT.HORIZONTAL | SWT.RIGHT);
final ToolItem btnMoveUp = new ToolItem(toolbar, SWT.PUSH);
btnMoveUp.setText("Up");
btnMoveUp.setImage(imgUp);
btnMoveUp.setEnabled(false);
final ToolItem btnMoveDown = new ToolItem(toolbar, SWT.PUSH);
btnMoveDown.setText("Down");
btnMoveDown.setImage(imgDown);
btnMoveDown.setEnabled(false);
// Listeners
table.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
ISelection selection = viewer.getSelection();
if (!selection.isEmpty())
managedForm.fireSelectionChanged(PkgPatternsListPart.this, selection);
}
});
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
List<C> oldSelection = selection;
IStructuredSelection structSel = (IStructuredSelection) event.getSelection();
@SuppressWarnings("unchecked")
List<C> newSelection = structSel.toList();
selection = newSelection;
propChangeSupport.firePropertyChange(PROP_SELECTION, oldSelection, selection);
managedForm.fireSelectionChanged(PkgPatternsListPart.this, event.getSelection());
boolean enabled = !viewer.getSelection().isEmpty();
insertItem.setEnabled(enabled);
removeItem.setEnabled(enabled);
btnMoveUp.setEnabled(enabled);
btnMoveDown.setEnabled(enabled);
}
});
viewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, new Transfer[] {
LocalSelectionTransfer.getTransfer(), ResourceTransfer.getInstance(), TextTransfer.getInstance()
}, new PackageDropAdapter<C>(viewer) {
@Override
protected C createNewEntry(String packageName) {
return newHeaderClause(packageName);
}
@Override
protected void addRows(int index, Collection<C> rows) {
doAddClauses(rows, index, true);
}
@Override
protected int indexOf(Object object) {
return clauses.indexOf(object);
}
});
table.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.character == SWT.DEL) {
doRemoveSelectedClauses();
} else if (e.character == '+') {
doAddClausesAfterSelection(generateClauses());
}
}
});
addItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doAddClausesAfterSelection(generateClauses());
}
});
insertItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doInsertClausesAtSelection(generateClauses());
}
});
removeItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doRemoveSelectedClauses();
}
});
btnMoveUp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doMoveUp();
}
});
btnMoveDown.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doMoveDown();
}
});
// Layout
GridLayout layout;
layout = new GridLayout(1, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 0;
layout.horizontalSpacing = 0;
composite.setLayout(layout);
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
gd.widthHint = 75;
gd.heightHint = 75;
table.setLayoutData(gd);
toolbar.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
}
protected abstract C newHeaderClause(String text);
protected abstract List<C> loadFromModel(BndEditModel model);
protected abstract void saveToModel(BndEditModel model, List< ? extends C> clauses);
protected List<C> getClauses() {
return clauses;
}
protected Collection< ? extends C> generateClauses() {
Collection<C> result = new ArrayList<C>();
result.add(newHeaderClause(""));
return result;
}
/**
* Add the specified clauses to the view.
*
* @param newClauses
* The new clauses.
* @param index
* The index at which to insert the new clauses OR -1 to append at the end.
*/
protected void doAddClauses(Collection< ? extends C> newClauses, int index, boolean select) {
Object[] newClausesArray = newClauses.toArray(new Object[newClauses.size()]);
if (index == -1 || index == this.clauses.size()) {
clauses.addAll(newClauses);
viewer.add(newClausesArray);
} else {
clauses.addAll(index, newClauses);
viewer.refresh();
}
if (select)
viewer.setSelection(new StructuredSelection(newClausesArray), true);
validate();
markDirty();
}
private void doAddClausesAfterSelection(Collection< ? extends C> newClauses) {
if (newClauses != null && !newClauses.isEmpty()) {
int selectedIndex = -1;
IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
if (!selection.isEmpty()) {
// find the highest selected index
for (Iterator< ? > iter = selection.iterator(); iter.hasNext();) {
int index = this.clauses.indexOf(iter.next());
if (index > selectedIndex)
selectedIndex = index;
}
}
doAddClauses(newClauses, selectedIndex, true);
}
}
private void doInsertClausesAtSelection(Collection< ? extends C> newClauses) {
if (newClauses != null && !newClauses.isEmpty()) {
int selectedIndex;
IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
if (selection.isEmpty())
return;
selectedIndex = this.clauses.indexOf(selection.getFirstElement());
doAddClauses(newClauses, selectedIndex, true);
}
}
protected void doRemoveClauses(List< ? > toRemove) {
clauses.removeAll(toRemove);
viewer.remove(toRemove.toArray());
validate();
markDirty();
}
private void doRemoveSelectedClauses() {
doRemoveClauses(((IStructuredSelection) viewer.getSelection()).toList());
validate();
markDirty();
}
void doMoveUp() {
int[] selectedIndexes = findSelectedIndexes();
if (CollectionUtils.moveUp(clauses, selectedIndexes)) {
viewer.refresh();
validate();
markDirty();
}
}
void doMoveDown() {
int[] selectedIndexes = findSelectedIndexes();
if (CollectionUtils.moveDown(clauses, selectedIndexes)) {
viewer.refresh();
validate();
markDirty();
}
}
int[] findSelectedIndexes() {
Object[] selection = ((IStructuredSelection) viewer.getSelection()).toArray();
int[] selectionIndexes = new int[selection.length];
for (int i = 0; i < selection.length; i++) {
selectionIndexes[i] = clauses.indexOf(selection[i]);
}
return selectionIndexes;
}
@Override
public void initialize(IManagedForm form) {
super.initialize(form);
this.managedForm = form;
this.model = (BndEditModel) form.getInput();
this.model.addPropertyChangeListener(Constants.IMPORT_PACKAGE, this);
}
@Override
public void dispose() {
super.dispose();
this.model.removePropertyChangeListener(Constants.IMPORT_PACKAGE, this);
imgAnalyse.dispose();
imgInsert.dispose();
imgUp.dispose();
imgDown.dispose();
}
@Override
public void refresh() {
super.refresh();
// Deep-copy the model
Collection<C> tmp = loadFromModel(model);
if (tmp != null) {
clauses = new ArrayList<C>(tmp.size());
for (C clause : tmp) {
@SuppressWarnings("unchecked")
C clone = (C) clause.clone();
clauses.add(clone);
}
} else {
clauses = new ArrayList<C>();
}
viewer.setInput(clauses);
validate();
}
public void validate() {
// Do nothing.
}
@Override
public void commit(boolean onSave) {
try {
model.removePropertyChangeListener(propertyName, this);
saveToModel(model, clauses.isEmpty() ? null : clauses);
} finally {
super.commit(onSave);
model.addPropertyChangeListener(propertyName, this);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
IFormPage page = (IFormPage) managedForm.getContainer();
if (page.isActive())
refresh();
else
markStale();
}
public void updateLabels(Collection< ? > elements) {
updateLabels(elements.toArray());
}
public void updateLabels(Object[] elements) {
viewer.update(elements, null);
}
public ISelectionProvider getSelectionProvider() {
return viewer;
}
public List<C> getSelection() {
return selection;
}
public void setSelection(List<C> selection) {
List<C> oldSelection = selection;
this.selection = selection;
propChangeSupport.firePropertyChange(PROP_SELECTION, oldSelection, selection);
}
public void addPropertyChangeListener(PropertyChangeListener l) {
propChangeSupport.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
propChangeSupport.addPropertyChangeListener(l);
}
public void addPropertyChangeListener(String name, PropertyChangeListener l) {
propChangeSupport.addPropertyChangeListener(name, l);
}
public void removePropertyChangeListener(String name, PropertyChangeListener l) {
propChangeSupport.addPropertyChangeListener(name, l);
}
}