/************************************************************************** * 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.typeeditor.common; import java.util.Arrays; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.UpdateValueStrategy; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.emf.common.command.BasicCommandStack; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.databinding.edit.EMFEditProperties; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.command.AddCommand; import org.eclipse.emf.edit.command.DeleteCommand; import org.eclipse.emf.edit.command.MoveCommand; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.jface.databinding.viewers.ViewerProperties; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerEditor; import org.eclipse.jface.viewers.ViewerCell; 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.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import era.foss.erf.ErfPackage; import era.foss.erf.Identifiable; import era.foss.erf.impl.ErfFactoryImpl; import era.foss.typeeditor.Activator; import era.foss.ui.contrib.EraImages; import era.foss.ui.contrib.SelectionProviderHasSelectionProperty; /** * A TableViewer with Add and Delete buttons. * <p> * Integrates layout and a Composite for the button bar. * <p> * Actually, this should be a generic UI widget. But now it is not any more. A description field was added, which is * bound to the basic {@link Identifiable} element from the ERF model. * */ public class AddDeleteTableViewer extends TableViewer { /** * Composite holding the table, the element description ({@link #descriptionText}) and the button bar ( * {@link #buttonBar}) */ private Composite composite; /** The description field for the elements in the table. */ Text descriptionText; /** The table. */ protected Table table; /** The editing domain. */ protected EditingDomain editingDomain; /** The type editor activator. */ protected Activator typeEditorActivator; /** * Sets the editing domain. * * @param editingDomain the new editing domain */ public void setEditingDomain( EditingDomain editingDomain ) { this.editingDomain = editingDomain; } /** Button bar holding the Add and remove buttons New buttons can be added by the users of this table. */ private Composite buttonBar; /** Owner of the elements shown in this table */ protected EObject elementOwner; /** Structural feature of the elements shown in this table */ private EStructuralFeature elementFeature; /** Type of object to be created when the add button is pressed */ protected EClass elementDefaultType; /** The active column. */ private int activeColumn; // /** The data bind context. */ private DataBindingContext dataBindContext; /** The master object to observe the selection of the table viewer */ private IObservableValue master; /** Style parameter for disabling the description textfield */ public static final int NO_DESCRIPTION = 1 << 22; /** * Show the description private boolean showDescriptionText; * * /** Instantiates a new adds the delete table viewer. * * @param parent the parent * @see TableViewer */ public AddDeleteTableViewer( Composite parent ) { this( parent, SWT.NONE ); } /** * Instantiates a new adds the delete table viewer. * * @param table the table * @see TableViewer */ public AddDeleteTableViewer( Table table ) { super( table ); table = this.getTable(); composite = table.getParent(); } /** * Instantiates a new adds the delete table viewer. * * @param parent the parent * @param style the style * @see TableViewer */ public AddDeleteTableViewer( Composite parent, int style ) { super( new Composite( new Composite( parent, SWT.NONE ), SWT.NONE ), style ); table = this.getTable(); this.typeEditorActivator = era.foss.typeeditor.Activator.INSTANCE; dataBindContext = new DataBindingContext(); layoutComposite(); createButtonBar(); setupTable(); if( (style & NO_DESCRIPTION) == 0 ) { createDescriptionField(); } triggerColumnSelectedColumn(); parent.addDisposeListener( new DisposeListener() { @Override public void widgetDisposed( DisposeEvent e ) { AddDeleteTableViewer.this.dispose(); } } ); } /** * Dispose observable to avoid listener leaking. */ protected void dispose() { if( master != null ) { master.dispose(); } if( dataBindContext != null ) { dataBindContext.dispose(); } } /** * Layout the Composites. */ private void layoutComposite() { // get composite for table column layout Composite tableComposite = table.getParent(); // get composite for buttons and table composite = tableComposite.getParent(); // set column layout of table composite tableComposite.setLayout( new TableColumnLayout() ); GridData gridData = new GridData( SWT.FILL, SWT.FILL, true, true ); // always show a mimimum of 5 rows gridData.minimumHeight = table.getItemHeight() * 5 + 5; tableComposite.setLayoutData( gridData ); GridLayout gridLayout = new GridLayout( 1, true ); composite.setLayout( gridLayout ); } /** * Create table. */ private void setupTable() { // set table attributes table.setHeaderVisible( true ); table.setLinesVisible( true ); // add key listener table.addKeyListener( new KeyListener() { public void keyPressed( KeyEvent e ) { if( e.character == SWT.DEL ) { removeElements(); refresh(); } } @Override public void keyReleased( KeyEvent e ) { /* do nothing */ } } ); // prepare the activation strategy for setting a default behavior when editing table cells // (may be overwritten) ColumnViewerEditorActivationStrategy actStrategy = new ColumnViewerEditorActivationStrategy( this ) { protected boolean isEditorActivationEvent( ColumnViewerEditorActivationEvent event ) { boolean retVal = false; // TODO: make click-behavior like: 1) single-click if drop-down box 2) double-click if text-box // ((ViewerCell) event.getSource()).getElement(). ...? if( ((ViewerCell)event.getSource()).getColumnIndex() <= 0 ) retVal = (event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC); if( ((ViewerCell)event.getSource()).getColumnIndex() >= 1 ) retVal = (event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC); return retVal; } }; // define strategy how table cells are traversed and activated with the keyboard TableViewerEditor.create( this, actStrategy, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION ); } /** * Create Text widget for displaying the element description. */ private void createDescriptionField() { // Label for description Label descriptionLabel = new Label( composite, SWT.NONE ); descriptionLabel.setText( typeEditorActivator.getString( "_UI_Description_label" ) + ":" ); descriptionLabel.setLayoutData( new GridData( SWT.LEFT, SWT.BOTTOM, false, false ) ); // Text widget for the general Description attribute of any ERF-Identifiable descriptionText = new Text( composite, SWT.BORDER | SWT.MULTI | SWT.WRAP ); GridData gridData = new GridData( SWT.FILL, SWT.FILL, true, true ); // always show a mimimum of 2 lines of Text gridData.minimumHeight = descriptionText.getLineHeight() * 2 + 10; descriptionText.setLayoutData( gridData ); descriptionText.setEnabled( false ); // bind values master = ViewerProperties.singleSelection().observe( this ); /* * We only need the listener as the F****** data binding can't handle null pointer as value of the master object * at the time the various listeners are created. Maybe this has something to do with the fact that the data * binding stuff automatically creates common converters (e.g. int -> string,...) but on the other hand... EMF * knows everything and especially the type... * * However a null pointer is OK once a data binding has been created where the value of the master object is not * null. * * This is major crap... */ master.addChangeListener( new IChangeListener() { @Override public void handleChange( ChangeEvent event ) { /* * create data binding as soon the master observable points to an object */ if( master.getValue() == null ) { descriptionText.setEnabled( false ); } else { descriptionText.setEnabled( true ); if( dataBindContext == null ) { dataBindContext.bindValue( WidgetProperties.text( SWT.Modify ) .observeDelayed( 400, descriptionText ), EMFEditProperties.value( editingDomain, ErfPackage.Literals.IDENTIFIABLE__DESC ) .observeDetail( master ) ); } } } } ); } /** * Create a button bar holding the Add and Remove Button. */ protected void createButtonBar() { buttonBar = new Composite( composite, SWT.NONE ); buttonBar.setLayoutData( new GridData( SWT.LEFT, SWT.BOTTOM, true, false ) ); buttonBar.setLayout( new RowLayout( SWT.HORIZONTAL ) ); // Create Add Button Button addElementButton = new Button( buttonBar, SWT.PUSH ); addElementButton.setImage( PlatformUI.getWorkbench().getSharedImages().getImage( ISharedImages.IMG_OBJ_ADD ) ); addElementButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event ) { AddDeleteTableViewer.this.addElement(); AddDeleteTableViewer.this.refresh(); } } ); // Create Delete Button Button removeElementsButton = new Button( buttonBar, SWT.PUSH ); removeElementsButton.setImage( PlatformUI.getWorkbench() .getSharedImages() .getImage( ISharedImages.IMG_TOOL_DELETE ) ); removeElementsButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event ) { removeElements(); refresh(); } } ); // Create move up button Button moveUpElementsButton = new Button( buttonBar, SWT.PUSH ); moveUpElementsButton.setImage( EraImages.getImage( EraImages.IMG_MOVE_UP ) ); moveUpElementsButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event ) { AddDeleteTableViewer.this.moveElements( -1 ); refresh(); } } ); // Create move up button Button moveDownElementsButton = new Button( buttonBar, SWT.PUSH ); moveDownElementsButton.setImage( EraImages.getImage( EraImages.IMG_MOVE_DOWN ) ); moveDownElementsButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event ) { AddDeleteTableViewer.this.moveElements( 1 ); refresh(); } } ); // Only enable delete and move buttons only when elements are selected IObservableValue selectionProperty = new SelectionProviderHasSelectionProperty().observe( AddDeleteTableViewer.this ); UpdateValueStrategy defaultStrategy = new UpdateValueStrategy(); UpdateValueStrategy neverStrategy = new UpdateValueStrategy( UpdateValueStrategy.POLICY_NEVER ); dataBindContext.bindValue( WidgetProperties.enabled().observe( removeElementsButton ), selectionProperty, neverStrategy, defaultStrategy ); dataBindContext.bindValue( WidgetProperties.enabled().observe( moveUpElementsButton ), selectionProperty, neverStrategy, defaultStrategy ); dataBindContext.bindValue( WidgetProperties.enabled().observe( moveDownElementsButton ), selectionProperty, neverStrategy, defaultStrategy ); } /** * Set layout data. * * @param layoutData the new layout data * @see org.eclipse.swt.widgets.Control#setLayoutData(Object) */ public void setLayoutData( Object layoutData ) { composite.setLayoutData( layoutData ); } /** * Set the information required for adding a new element to the table with {@link #addElement()}. * * @param elementOwner The parent of the elements show in the table viewer * @param elementFeature name of the structural feature of the element * @param elementDefaultType the default type for new elements created with the 'Add' button */ public void setElementInformation( EObject elementOwner, EStructuralFeature elementFeature, EClass elementDefaultType ) { this.elementOwner = elementOwner; this.elementFeature = elementFeature; this.elementDefaultType = elementDefaultType; } /** * Add a new element to the list using the EMF Command Stack The information for adding the element has to be * specified by calling {@link #setAddCommandParameter(EObject, EReference, EClass)}. */ public void addElement() { EObject addCommandValue = ErfFactoryImpl.eINSTANCE.create( elementDefaultType ); if( addCommandValue instanceof Identifiable ) { ((Identifiable)addCommandValue).setLongName( Ui.getUiName( elementDefaultType ) + " " + this.getTable().getItems().length ); } Command cmd = AddCommand.create( editingDomain, elementOwner, elementFeature, addCommandValue ); BasicCommandStack basicCommandStack = (BasicCommandStack)editingDomain.getCommandStack(); basicCommandStack.execute( cmd ); } /** * Remove selected elements from this table. * * Use a {@link DeleteCommand}, instead of a {@link REmoveCommand} as the first one also deletes references to the * removed objects */ public void removeElements() { if( getSelection().isEmpty() == false ) { Command deleteCommand = DeleteCommand.create( editingDomain, ((IStructuredSelection)getSelection()).toList() ); editingDomain.getCommandStack().execute( deleteCommand ); } } /** * Move selected elements * * @param relIndex relative position how the elements shall be moved */ public void moveElements( int relIndex ) { // sort elements in selection according to position int[] selectedIndices = this.getTable().getSelectionIndices(); Arrays.sort( selectedIndices ); // reverse array in case we move down if( relIndex > 0 ) { int selectedIndicesMaxPos = selectedIndices.length - 1; for( int i = 0; i < selectedIndices.length / 2; i++ ) { int tmp = selectedIndices[i]; selectedIndices[i] = selectedIndices[selectedIndicesMaxPos - i]; selectedIndices[selectedIndicesMaxPos - i] = tmp; } } // don't perform move Up in case the min position is less than 0 // don't perform move Down in case the max position is grater than the total number of elements if( ((selectedIndices[0] + relIndex) < 0) || ((selectedIndices[0] + relIndex) > this.getTable().getItemCount() - 1) ) { // do nothing return; } // do move for( int i = 0; i < selectedIndices.length; i++ ) { int selectedIndex = selectedIndices[i]; moveElement( this.getElementAt( selectedIndex ), selectedIndex + relIndex ); } } /** * Move an element * * @param element element to be moved * @param index new absolute position of the element */ public void moveElement( Object element, int index ) { Command moveCommand = MoveCommand.create( editingDomain, elementOwner, elementFeature, element, index ); editingDomain.getCommandStack().execute( moveCommand ); } /** * Get position of column. * */ private void triggerColumnSelectedColumn() { AddDeleteTableViewer.this.getTable().addMouseListener( new MouseAdapter() { public void mouseDown( MouseEvent e ) { int x = 0; for( int i = 0; i < AddDeleteTableViewer.this.getTable().getColumnCount(); i++ ) { x += AddDeleteTableViewer.this.getTable().getColumn( i ).getWidth(); if( e.x <= x ) { activeColumn = i; break; } } } } ); } /** * Get column where a mouse down event occured. * * @return number of active column */ public int getActiveColumn() { return activeColumn; } }