/**************************************************************************
* ERA - Eclipse Requirements Analysis
* ==============================================
* Copyright (C) 2009-2013 by Georg Blaschke, Christoph P. Neumann
* and Bernd Haberstumpf (http://era.origo.ethz.ch)
**************************************************************************
* Licensed under the Eclipse Public License - v 1.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.eclipse.org/org/documents/epl-v10.html
* 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 era.foss.objecteditor.specobject;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.databinding.IEMFListProperty;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.databinding.viewers.IViewerObservableValue;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ViewerProperties;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IInputSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.nebula.widgets.compositetable.CompositeTable;
import org.eclipse.swt.nebula.widgets.compositetable.IRowContentProvider;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import era.foss.erf.ERF;
import era.foss.erf.EraToolExtension;
import era.foss.erf.ErfPackage;
import era.foss.erf.SpecObject;
import era.foss.erf.SpecType;
import era.foss.erf.ToolExtension;
import era.foss.erf.contrib.HierachicalSpecObjectProvider;
import era.foss.objecteditor.contrib.IAllowViewerSchemaChange;
import era.foss.ui.contrib.NotifyingListSizeProperty;
/**
* The Class SpecObjectCompositeViewer.
*/
public class SpecObjectCompositeViewer extends Viewer implements IInputSelectionProvider, IAllowViewerSchemaChange {
/** Master observable referring to the currently selected view. */
IViewerObservableValue viewMaster;
/** ERF Model. */
ERF erfModel;
/** ERA specific extensions. */
EraToolExtension toolExtension;
/** top level composite of this viewer. */
Composite topLevelComposite;
/** composite table showing the spec objects. */
CompositeTable compositeTable;
/** Button bar for various buttons (adding elements, selecting views,... */
Composite buttonBarComposite;
/** editing Domain. */
EditingDomain editingDomain;
/** Databinding context for this viewer. */
DataBindingContext dbc;
/** The current selected SpecObjects. */
protected LinkedHashMap<Integer, SpecObject> selectedSpecObjectMap = new LinkedHashMap<Integer, SpecObject>();
/** List of selection changed listeners of this viewer. */
List<ISelectionChangedListener> selectionChangedListeners = new LinkedList<ISelectionChangedListener>();
HierachicalSpecObjectProvider specObjectProvider;
/** The spec type master. */
protected IObservableValue specTypeMaster;
/**
* Create a Viewer for the SpecObjects.
*
* @param parent the parent
* @param editingDomain the editing domain
* @param erfModel the erf model
*/
public SpecObjectCompositeViewer( Composite parent, AdapterFactoryEditingDomain editingDomain, ERF erfModel ) {
this.editingDomain = editingDomain;
this.erfModel = erfModel;
this.dbc = new DataBindingContext();
// find Era specific tool extensions
for( ToolExtension toolExtension : this.erfModel.getToolExtensions() ) {
if( toolExtension.eClass().getClassifierID() == ErfPackage.ERA_TOOL_EXTENSION ) {
this.toolExtension = (EraToolExtension)toolExtension;
}
}
assert (this.toolExtension != null);
// TODO: don't rely on the existence of a specification
specObjectProvider = new HierachicalSpecObjectProvider( this.erfModel.getCoreContent()
.getSpecifications()
.get( 0 ) );
doLayout( parent );
// binding the UI with the model
binding();
}
/**
* layout the viewer gui elements
*
* @param parent
*/
void doLayout( Composite parent ) {
topLevelComposite = new Composite( parent, SWT.NONE );
topLevelComposite.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
topLevelComposite.setLayout( new GridLayout( 1, false ) );
// button bar
createButtonBar();
createCompositeTable();
}
/**
* Create the composite table
*/
private void createCompositeTable() {
// composite table
this.compositeTable = new CompositeTable( topLevelComposite, SWT.NULL | SWT.NO_SCROLL );
this.compositeTable.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true, 0, 0 ) );
SpecObjectViewerRow.setViewMaster( viewMaster );
SpecObjectViewerRow.setEditingDomain( editingDomain );
SpecObjectViewerRow.setErfModel( erfModel );
new SpecObjectViewerRow( compositeTable, SWT.NULL );
compositeTable.setRunTime( true );
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#getControl()
*/
@Override
public Control getControl() {
return topLevelComposite;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#refresh()
*/
@Override
public void refresh() {
compositeTable.refreshAllRows();
}
@Override
public void recreateViewerSchema() {
dbc.dispose();
viewMaster.dispose();
buttonBarComposite.dispose();
createButtonBar();
compositeTable.dispose();
createCompositeTable();
binding();
topLevelComposite.layout();
// buttonBarComposite.layout();
// viewerComposite.update();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#getInput()
*/
@Override
public Object getInput() {
// TODO implement getInput(..) which stems from IInputProvider
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#setInput(java.lang.Object)
*/
@Override
public void setInput( Object input ) {
// TODO implement setInput(..)
}
/**
* A row of the composite table
*/
/**
* Create button bar showing:
* <ul>
* <li>combo box for the view</li>
* <li>button for adding new SpecObjects</li>
* </ul>
*
*
* @param viewerComposite the parent composite
* @return
*/
private void createButtonBar() {
buttonBarComposite = new Composite( topLevelComposite, SWT.NONE );
buttonBarComposite.setLayout( new GridLayout( 4, true ) );
buttonBarComposite.setLayoutData( new GridData( SWT.FILL, SWT.TOP, true, false, 0, 0 ) );
/*
* create combo box showing the availible views
*/
ComboViewer viewComboViewer = new ComboViewer( buttonBarComposite, SWT.READ_ONLY ) {
@Override
protected void doUpdateItem( Widget data, Object element, boolean fullMap ) {
// memorize the selection before updating the item, as the
// update routine removes the selection...
ISelection currentSelection = this.getSelection();
super.doUpdateItem( data, element, fullMap );
// set the selection to the previous value
this.setSelection( currentSelection );
}
};
ObservableListContentProvider contentProvider = new ObservableListContentProvider();
viewComboViewer.setContentProvider( contentProvider );
viewComboViewer.setLabelProvider( new ObservableMapLabelProvider(
EMFProperties.value( ErfPackage.Literals.IDENTIFIABLE__LONG_NAME )
.observeDetail( contentProvider.getKnownElements() ) ) );
IEMFListProperty dataTypeDefinitions = EMFProperties.list( ErfPackage.Literals.ERA_TOOL_EXTENSION__VIEWS );
IObservableList observableList = dataTypeDefinitions.observe( toolExtension );
viewComboViewer.setInput( observableList );
// use first view available
// TODO: use a dedicated default view if available
if( toolExtension.getViews().size() > 0 ) {
viewComboViewer.setSelection( new StructuredSelection( toolExtension.getViews().get( 0 ) ) );
}
viewComboViewer.getControl().setLayoutData( new GridData( SWT.LEFT, SWT.CENTER, true, false, 2, 1 ) );
viewMaster = ViewerProperties.singleSelection().observe( viewComboViewer );
// refresh composite table in case view has been changed
viewMaster.addChangeListener( new IChangeListener() {
@Override
public void handleChange( ChangeEvent event ) {
dbc.dispose();
compositeTable.dispose();
createCompositeTable();
binding();
topLevelComposite.layout();
}
} );
/*
* Create Combo box for selecting the SpecType
*/
final ComboViewer specTypecomboViewer = new ComboViewer( buttonBarComposite, SWT.READ_ONLY ) {
@Override
protected void doUpdateItem( Widget data, Object element, boolean fullMap ) {
// memorize the selection before updating the item, as the
// update routine removes the selection...
ISelection currentSelection = this.getSelection();
super.doUpdateItem( data, element, fullMap );
// set the selection to the previous value
this.setSelection( currentSelection );
}
};
ObservableListContentProvider comboViewercontentProvider = new ObservableListContentProvider();
specTypecomboViewer.getControl().setLayoutData( new GridData( SWT.RIGHT, SWT.CENTER, true, false ) );
// set content provider
specTypecomboViewer.setContentProvider( comboViewercontentProvider );
// set label provider
specTypecomboViewer.setLabelProvider( new ObservableMapLabelProvider(
EMFProperties.value( ErfPackage.Literals.IDENTIFIABLE__LONG_NAME )
.observeDetail( comboViewercontentProvider.getKnownElements() ) ) );
// set input
IEMFListProperty specTypeProperty = EMFProperties.list( ErfPackage.Literals.CONTENT__SPEC_TYPES );
specTypecomboViewer.setInput( specTypeProperty.observe( this.erfModel.getCoreContent() ) );
// TODO: use a dedicated default type if available
if( erfModel.getCoreContent().getSpecTypes().size() > 0 ) {
specTypecomboViewer.setSelection( new StructuredSelection( erfModel.getCoreContent()
.getSpecTypes()
.get( 0 ) ) );
}
specTypeMaster = ViewerProperties.singleSelection().observe( specTypecomboViewer );
/*
* create add button
*/
final Button addButton = new Button( buttonBarComposite, SWT.NONE );
addButton.setImage( PlatformUI.getWorkbench().getSharedImages().getImage( ISharedImages.IMG_OBJ_ADD ) );
addButton.setLayoutData( new GridData( SWT.RIGHT, SWT.CENTER, true, false ) );
addButton.setEnabled( specTypeMaster.getValue() != null );
addButton.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
SpecObjectHandler.createNewSpecObject( editingDomain,
erfModel.getCoreContent(),
(SpecType)SpecObjectCompositeViewer.this.specTypeMaster.getValue(),
erfModel.getCoreContent().getSpecifications().get( 0 ) );
}
} );
specTypeMaster.addValueChangeListener( new IValueChangeListener() {
@Override
public void handleValueChange( ValueChangeEvent event ) {
addButton.setEnabled( event.getObservableValue().getValue() != null );
}
} );
}
/**
* Bind composite table and rows of composite table to data model. Also handle selection events from the composite
* table
*/
private void binding() {
dbc.bindValue( PojoObservables.observeValue( compositeTable, "numRowsInCollection" ),
new NotifyingListSizeProperty().observe( specObjectProvider.getSpecObjectList() ),
new UpdateValueStrategy( UpdateValueStrategy.POLICY_NEVER ),
new UpdateValueStrategy() );
compositeTable.addRowContentProvider( new SpecObjectRowContentProvider() );
}
@Override
public void addSelectionChangedListener( ISelectionChangedListener listener ) {
if( !selectionChangedListeners.contains( listener ) ) {
selectionChangedListeners.add( listener );
}
}
@Override
public void removeSelectionChangedListener( ISelectionChangedListener listener ) {
selectionChangedListeners.remove( listener );
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.Viewer#getSelection()
*/
@Override
public ISelection getSelection() {
if( selectedSpecObjectMap.size() == 0 ) {
return StructuredSelection.EMPTY;
}
return new StructuredSelection( selectedSpecObjectMap.values().toArray() );
}
@SuppressWarnings("unchecked")
@Override
public void setSelection( ISelection selection, boolean reveal ) {
if( selection.isEmpty() ) {
return;
}
// unpack ISelection into SpecObject
assert (selection instanceof StructuredSelection);
// remove all Spec Objects
selectedSpecObjectMap.clear();
Integer selectedSpecOjectOffset = null;
// set selectedSpecObject to given one
for( SpecObject specObject : ((List<SpecObject>)((StructuredSelection)selection).toList()) ) {
int specOjectOffset = specObjectProvider.getSpecObjectList().indexOf( specObject );
if( selectedSpecOjectOffset == null ) {
selectedSpecOjectOffset = specOjectOffset;
}
selectedSpecObjectMap.put( specOjectOffset, specObject );
}
// if specObject is not yet displayed in the composite table show it in the first row
if( selectedSpecOjectOffset < compositeTable.getTopRow()
|| selectedSpecOjectOffset > (compositeTable.getTopRow() + compositeTable.getNumRowsVisible()) ) {
compositeTable.setTopRow( selectedSpecOjectOffset );
updateRowSelectionStatus( true );
} else {
updateRowSelectionStatus( false );
}
}
/**
* Update the selection status of all rows controls
*/
private void updateRowSelectionStatus( boolean setFocus ) {
for( Control control : compositeTable.getRowControls() ) {
SpecObjectViewerRow row = (SpecObjectViewerRow)control;
row.setSelected( selectedSpecObjectMap.containsKey( row.getSpecObjectOffset() ), setFocus );
}
// send event to selectionChangedListener
SelectionChangedEvent selectionChangeEvent = new SelectionChangedEvent(
SpecObjectCompositeViewer.this,
new StructuredSelection( selectedSpecObjectMap.values().toArray() ) );
for( ISelectionChangedListener listener : selectionChangedListeners ) {
listener.selectionChanged( selectionChangeEvent );
}
}
/**
* Content Provider for Row in the Composite table.
*/
private class SpecObjectRowContentProvider implements IRowContentProvider {
/**
* Keeps a reference of each delete listener for each row, so we can remove a listener when compositetable
* associate the row to another SpecObject.
*/
private Map<SpecObjectViewerRow, MouseListener> deleteListenerMap = new HashMap<SpecObjectViewerRow, MouseListener>();
/**
* Keeps a reference of each selection listener for each row, so we can remove a listener when compositetable
* associate the row to another SpecObject.
*/
private Map<SpecObjectViewerRow, SelectionListener> selectionListenerMap = new HashMap<SpecObjectViewerRow, SelectionListener>();
/**
* remove and add selectionlistener bind new SpecObject to Row
*/
public void refresh( CompositeTable sender, final int currentObjectOffset, Control rowControl ) {
final SpecObjectViewerRow currentRow = (SpecObjectViewerRow)rowControl;
final SpecObject currentSpecObject = specObjectProvider.getSpecObjectList().get( currentObjectOffset );
// set offset of the current specObject
currentRow.setSpecObject( currentSpecObject, currentObjectOffset );
// bind the new SpecObject
currentRow.bind( currentSpecObject );
// in case the specObject is selected set the selected status of the row
currentRow.setSelected( selectedSpecObjectMap.containsKey( currentObjectOffset ), false );
// listener for the delete button of the row
MouseListener deleteListener = new MouseAdapter() {
@Override
public void mouseDown( MouseEvent e ) {
SpecObjectHandler.deleteSpecObject( editingDomain, currentSpecObject );
}
};
// listener for selection events of a row
SelectionListener selectionListener = new SelectionAdapter() {
@Override
public void widgetSelected( SelectionEvent e ) {
// SHIFT is not pressed: set selection to the SpecObject associated with the current row
if( (e.stateMask & SWT.SHIFT) == 0
|| SpecObjectCompositeViewer.this.selectedSpecObjectMap.isEmpty() ) {
SpecObjectCompositeViewer.this.selectedSpecObjectMap.clear();
SpecObjectCompositeViewer.this.selectedSpecObjectMap.put( currentObjectOffset,
currentSpecObject );
}
// SHIFT is pressed: add all elements between the selected SpecObject and the SpecObject
// associated with the current row
else {
int selectedSpecObjectOffset = (Integer)selectedSpecObjectMap.keySet().toArray()[0];
int objectOffset = Math.min( currentObjectOffset, selectedSpecObjectOffset );
int objectEndOffset = Math.max( currentObjectOffset, selectedSpecObjectOffset );
while (objectOffset <= objectEndOffset) {
SpecObjectCompositeViewer.this.selectedSpecObjectMap.put( objectOffset,
specObjectProvider.getSpecObjectList()
.get( objectOffset ) );
objectOffset++;
}
}
// Only set the focus in case the event is sent by a composite
boolean setFocus = e.widget instanceof Composite;
// update the selection status of the row controls
SpecObjectCompositeViewer.this.updateRowSelectionStatus( setFocus );
}
};
// add listener to row
currentRow.addSelectionListener( selectionListener );
// keep a reference to the listeners to be able to remove it
// during the next refresh
deleteListenerMap.put( currentRow, deleteListener );
selectionListenerMap.put( currentRow, selectionListener );
}
}
}