Package org.eclipse.sapphire.ui.forms.swt

Source Code of org.eclipse.sapphire.ui.forms.swt.TablePropertyEditorPresentation$CustomTableViewer

/******************************************************************************
* Copyright (c) 2014 Oracle and Liferay
* 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:
*    Konstantin Komissarchik - initial implementation and ongoing maintenance
*    Gregory Amerson - [358295] Need access to selection in list property editor
******************************************************************************/

package org.eclipse.sapphire.ui.forms.swt;

import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_ADD;
import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_ASSIST;
import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_DELETE;
import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_JUMP;
import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_MOVE_DOWN;
import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_MOVE_UP;
import static org.eclipse.sapphire.ui.SapphireActionSystem.createFilterByActionId;
import static org.eclipse.sapphire.ui.forms.PropertyEditorPart.RELATED_CONTROLS;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gd;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdfill;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdvalign;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdvfill;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdwhint;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.glayout;
import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.glspacing;
import static org.eclipse.sapphire.ui.forms.swt.SwtUtil.runOnDisplayThread;
import static org.eclipse.sapphire.ui.forms.swt.SwtUtil.suppressDashedTableEntryBorder;
import static org.eclipse.sapphire.ui.util.MiscUtil.findSelectionPostDelete;
import static org.eclipse.sapphire.util.CollectionsUtil.equalsBasedOnEntryIdentity;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArraySet;

import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OwnerDrawLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.sapphire.DisposeEvent;
import org.eclipse.sapphire.Element;
import org.eclipse.sapphire.ElementData;
import org.eclipse.sapphire.ElementList;
import org.eclipse.sapphire.ElementType;
import org.eclipse.sapphire.Event;
import org.eclipse.sapphire.FilteredListener;
import org.eclipse.sapphire.ImageData;
import org.eclipse.sapphire.ImageService;
import org.eclipse.sapphire.ListProperty;
import org.eclipse.sapphire.Listener;
import org.eclipse.sapphire.LocalizableText;
import org.eclipse.sapphire.LoggingService;
import org.eclipse.sapphire.PossibleValues;
import org.eclipse.sapphire.Property;
import org.eclipse.sapphire.PropertyContentEvent;
import org.eclipse.sapphire.PropertyDef;
import org.eclipse.sapphire.PropertyEvent;
import org.eclipse.sapphire.PropertyValidationEvent;
import org.eclipse.sapphire.Sapphire;
import org.eclipse.sapphire.Text;
import org.eclipse.sapphire.Value;
import org.eclipse.sapphire.ValueProperty;
import org.eclipse.sapphire.modeling.CapitalizationType;
import org.eclipse.sapphire.modeling.EditFailedException;
import org.eclipse.sapphire.modeling.ModelPath;
import org.eclipse.sapphire.modeling.Status;
import org.eclipse.sapphire.modeling.Status.Severity;
import org.eclipse.sapphire.modeling.annotations.FixedOrderList;
import org.eclipse.sapphire.modeling.el.FunctionResult;
import org.eclipse.sapphire.modeling.el.Literal;
import org.eclipse.sapphire.modeling.localization.LabelTransformer;
import org.eclipse.sapphire.modeling.localization.LocalizationService;
import org.eclipse.sapphire.modeling.util.MiscUtil;
import org.eclipse.sapphire.services.PossibleTypesService;
import org.eclipse.sapphire.services.ValueImageService;
import org.eclipse.sapphire.services.ValueLabelService;
import org.eclipse.sapphire.ui.ListSelectionService;
import org.eclipse.sapphire.ui.ListSelectionService.ListSelectionChangedEvent;
import org.eclipse.sapphire.ui.Presentation;
import org.eclipse.sapphire.ui.SapphireAction;
import org.eclipse.sapphire.ui.SapphireActionGroup;
import org.eclipse.sapphire.ui.SapphireActionHandler;
import org.eclipse.sapphire.ui.SapphireActionHandler.PostExecuteEvent;
import org.eclipse.sapphire.ui.assist.internal.PropertyEditorAssistDecorator;
import org.eclipse.sapphire.ui.def.ActionHandlerDef;
import org.eclipse.sapphire.ui.forms.FormComponentPart;
import org.eclipse.sapphire.ui.forms.PropertyEditorActionHandler;
import org.eclipse.sapphire.ui.forms.PropertyEditorDef;
import org.eclipse.sapphire.ui.forms.PropertyEditorPart;
import org.eclipse.sapphire.ui.forms.swt.internal.ElementsTransfer;
import org.eclipse.sapphire.ui.forms.swt.internal.HyperlinkTable;
import org.eclipse.sapphire.ui.forms.swt.internal.PopUpListFieldCellEditorPresentation;
import org.eclipse.sapphire.ui.forms.swt.internal.PopUpListFieldStyle;
import org.eclipse.sapphire.util.ListFactory;
import org.eclipse.sapphire.util.MutableReference;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolBar;

/**
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
* @author <a href="mailto:gregory.amerson@liferay.com">Gregory Amerson</a>
*/

public class TablePropertyEditorPresentation extends ListPropertyEditorPresentation
{
    @Text( "<empty>" )
    private static LocalizableText emptyRowIndicator;
   
    static
    {
        LocalizableText.init( TablePropertyEditorPresentation.class );
    }

    private boolean exposeAddAction;
    private boolean exposeDeleteAction;
    private Map<Element,TableRow> rows;
    private Table table;
    private CustomTableViewer tableViewer;
    private SelectionProvider selectionProvider;
    private List<ColumnHandler> columnHandlers;
    private Runnable refreshOperation;
   
    public TablePropertyEditorPresentation( final FormComponentPart part, final SwtPresentation parent, final Composite composite )
    {
        super( part, parent, composite );
       
        this.exposeAddAction = true;
        this.exposeDeleteAction = true;
    }

    @Override
    protected void createContents( final Composite parent )
    {
        createContents( parent, false );
    }
   
    @SuppressWarnings( "unchecked" ) // TreeViewer is parameterized since Eclipse 4.4
   
    protected Control createContents( final Composite parent, final boolean embedded )
    {
        final PropertyEditorPart part = part();
        final Property property = part.property();
        final boolean isReadOnly = part.isReadOnly();
        final boolean showHeader = part.getRenderingHint( PropertyEditorDef.HINT_SHOW_HEADER, true );
       
        final SapphireActionGroup actions = getActions();
        final SapphireActionPresentationManager actionPresentationManager = getActionPresentationManager();
       
        final SapphireToolBarActionPresentation toolBarActionsPresentation = new SapphireToolBarActionPresentation( actionPresentationManager );
        toolBarActionsPresentation.addFilter( createFilterByActionId( ACTION_ASSIST ) );
        toolBarActionsPresentation.addFilter( createFilterByActionId( ACTION_JUMP ) );
       
        final SapphireMenuActionPresentation menuActionsPresentation = new SapphireMenuActionPresentation( actionPresentationManager );
        menuActionsPresentation.addFilter( createFilterByActionId( ACTION_ASSIST ) );
        menuActionsPresentation.addFilter( createFilterByActionId( ACTION_JUMP ) );
       
        addOnDisposeOperation
        (
            new Runnable()
            {
                public void run()
                {
                    actionPresentationManager.dispose();
                }
            }
        );
       
        final Composite mainComposite = createMainComposite
        (
            parent,
            new CreateMainCompositeDelegate( part )
            {
                @Override
                public boolean getShowLabel()
                {
                    return ( embedded ? false : super.getShowLabel() );
                }

                @Override
                public int getLeftMargin()
                {
                    return ( embedded ? 0 : super.getLeftMargin() );
                }

                @Override
                public boolean getSpanBothColumns()
                {
                    return ( embedded ? true : super.getSpanBothColumns() );
                }
            }
        );
       
        final Composite tableComposite;
       
        if( this.decorator == null )
        {
            tableComposite = new Composite( mainComposite, SWT.NULL );
            tableComposite.setLayoutData( gdfill() );
            tableComposite.setLayout( glspacing( glayout( 2, 0, 0 ), 2 ) );
           
            this.decorator = createDecorator( tableComposite );
            this.decorator.control().setLayoutData( gdvalign( gd(), SWT.TOP ) );

            this.decorator.addEditorControl( tableComposite );
        }
        else
        {
            tableComposite = mainComposite;
        }
       
        this.decorator.addEditorControl( mainComposite );
       
        // Setting the whint in the following code is a hacky workaround for the problem
        // tracked by the following JFace bug:
        //
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=215997
        //
       
        final Composite tableParentComposite = new Composite( tableComposite, SWT.NULL );
        tableParentComposite.setLayoutData( gdwhint( gdfill(), 1 ) );
        final TableColumnLayout tableColumnLayout = new TableColumnLayout();
        tableParentComposite.setLayout( tableColumnLayout );
       
        this.tableViewer = new CustomTableViewer( tableParentComposite, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI );
        this.table = this.tableViewer.getTable();
        this.decorator.addEditorControl( this.table );
       
        final List<Control> relatedControls = new ArrayList<Control>();
        this.table.setData( RELATED_CONTROLS, relatedControls );
       
        this.table.addListener
        (
            SWT.MeasureItem,
            new org.eclipse.swt.widgets.Listener()
            {
                public void handleEvent( final org.eclipse.swt.widgets.Event event )
                {
                    // The rows should be 18 pixels at minimum to allow sufficient
                    // room for the cell editors.
                   
                    event.height = Math.max( event.height, 18 );
                }
            }
        );
       
        this.columnHandlers = new ArrayList<ColumnHandler>();
       
        final ColumnViewerEditorActivationStrategy activationStrategy = new ColumnViewerEditorActivationStrategy( this.tableViewer )
        {
            protected boolean isEditorActivationEvent( final ColumnViewerEditorActivationEvent event )
            {
                final int columnIndex = ( (ViewerCell) event.getSource() ).getColumnIndex();
                final ColumnHandler columnHandler = getColumnHandler( columnIndex );
                return columnHandler.isEditorActivationEvent( event );
            }
        };
       
        TableViewerEditor.create( this.tableViewer, null, activationStrategy, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_CYCLE_IN_ROW );

        this.table.setHeaderVisible( showHeader );
       
        this.selectionProvider = new SelectionProvider( this.tableViewer );
       
        this.table.addFocusListener
        (
            new FocusAdapter()
            {
                @Override
                public void focusGained( final FocusEvent event )
                {
                    handleTableFocusGainedEvent();
                }
            }
        );
       
        this.refreshOperation = new Runnable()
        {
            boolean running = false;
           
            public void run()
            {
                if( TablePropertyEditorPresentation.this.table.isDisposed() )
                {
                    return;
                }
               
                if( this.running == true )
                {
                    return;
                }
               
                this.running = true;
               
                try
                {
                    TablePropertyEditorPresentation.this.tableViewer.refresh();

                    if( TablePropertyEditorPresentation.this.table.isDisposed() )
                    {
                        return;
                    }
                   
                    TablePropertyEditorPresentation.this.table.notifyListeners( SWT.Selection, null );
                    tableParentComposite.layout();
                }
                finally
                {
                    this.running = false;
                }
            }
        };
       
        final Listener listener = new FilteredListener<PropertyContentEvent>()
        {
            @Override
            protected void handleTypedEvent( final PropertyContentEvent event )
            {
                TablePropertyEditorPresentation.this.refreshOperation.run();
            }
        };
       
        property.attach( listener );
       
        addOnDisposeOperation
        (
            new Runnable()
            {
                public void run()
                {
                    property.detach( listener );
                }
            }
        );
       
        boolean showImages = true;
       
        final String columnWidthsHint = part.getRenderingHint( PropertyEditorDef.HINT_COLUMN_WIDTHS, "" );
        final StringTokenizer columnWidthsHintTokenizer = new StringTokenizer( columnWidthsHint, "," );
       
        for( final ModelPath childPropertyPath : part.getChildProperties() )
        {
            final PropertyDef childProperty = property.definition().getType().property( childPropertyPath );
            final PropertyEditorDef childPropertyEditorDef = part.definition().getChildPropertyEditor( childPropertyPath );
            final TableViewerColumn tableViewerColumn = new TableViewerColumn( this.tableViewer, SWT.NONE );
           
            if( childPropertyEditorDef == null )
            {
                final String label = childProperty.getLabel( false, CapitalizationType.TITLE_STYLE, false );
                tableViewerColumn.getColumn().setText( label );
            }
            else
            {
                final MutableReference<FunctionResult> labelFunctionResultRef = new MutableReference<FunctionResult>();
               
                final Runnable updateLabelOp = new Runnable()
                {
                    public void run()
                    {
                        String label = (String) labelFunctionResultRef.get().value();
                        label = LabelTransformer.transform( label, CapitalizationType.TITLE_STYLE, false );
                        tableViewerColumn.getColumn().setText( label );
                    }
                };
               
                final FunctionResult labelFunctionResult = part.initExpression
                (
                    childPropertyEditorDef.getLabel().content(),
                    String.class,
                    Literal.create( childProperty.getLabel( false, CapitalizationType.NO_CAPS, true ) ),
                    updateLabelOp
                );
               
                labelFunctionResultRef.set( labelFunctionResult );
               
                updateLabelOp.run();
               
                addOnDisposeOperation
                (
                    new Runnable()
                    {
                        public void run()
                        {
                            labelFunctionResult.dispose();
                        }
                    }
                );
            }
           
            ColumnWeightData columnWeightData = null;
           
            if( columnWidthsHintTokenizer.hasMoreTokens() )
            {
                final String columnWidthHint = columnWidthsHintTokenizer.nextToken();
                final String[] columnWidthHintSplit = columnWidthHint.split( ":" );
               
                if( columnWidthHintSplit.length == 1 || columnWidthHintSplit.length == 2 )
                {
                    try
                    {
                        final int minColumnWidth = Integer.parseInt( columnWidthHintSplit[ 0 ].trim() );
                        final int columnWeight;
                       
                        if( columnWidthHintSplit.length == 2 )
                        {
                            columnWeight = Integer.parseInt( columnWidthHintSplit[ 1 ].trim() );
                        }
                        else
                        {
                            columnWeight = 0;
                        }
                       
                        columnWeightData = new ColumnWeightData( columnWeight, minColumnWidth, true );
                    }
                    catch( NumberFormatException e ) {}
                }
            }
           
            if( columnWeightData == null )
            {
                columnWeightData = new ColumnWeightData( 1, 100, true );
            }
           
            tableColumnLayout.setColumnData( tableViewerColumn.getColumn(), columnWeightData );
           
            final ColumnHandler columnHandler = createColumnHandler( this.columnHandlers, childPropertyPath, showImages, childPropertyEditorDef );
           
            showImages = false; // Only the first column should ever show the image.
           
            tableViewerColumn.setLabelProvider( columnHandler.getLabelProvider() );
            tableViewerColumn.setEditingSupport( columnHandler.getEditingSupport() );
           
            final TableColumn tableColumn = tableViewerColumn.getColumn();
           
            tableColumn.addSelectionListener
            (
                new SelectionAdapter()
                {
                    @Override
                    public void widgetSelected( final SelectionEvent event )
                    {
                        final TableColumn currentSortColumn = TablePropertyEditorPresentation.this.table.getSortColumn();
                       
                        if( currentSortColumn != tableColumn )
                        {
                            TablePropertyEditorPresentation.this.table.setSortColumn( tableColumn );
                            TablePropertyEditorPresentation.this.table.setSortDirection( SWT.DOWN );
                            TablePropertyEditorPresentation.this.tableViewer.setComparator( new TableSorter( columnHandler, SWT.DOWN ) );
                        }
                        else
                        {
                            final int currentSortDirection = TablePropertyEditorPresentation.this.table.getSortDirection();
                           
                            if( currentSortDirection == SWT.DOWN )
                            {
                                TablePropertyEditorPresentation.this.table.setSortDirection( SWT.UP );
                                TablePropertyEditorPresentation.this.tableViewer.setComparator( new TableSorter( columnHandler, SWT.UP ) );
                            }
                            else
                            {
                                TablePropertyEditorPresentation.this.table.setSortColumn( null );
                                TablePropertyEditorPresentation.this.tableViewer.setComparator( null );
                            }
                        }
                       
                        for( SapphireAction action : actions.getActions() )
                        {
                            for( SapphireActionHandler handler : action.getActiveHandlers() )
                            {
                                if( handler instanceof PropertyEditorActionHandler )
                                {
                                    ( (PropertyEditorActionHandler) handler ).refreshEnablementState();
                                }
                            }
                        }
                    }
                }
            );
        }
       
        final IStructuredContentProvider contentProvider = new IStructuredContentProvider()
        {
            public Object[] getElements( final Object inputElement )
            {
                final ElementList<?> list = property();
                final Map<Element,TableRow> rows = new LinkedHashMap<Element,TableRow>();
               
                for( Element element : list )
                {
                    TableRow row = null;
                   
                    if( TablePropertyEditorPresentation.this.rows != null )
                    {
                        row = TablePropertyEditorPresentation.this.rows.remove( element );
                    }
                   
                    if( row == null )
                    {
                        ImageProvider imageProvider = null;
                       
                        final ImageService imageService = element.service( ImageService.class );
                       
                        if( imageService != null )
                        {
                            imageProvider = new ImageProvider()
                            {
                                private Listener imageServiceListener;
                               
                                @Override
                                public ImageData image()
                                {
                                    if( this.imageServiceListener == null )
                                    {
                                        this.imageServiceListener = new Listener()
                                        {
                                            @Override
                                            public void handle( final Event event )
                                            {
                                                update( row() );
                                            }
                                        };
                                       
                                        imageService.attach( this.imageServiceListener );
                                    }
                                   
                                    return imageService.image();
                                }

                                @Override
                                public void dispose()
                                {
                                    if( this.imageServiceListener != null )
                                    {
                                        imageService.detach( this.imageServiceListener );
                                    }
                                }
                            };
                        }
                        else if( getColumnCount() == 1 )
                        {
                            final Value<?> value = (Value<?>) element.property( getColumnHandler( 0 ).property() );
                            final ValueImageService valueImageService = value.service( ValueImageService.class );
                           
                            if( valueImageService != null )
                            {
                                imageProvider = new ImageProvider()
                                {
                                    @Override
                                    public ImageData image()
                                    {
                                        return valueImageService.provide( value.text() );
                                    }
                                };
                            }
                        }
                       
                        row = new TableRow( element, imageProvider );
                    }
                   
                    rows.put( element, row );
                }
               
                if( TablePropertyEditorPresentation.this.rows != null )
                {
                    for( TableRow row : TablePropertyEditorPresentation.this.rows.values() )
                    {
                        row.dispose();
                    }
                }
               
                TablePropertyEditorPresentation.this.rows = rows;

                return rows.values().toArray();
            }

            public void inputChanged( final Viewer viewer,
                                      final Object oldInput,
                                      final Object newInput )
            {
            }

            public void dispose()
            {
                for( TableRow row : TablePropertyEditorPresentation.this.rows.values() )
                {
                    row.dispose();
                }
            }
        };
       
        this.tableViewer.setContentProvider( contentProvider );
        this.tableViewer.setInput( contentProvider );
       
        this.table.addTraverseListener
        (
            new TraverseListener()
            {
                public void keyTraversed( final TraverseEvent event )
                {
                    handleTableTraverseEvent( event );
                }
            }
        );
       
        final ListSelectionService selectionService = part.service( ListSelectionService.class );
       
        this.selectionProvider.addSelectionChangedListener
        (
            new ISelectionChangedListener()
            {
                public void selectionChanged( SelectionChangedEvent event )
                {
                    selectionService.select( getSelectedElements() );
                }
            }
        );
       
        setSelectedElements( selectionService.selection() );
       
        final org.eclipse.sapphire.Listener selectionServiceListener = new FilteredListener<ListSelectionChangedEvent>()
        {
            @Override
            protected void handleTypedEvent( final ListSelectionChangedEvent event )
            {
                setSelectedElements( event.after() );
            }
        };

        selectionService.attach( selectionServiceListener );

        if( ! isReadOnly )
        {
            if( this.exposeAddAction )
            {
                final SapphireAction addAction = actions.getAction( ACTION_ADD );
                final List<SapphireActionHandler> addActionHandlers = new ArrayList<SapphireActionHandler>();
               
                final org.eclipse.sapphire.Listener addActionHandlerListener = new org.eclipse.sapphire.Listener()
                {
                    @Override
                    public void handle( final org.eclipse.sapphire.Event event )
                    {
                        if( event instanceof PostExecuteEvent )
                        {
                            if( TablePropertyEditorPresentation.this.table.isDisposed() )
                            {
                                return;
                            }
                           
                            final Element newListElement = (Element) ( (PostExecuteEvent) event ).getResult();
       
                            if( newListElement != null )
                            {
                                TablePropertyEditorPresentation.this.refreshOperation.run();
                               
                                final TableRow row = findTableRow( newListElement );
                               
                                TablePropertyEditorPresentation.this.tableViewer.setSelection( new StructuredSelection( row ), true );
                               
                                if( TablePropertyEditorPresentation.this.table.isDisposed() )
                                {
                                    return;
                                }
                               
                                TablePropertyEditorPresentation.this.tableViewer.editElement( row, 0 );
                                TablePropertyEditorPresentation.this.table.notifyListeners( SWT.Selection, null );
                            }
                        }
                    }
                };
               
                final PossibleTypesService possibleTypesService = property.service( PossibleTypesService.class );

                final Runnable refreshAddActionHandlersOp = new Runnable()
                {
                    public void run()
                    {
                        addAction.removeHandlers( addActionHandlers );
                       
                        for( SapphireActionHandler addActionHandler : addActionHandlers )
                        {
                            addActionHandler.dispose();
                        }
                       
                        for( ElementType memberType : possibleTypesService.types() )
                        {
                            final SapphireActionHandler addActionHandler = new AddActionHandler( memberType );
                            addActionHandler.init( addAction, null );
                            addActionHandler.attach( addActionHandlerListener );
                            addActionHandlers.add( addActionHandler );
                            addAction.addHandler( addActionHandler );
                        }
                    }
                };
               
                refreshAddActionHandlersOp.run();
               
                final org.eclipse.sapphire.Listener possibleTypesServiceListener = new org.eclipse.sapphire.Listener()
                {
                    @Override
                    public void handle( final org.eclipse.sapphire.Event event )
                    {
                        refreshAddActionHandlersOp.run();
                    }
                };
               
                possibleTypesService.attach( possibleTypesServiceListener );
               
                addOnDisposeOperation
                (
                    new Runnable()
                    {
                        public void run()
                        {
                            addAction.removeHandlers( addActionHandlers );
                           
                            for( SapphireActionHandler addActionHandler : addActionHandlers )
                            {
                                addActionHandler.dispose();
                            }
                           
                            possibleTypesService.detach( possibleTypesServiceListener );
                        }
                    }
                );
            }
           
            if( this.exposeDeleteAction )
            {
                final SapphireAction deleteAction = actions.getAction( ACTION_DELETE );
                final SapphireActionHandler deleteActionHandler = new DeleteActionHandler();
                deleteActionHandler.init( deleteAction, null );
                deleteAction.addHandler( deleteActionHandler );
               
                addOnDisposeOperation
                (
                    new Runnable()
                    {
                        public void run()
                        {
                            deleteAction.removeHandler( deleteActionHandler );
                        }
                    }
                );
            }

            if( ! property.definition().hasAnnotation( FixedOrderList.class ) )
            {
                final SapphireAction moveUpAction = actions.getAction( ACTION_MOVE_UP );
                final SapphireActionHandler moveUpActionHandler = new MoveUpActionHandler();
                moveUpActionHandler.init( moveUpAction, null );
                moveUpAction.addHandler( moveUpActionHandler );
               
                addOnDisposeOperation
                (
                    new Runnable()
                    {
                        public void run()
                        {
                            moveUpAction.removeHandler( moveUpActionHandler );
                        }
                    }
                );
               
                final SapphireAction moveDownAction = actions.getAction( ACTION_MOVE_DOWN );
                final SapphireActionHandler moveDownActionHandler = new MoveDownActionHandler();
                moveDownActionHandler.init( moveDownAction, null );
                moveDownAction.addHandler( moveDownActionHandler );
               
                addOnDisposeOperation
                (
                    new Runnable()
                    {
                        public void run()
                        {
                            moveDownAction.removeHandler( moveDownActionHandler );
                        }
                    }
                );

                final org.eclipse.sapphire.Listener moveActionHandlerListener = new org.eclipse.sapphire.Listener()
                {
                    @Override
                    public void handle( final org.eclipse.sapphire.Event event )
                    {
                        if( event instanceof PostExecuteEvent )
                        {
                            TablePropertyEditorPresentation.this.refreshOperation.run();
                           
                            // This is a workaround for a weird problem on SWT on Windows. If modifier keys are pressed
                            // when the list is re-ordered (as in when issuing move up or move down command from the
                            // keyboard), the focused row can detached from selected row.
                           
                            final Element element = getSelectedElement();
                            final TableItem[] items = TablePropertyEditorPresentation.this.table.getItems();
                           
                            for( int i = 0; i < items.length; i++ )
                            {
                                if( items[ i ].getData() == element )
                                {
                                    TablePropertyEditorPresentation.this.table.setSelection( i );
                                    break;
                                }
                            }
                        }
                    }
                };
               
                moveUpAction.attach( moveActionHandlerListener );
                moveDownAction.attach( moveActionHandlerListener );
               
                final ElementsTransfer transfer = new ElementsTransfer( element().type().getModelElementClass().getClassLoader() );
                final Transfer[] transfers = new Transfer[] { transfer };
               
                final DragSource dragSource = new DragSource( this.table, DND.DROP_COPY | DND.DROP_MOVE );
                dragSource.setTransfer( transfers );

                final List<Element> dragElements = new ArrayList<Element>();
               
                dragSource.addDragListener
                (
                    new DragSourceListener()
                    {
                        public void dragStart( final DragSourceEvent event )
                        {
                            if( TablePropertyEditorPresentation.this.tableViewer.getComparator() == null )
                            {
                                dragElements.addAll( getSelectedElements() );
                                event.doit = true;
                            }
                            else
                            {
                                event.doit = false;
                            }
                        }
                       
                        public void dragSetData( final DragSourceEvent event )
                        {
                            event.data = dragElements;
                        }
                       
                        public void dragFinished( final DragSourceEvent event )
                        {
                            if( event.detail == DND.DROP_MOVE )
                            {
                                // When drop target is the same editor as drag source, the drop handler takes care of removing
                                // elements from their original location. The following block of code accounts for the case when
                                // dropping into another editor.
                               
                                boolean droppedIntoAnotherEditor = false;
                               
                                for( Element dragElement : dragElements )
                                {
                                    if( ! dragElement.disposed() )
                                    {
                                        droppedIntoAnotherEditor = true;
                                        break;
                                    }
                                }
                               
                                if( droppedIntoAnotherEditor )
                                {
                                    try
                                    {
                                        final Element selectionPostDelete = findSelectionPostDelete( property(), dragElements );
                                       
                                        for( Element dragElement : dragElements )
                                        {
                                            final ElementList<?> dragElementContainer = (ElementList<?>) dragElement.parent();
                                            dragElementContainer.remove( dragElement );
                                        }
                                       
                                        setSelectedElement( selectionPostDelete );
                                    }
                                    catch( Exception e )
                                    {
                                        // Log this exception unless the cause is EditFailedException. These exception
                                        // are the result of the user declining a particular action that is necessary
                                        // before the edit can happen (such as making a file writable).
                                       
                                        final EditFailedException editFailedException = EditFailedException.findAsCause( e );
                                       
                                        if( editFailedException == null )
                                        {
                                            Sapphire.service( LoggingService.class ).log( e );
                                        }
                                    }
                                }
                            }
                           
                            dragElements.clear();
                        }
                    }
                );
               
                final DropTarget target = new DropTarget( this.table, DND.DROP_COPY | DND.DROP_MOVE );
                target.setTransfer( transfers );
               
                target.addDropListener
                (
                    new DropTargetAdapter()
                    {
                        public void dragOver( final DropTargetEvent event )
                        {
                            if( event.item != null )
                            {
                                final TableItem dragOverItem = (TableItem) event.item;

                                final Point pt = dragOverItem.getDisplay().map( null, TablePropertyEditorPresentation.this.table, event.x, event.y );
                                final Rectangle bounds = dragOverItem.getBounds();
                               
                                if( pt.y < bounds.y + bounds.height / 2 )
                                {
                                    event.feedback = DND.FEEDBACK_INSERT_BEFORE;
                                }
                                else
                                {
                                    event.feedback = DND.FEEDBACK_INSERT_AFTER;
                                }
                            }
                           
                            event.feedback |= DND.FEEDBACK_SCROLL;
                        }

                        public void drop( final DropTargetEvent event )
                        {
                            if( event.data == null )
                            {
                                event.detail = DND.DROP_NONE;
                                return;
                            }
                           
                            final List<ElementData> droppedElements = (List<ElementData>) event.data;
                            final Set<ElementType> possibleTypesService = property.service( PossibleTypesService.class ).types();
                           
                            for( final ElementData droppedElement : droppedElements )
                            {
                                if( ! possibleTypesService.contains( droppedElement.type() ) )
                                {
                                    event.detail = DND.DROP_NONE;
                                    return;
                                }
                            }
                           
                            final ElementList<?> list = property();
                           
                            int position;
                           
                            if( event.item == null )
                            {
                                position = list.size();
                            }
                            else
                            {
                                final TableItem dropTargetItem = (TableItem) event.item;
                                final TableRow dropTargetRow = (TableRow) dropTargetItem.getData();
                                final Element dropTargetElement = dropTargetRow.element();
                               
                                final Point pt = TablePropertyEditorPresentation.this.table.getDisplay().map( null, TablePropertyEditorPresentation.this.table, event.x, event.y );
                                final Rectangle bounds = dropTargetItem.getBounds();
                               
                                position = list.indexOf( dropTargetElement );
                               
                                if( pt.y >= bounds.y + bounds.height / 2 )
                                {
                                    position++;
                                }
                            }
                           
                            try
                            {
                                if( event.detail == DND.DROP_MOVE )
                                {
                                    for( Element dragElement : dragElements )
                                    {
                                        final ElementList<?> dragElementContainer = (ElementList<?>) dragElement.parent();
                                       
                                        if( dragElementContainer == list && dragElementContainer.indexOf( dragElement ) < position )
                                        {
                                            position--;
                                        }
                                       
                                        dragElementContainer.remove( dragElement );
                                    }
                                }
           
                                final List<Element> newSelection = new ArrayList<Element>();
                               
                                for( final ElementData droppedElement : droppedElements )
                                {
                                    final Element insertedElement = list.insert( droppedElement.type(), position );
                                    insertedElement.copy( droppedElement );
                                   
                                    newSelection.add( insertedElement );
                                   
                                    position++;
                                }
                               
                                if( TablePropertyEditorPresentation.this.table.isDisposed() )
                                {
                                    return;
                                }
                               
                                TablePropertyEditorPresentation.this.tableViewer.refresh();
                                setSelectedElements( newSelection );
                            }
                            catch( Exception e )
                            {
                                // Log this exception unless the cause is EditFailedException. These exception
                                // are the result of the user declining a particular action that is necessary
                                // before the edit can happen (such as making a file writable).
                               
                                final EditFailedException editFailedException = EditFailedException.findAsCause( e );
                               
                                if( editFailedException == null )
                                {
                                    Sapphire.service( LoggingService.class ).log( e );
                                }
                               
                                event.detail = DND.DROP_NONE;
                            }
                        }
                    }
                );
            }
        }
       
        final boolean toolBarNeeded = toolBarActionsPresentation.hasActions();
       
        mainComposite.setLayout( glayout( ( toolBarNeeded ? 2 : 1 ), 0, 0, 0, 0 ) );
       
        if( toolBarNeeded )
        {
            final ToolBar toolbar = new ToolBar( mainComposite, SWT.FLAT | SWT.VERTICAL );
            toolbar.setLayoutData( gdvfill() );
            toolBarActionsPresentation.setToolBar( toolbar );
            toolBarActionsPresentation.render();
            addControl( toolbar );
            this.decorator.addEditorControl( toolbar );
        }
       
        if( menuActionsPresentation.hasActions() )
        {
            final Menu menu = new Menu( this.table );
            this.table.setMenu( menu );
            menuActionsPresentation.setMenu( menu );
            menuActionsPresentation.render();
        }
       
        final HyperlinkTable hyperlinkTable = new HyperlinkTable( this.table, actions );
       
        hyperlinkTable.setController
        (
            new HyperlinkTable.Controller()
            {
                @Override
                public boolean isHyperlinkEnabled( final TableItem item,
                                                   final int column )
                {
                    final SapphireActionHandler jumpHandler = getJumpHandler( item, column );
                   
                    if( jumpHandler != null )
                    {
                        return jumpHandler.isEnabled();
                    }

                    return false;
                }

                @Override
                public void handleHyperlinkEvent( final TableItem item,
                                                  final int column )
                {
                    final SapphireActionHandler jumpHandler = getJumpHandler( item, column );
                   
                    if( jumpHandler != null )
                    {
                        jumpHandler.execute( TablePropertyEditorPresentation.this );
                    }
                }
               
                private SapphireActionHandler getJumpHandler( final TableItem item,
                                                              final int column )
                {
                    final Element element = ( (TableRow) item.getData() ).element();
                    final ModelPath property = part.getChildProperties().get( column );
                    final PropertyEditorPart propertyEditor = part.getChildPropertyEditor( element, property );
                    final SapphireActionGroup actions = propertyEditor.getActions();
                    return actions.getAction( ACTION_JUMP ).getFirstActiveHandler();
                }
            }
        );
       
        suppressDashedTableEntryBorder( this.table );
       
        addControl( this.table );
       
        addOnDisposeOperation
        (
            new Runnable()
            {
                public void run()
                {
                    selectionService.detach( selectionServiceListener );
                }
            }
        );
       
        return this.table;
    }
   
    @Override
    protected boolean canScaleVertically()
    {
        return true;
    }
   
    public final PropertyEditorAssistDecorator getDecorator()
    {
        return this.decorator;
    }
   
    public final void setDecorator( final PropertyEditorAssistDecorator decorator )
    {
        this.decorator = decorator;
    }
   
    public final boolean isAddActionDesired()
    {
        return this.exposeAddAction;
    }
   
    public final void setAddActionDesired( final boolean exposeAddAction )
    {
        this.exposeAddAction = exposeAddAction;
    }
   
    public final boolean isDeleteActionDesired()
    {
        return this.exposeDeleteAction;
    }
   
    public final void setDeleteActionDesired( final boolean exposeDeleteAction )
    {
        this.exposeDeleteAction = exposeDeleteAction;
    }
   
    public final Element getSelectedElement()
    {
        final IStructuredSelection sel = (IStructuredSelection) this.selectionProvider.getSelection();
        return (Element) sel.getFirstElement();
    }
   
    public final List<Element> getSelectedElements()
    {
        final IStructuredSelection sel = (IStructuredSelection) this.selectionProvider.getSelection();
        final ListFactory<Element> elements = ListFactory.start();
       
        for( Iterator<?> itr = sel.iterator(); itr.hasNext(); )
        {
            elements.add( (Element) itr.next() );
        }
       
        return elements.result();
    }
   
    public final void setSelectedElement( final Element element )
    {
        setSelectedElements( element == null ? Collections.<Element>emptyList() : Collections.singletonList( element ) );
    }
   
    public final void setSelectedElements( final List<Element> elements )
    {
        if( ! equalsBasedOnEntryIdentity( getSelectedElements(), elements ) )
        {
            final ListFactory<TableRow> rows = ListFactory.start();
           
            for( Element element : elements )
            {
                final TableRow row = findTableRow( element );
               
                if( row != null )
                {
                    rows.add( row );
                }
            }
           
            this.tableViewer.setSelection( new StructuredSelection( rows.result() ) );
        }
    }
   
    private final List<TableRow> getSelectedRows()
    {
        final List<TableRow> rows = new ArrayList<TableRow>();
       
        for( Iterator<?> itr = this.selectionProvider.getSelectedRows().iterator(); itr.hasNext(); )
        {
            rows.add( (TableRow) itr.next() );
        }
       
        return rows;
    }
   
    public final void setFocusOnTable()
    {
        this.table.setFocus();
    }
   
    @Override
    public PropertyEditorPresentation2 createChildPropertyEditorPresentation( final PropertyEditorPart part )
    {
        final Table table = this.table;
        final Property property = part.property();
        final Element element = findTableRowElement( property.element() );
       
        ColumnHandler handler = null;
       
        for( final ColumnHandler h : this.columnHandlers )
        {
            if( element.property( h.property() ) == property )
            {
                handler = h;
                break;
            }
        }
       
        if( handler == null )
        {
            throw new IllegalStateException();
        }
       
        final int column = this.columnHandlers.indexOf( handler );
       
        return new PropertyEditorPresentation2( part, this, table )
        {
            @Override
            public Rectangle bounds()
            {
                Rectangle bounds = null;
               
                for( int i = 0, n = table.getItemCount(); i < n && bounds == null; i++ )
                {
                    final TableItem item = table.getItem( i );
                   
                    if( ( (TableRow) item.getData() ).element() == element )
                    {
                        bounds = item.getBounds( column );
                    }
                }
               
                if( bounds == null )
                {
                    throw new IllegalStateException();
                }
                else
                {
                    final Point position = table.toDisplay( bounds.x, bounds.y );
                    bounds = new Rectangle( position.x, position.y, bounds.width, bounds.height );
                }
               
                return bounds;
            }

            @Override
            public void render()
            {
            }
        };
    }
   
    @Override
    protected void handleChildPropertyEvent( final PropertyContentEvent event )
    {
        super.handleChildPropertyEvent( event );
       
        if( ! this.table.isDisposed() )
        {
            update( ( (PropertyEvent) event ).property().element() );
        }
    }
   
    protected void handleTableFocusGainedEvent()
    {
        if( this.table.isDisposed() )
        {
            return;
        }
       
        if( this.tableViewer.getSelection().isEmpty() && this.table.getItemCount() > 0 )
        {
            final TableItem firstTableItem = this.table.getItem( 0 );
           
            if( firstTableItem.isDisposed() )
            {
                return;
            }
           
            final Object firstItem = firstTableItem.getData();
            this.tableViewer.setSelection( new StructuredSelection( firstItem ) );
        }
    }
   
    private void handleTableTraverseEvent( final TraverseEvent event )
    {
        if( event.detail == SWT.TRAVERSE_RETURN )
        {
            event.doit = false;

            final IStructuredSelection selection = (IStructuredSelection) this.tableViewer.getSelection();
           
            if( selection.size() == 1 )
            {
                final TableRow row = (TableRow) selection.getFirstElement();
                int firstEditableColumn = -1;
               
                for( int i = 0, n = getColumnCount(); i < n; i++ )
                {
                    final ColumnHandler handler = getColumnHandler( i );
                   
                    if( handler.getEditingSupport().canEdit( row ) )
                    {
                        firstEditableColumn = i;
                        break;
                    }
                }
               
                if( firstEditableColumn != -1 )
                {
                    this.tableViewer.editElement( row, firstEditableColumn );
                }
            }
        }
    }

    @Override
    protected void handleFocusReceivedEvent()
    {
        this.table.setFocus();
    }
   
    private ColumnHandler createColumnHandler( final List<ColumnHandler> allColumnHandlers,
                                               final ModelPath childPropertyPath,
                                               final boolean showImages,
                                               final PropertyEditorDef childPropertyEditorDef )
    {
        final PropertyDef childProperty = property().definition().getType().property( childPropertyPath );
        final ColumnHandler columnHandler;
       
        if( childProperty.isOfType( Boolean.class ) )
        {
            columnHandler = new BooleanPropertyColumnHandler( this.tableViewer, this.selectionProvider, part(),
                                                              allColumnHandlers, childPropertyPath, showImages );
        }
        else
        {
            PopUpListFieldStyle popUpListFieldPresentationStyle = null;
           
            if( childProperty.isOfType( Enum.class ) )
            {
                popUpListFieldPresentationStyle = PopUpListFieldStyle.STRICT;
            }
            else if( childPropertyEditorDef != null )
            {
                final String style = childPropertyEditorDef.getStyle().text();
               
                if( style != null )
                {
                    if( style.startsWith( "Sapphire.PropertyEditor.PopUpListField" ) )
                    {
                        if( style.equals( "Sapphire.PropertyEditor.PopUpListField" ) )
                        {
                            final PossibleValues possibleValuesAnnotation = childProperty.getAnnotation( PossibleValues.class );
                           
                            if( possibleValuesAnnotation != null )
                            {
                                popUpListFieldPresentationStyle
                                    = ( possibleValuesAnnotation.invalidValueSeverity() == Severity.ERROR
                                        ? PopUpListFieldStyle.STRICT : PopUpListFieldStyle.EDITABLE );
                            }
                            else
                            {
                                popUpListFieldPresentationStyle = PopUpListFieldStyle.EDITABLE;
                            }
                        }
                        else if( style.equals( "Sapphire.PropertyEditor.PopUpListField.Editable" ) )
                        {
                            popUpListFieldPresentationStyle = PopUpListFieldStyle.EDITABLE;
                        }
                        else if( style.equals( "Sapphire.PropertyEditor.PopUpListField.Strict" ) )
                        {
                            popUpListFieldPresentationStyle = PopUpListFieldStyle.STRICT;
                        }
                    }
                }
            }
           
            if( popUpListFieldPresentationStyle != null )
            {
                columnHandler = new PopUpListFieldColumnPresentation( this.tableViewer, this.selectionProvider, part(),
                                                                      allColumnHandlers, childPropertyPath, showImages, popUpListFieldPresentationStyle );
            }
            else
            {
                columnHandler = new ColumnHandler( this.tableViewer, this.selectionProvider, part(),
                                                   allColumnHandlers, childPropertyPath, showImages );
            }
        }
       
        allColumnHandlers.add( columnHandler );
       
        return columnHandler;
    }
   
    private int getColumnCount()
    {
        return this.table.getColumnCount();
    }
   
    private ColumnHandler getColumnHandler( final int column )
    {
        return this.columnHandlers.get( column );
    }
   
    private void update( final Element element )
    {
        if( element == null )
        {
            throw new IllegalArgumentException();
        }
       
        if( ! disposed() )
        {
            final Element root = part().getLocalModelElement();
           
            Element el = element;
            TableRow row = null;
           
            while( row == null && el != null && el != root )
            {
                row = findTableRow( el );
               
                if( row == null )
                {
                    final Property parent = el.parent();
                   
                    if( parent != null )
                    {
                        el = parent.element();
                    }
                }
            }
           
            if( row != null )
            {
                update( row );
            }
        }
    }

    private void update( final TableRow row )
    {
        if( row == null )
        {
            throw new IllegalArgumentException();
        }
       
        runOnDisplayThread
        (
            new Runnable()
            {
                @Override
                public void run()
                {
                    TablePropertyEditorPresentation.this.tableViewer.update( row, null );
                }
            }
        );
    }
   
    private Element findTableRowElement( final Element element )
    {
        final ElementList<?> list = list();
        Element result = element;
       
        for( Property parent = element.parent(); parent != list; parent = result.parent() )
        {
            result = parent.element();
        }
       
        return result;
    }
   
    private TableRow findTableRow( final Element element )
    {
        if( element == null )
        {
            throw new IllegalArgumentException();
        }
       
        return this.rows.get( element );
    }
   
    public static final class Factory extends PropertyEditorPresentationFactory
    {
        @Override
        public PropertyEditorPresentation create( final PropertyEditorPart part, final SwtPresentation parent, final Composite composite )
        {
            if( part.property().definition() instanceof ListProperty )
            {
                return new TablePropertyEditorPresentation( part, parent, composite );
            }
           
            return null;
        }
    }
   
    private static final class CustomTableViewer extends TableViewer
    {
        public CustomTableViewer( final Composite parent, final int style )
        {
            super( parent, style );
        }

        @Override
        public void applyEditorValue()
        {
            super.applyEditorValue();
        }
    }

    private final class DefaultColumnLabelProvider extends ColumnLabelProvider
    {
        private final ColumnHandler columnHandler;
       
        public DefaultColumnLabelProvider( final ColumnHandler columnHandler )
        {
            this.columnHandler = columnHandler;
        }
   
        @Override
        public String getText( final Object obj )
        {
            final Element element = ( (TableRow) obj ).element();
            final Value<?> value = this.columnHandler.property( element );
           
            final String text = value.text();
            String label = null;
           
            try
            {
                label = value.service( ValueLabelService.class ).provide( text );
            }
            catch( Exception e )
            {
                Sapphire.service( LoggingService.class ).log( e );
            }
           
            if( label == null )
            {
                label = text;
            }
            else if( ! label.equals( text ) )
            {
                final LocalizationService localizationService = part().definition().adapt( LocalizationService.class );
                label = localizationService.transform( label, CapitalizationType.FIRST_WORD_ONLY, false );
            }
           
            if( label == null )
            {
                if( this.columnHandler.isEmptyTextLabelDesired( element ) )
                {
                    label = emptyRowIndicator.text();
                }
                else
                {
                    label = MiscUtil.EMPTY_STRING;
                }
            }
           
            return label;
        }
       
        @Override
        public Image getImage( final Object obj )
        {
            if( this.columnHandler.isElementImageDesired() )
            {
                return ( (TableRow) obj ).image();
            }
            else
            {
                return null;
            }
        }
   
        @Override
        public Color getForeground( final Object obj )
        {
            final Value<?> value = this.columnHandler.property( ( (TableRow) obj ).element() );
           
            if( value.text( false ) == null )
            {
                return Display.getCurrent().getSystemColor( SWT.COLOR_DARK_GRAY );
            }
            else
            {
                return null;
            }
        }
    }
   
    private static abstract class AbstractColumnEditingSupport extends EditingSupport
    {
        protected final ColumnHandler columnHandler;
       
        public AbstractColumnEditingSupport( final ColumnHandler columnHandler )
        {
            super( columnHandler.getTableViewer() );
           
            this.columnHandler = columnHandler;
        }

        @Override
        public boolean canEdit( final Object obj )
        {
            final Element element = ( (TableRow) obj ).element();
           
            boolean canEdit;
           
            if( this.columnHandler.property( element ).enabled() )
            {
                canEdit = ! this.columnHandler.editor( element ).isReadOnly();
            }
            else
            {
                canEdit = false;
            }
           
            return canEdit;
        }
       
        public abstract CellEditor getCellEditor( Object element );
        public abstract Object getValue( Object element );
        public abstract void setValue( Object element, Object value );
    }
   
    private class ColumnHandler
    {
        protected final Table table;
        protected final TableViewer tableViewer;
        protected final SelectionProvider selectionProvider;
        protected final PropertyEditorPart listPropertyEditor;
        protected final List<ColumnHandler> allColumnHandlers;
        protected final ModelPath property;
        protected final boolean showElementImage;
        protected final Collator collator;
        private CellLabelProvider labelProvider;
        private AbstractColumnEditingSupport editingSupport;
       
        public ColumnHandler( final TableViewer tableViewer,
                              final SelectionProvider selectionProvider,
                              final PropertyEditorPart listPropertyEditor,
                              final List<ColumnHandler> allColumnHandlers,
                              final ModelPath property,
                              final boolean showElementImage )
        {
            this.table = tableViewer.getTable();
            this.tableViewer = tableViewer;
            this.selectionProvider = selectionProvider;
            this.listPropertyEditor = listPropertyEditor;
            this.allColumnHandlers = allColumnHandlers;
            this.property = property;
            this.showElementImage = showElementImage;
            this.collator = Collator.getInstance();
        }
       
        public final Table getTable()
        {
            return this.table;
        }
       
        public final TableViewer getTableViewer()
        {
            return this.tableViewer;
        }
       
        public final SelectionProvider getSelectionProvider()
        {
            return this.selectionProvider;
        }
       
        public final PropertyEditorPart getListPropertyEditor()
        {
            return this.listPropertyEditor;
        }
       
        public final SwtResourceCache getImageCache()
        {
            return this.listPropertyEditor.getSwtResourceCache();
        }
       
        public final boolean isElementImageDesired()
        {
            return this.showElementImage;
        }
       
        public final ModelPath property()
        {
            return this.property;
        }
       
        public final Value<?> property( final Element element )
        {
            return (Value<?>) element.property( this.property );
        }
       
        public final PropertyEditorPart editor( final Element element )
        {
            return getListPropertyEditor().getChildPropertyEditor( element, property() );
        }
       
        public final CellLabelProvider getLabelProvider()
        {
            if( this.labelProvider == null )
            {
                this.labelProvider = createLabelProvider();
            }
           
            return this.labelProvider;
        }
       
        protected CellLabelProvider createLabelProvider()
        {
            return new DefaultColumnLabelProvider( this );
        }
       
        public final AbstractColumnEditingSupport getEditingSupport()
        {
            if( this.editingSupport == null && ! TablePropertyEditorPresentation.this.property().definition().getType().property( property() ).isReadOnly() )
            {
                this.editingSupport = createEditingSupport();
            }
           
            return this.editingSupport;
        }
       
        protected AbstractColumnEditingSupport createEditingSupport()
        {
            return new AbstractColumnEditingSupport( this )
            {
                private SapphireTextCellEditor cellEditor;
   
                @Override
                public CellEditor getCellEditor( final Object obj )
                {
                    if( this.cellEditor != null )
                    {
                        this.cellEditor.dispose();
                    }
                   
                    final PropertyEditorPart propertyEditor = editor( ( (TableRow) obj ).element() );
                    final SapphireActionGroup actions = propertyEditor.getActions();

                    final int style = ( getTable().getLinesVisible() ? SWT.NONE : SWT.BORDER );
                   
                    this.cellEditor = new SapphireTextCellEditor
                    (
                        createChildPropertyEditorPresentation( propertyEditor ),
                        getTableViewer(),
                        getSelectionProvider(),
                        propertyEditor.getLocalModelElement(),
                        (ValueProperty) propertyEditor.property().definition(),
                        actions,
                        style
                    );

                    if( isElementImageDesired() )
                    {
                        this.cellEditor.setHorizonalIndent( 3 );
                    }
                   
                    return this.cellEditor;
                }
   
                @Override
                public Object getValue( final Object obj )
                {
                    return property( ( (TableRow) obj ).element() );
                }
   
                @Override
                public void setValue( final Object obj,
                                      final Object value )
                {
                    property( ( (TableRow) obj ).element() ).write( value, true );
                }
            };
        }
       
        public boolean isEditorActivationEvent( final ColumnViewerEditorActivationEvent event )
        {
            return event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION ||
                   event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC ||
                   event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL;
        }
       
        @SuppressWarnings( { "unchecked", "rawtypes" } )
       
        public final int comparePropertyValues( final Element aElement, final Element bElement )
        {
            final Value<?> aValue = property( aElement );
            final Value<?> bValue = property( bElement );
           
            final boolean aEmpty = aValue.empty();
            final boolean bEmpty = bValue.empty();
           
            if( aEmpty && bEmpty )
            {
                return 0;
            }
            else if( aEmpty )
            {
                return 1;
            }
            else if( bEmpty )
            {
                return -1;
            }
            else
            {
                if( ! aValue.malformed() && ! bValue.malformed() )
                {
                    final Object aContent = aValue.content();
                    final Object bContent = bValue.content();
                   
                    if( aContent instanceof Boolean && bContent instanceof Boolean )
                    {
                        // The boolean comparison is intentionally reversed so that the following
                        // statement is true:
                        //
                        // compare( true, false ) == compare( true, null )
                       
                        return ( (Boolean) bContent ).compareTo( (Boolean) aContent );
                    }
                    else if( aContent instanceof Comparable && bContent instanceof Comparable )
                    {
                        final Comparable aComparable = (Comparable) aContent;
                        final Comparable bComparable = (Comparable) bContent;
                       
                        return aComparable.compareTo( bComparable );
                    }
                }
               
                return this.collator.compare( aValue.text(), bValue.text() );
            }
        }
       
        public boolean isEmptyTextLabelDesired( final Element element )
        {
            if( this.allColumnHandlers.get( 0 ) == this )
            {
                for( ColumnHandler handler : this.allColumnHandlers )
                {
                    if( handler instanceof BooleanPropertyColumnHandler )
                    {
                        return false;
                    }
                }
               
                for( ColumnHandler handler : this.allColumnHandlers )
                {
                    if( handler.property( element ).text() != null )
                    {
                        return false;
                    }
                }
               
                return true;
            }
           
            return false;
        }
    }
   
    private final class PopUpListFieldColumnPresentation extends ColumnHandler
    {
        private final PopUpListFieldStyle popUpListFieldStyle;
       
        public PopUpListFieldColumnPresentation( final TableViewer tableViewer,
                                                 final SelectionProvider selectionProvider,
                                                 final PropertyEditorPart listPropertyEditor,
                                                 final List<ColumnHandler> allColumnHandlers,
                                                 final ModelPath property,
                                                 final boolean showElementImage,
                                                 final PopUpListFieldStyle popUpListFieldStyle )
        {
            super( tableViewer, selectionProvider, listPropertyEditor, allColumnHandlers, property, showElementImage );
           
            this.popUpListFieldStyle = popUpListFieldStyle;
        }
       
        @Override
        protected AbstractColumnEditingSupport createEditingSupport()
        {
            return new AbstractColumnEditingSupport( this )
            {
                private PopUpListFieldCellEditorPresentation cellEditor;

                @Override
                public CellEditor getCellEditor( final Object obj )
                {
                    if( this.cellEditor != null )
                    {
                        this.cellEditor.dispose();
                    }
                   
                    final PropertyEditorPart propertyEditor = editor( ( (TableRow) obj ).element() );
                   
                    this.cellEditor = new PopUpListFieldCellEditorPresentation
                    (
                        getTableViewer(),
                        getSelectionProvider(),
                        propertyEditor.property(),
                        PopUpListFieldColumnPresentation.this.popUpListFieldStyle,
                        getTable().getLinesVisible() ? SWT.NONE : SWT.BORDER
                    );
                   
                    return this.cellEditor;
                }

                @Override
                public Object getValue( final Object obj )
                {
                    return property( ( (TableRow) obj ).element() );
                }
   
                @Override
                public void setValue( final Object obj,
                                      final Object value )
                {
                    property( ( (TableRow) obj ).element() ).write( value, true );
                }
            };
        }
    }
   
    private static final ImageDescriptor IMG_CHECKBOX_ON
        = SwtUtil.createImageDescriptor( BooleanPropertyColumnHandler.class, "CheckBoxOn.gif" );
   
    private static final ImageDescriptor IMG_CHECKBOX_OFF
        = SwtUtil.createImageDescriptor( BooleanPropertyColumnHandler.class, "CheckBoxOff.gif" );
   
    private final class BooleanPropertyColumnHandler extends ColumnHandler
    {
        private static final int CHECKBOX_IMAGE_WIDTH = 16;
        private static final int CHECKBOX_IMAGE_HEIGHT = 16;
       
        public BooleanPropertyColumnHandler( final TableViewer tableViewer,
                                             final SelectionProvider selectionProvider,
                                             final PropertyEditorPart listPropertyEditor,
                                             final List<ColumnHandler> allColumnHandlers,
                                             final ModelPath property,
                                             final boolean showElementImage )
        {
            super( tableViewer, selectionProvider, listPropertyEditor, allColumnHandlers, property, showElementImage );
        }

        @Override
        protected CellLabelProvider createLabelProvider()
        {
            return new OwnerDrawLabelProvider()
            {
                @Override
                protected void erase( final org.eclipse.swt.widgets.Event event,
                                      final Object element )
                {
                    // The default implementation causes non-native behavior on some platforms and
                    // is not desired. Nothing needs to happen on this event.
                }
   
                @Override
                protected void measure( final org.eclipse.swt.widgets.Event event,
                                        final Object element )
                {
                }
   
                @Override
                protected void paint( final org.eclipse.swt.widgets.Event event,
                                      final Object object )
                {
                    final TableItem item = (TableItem) event.item;
                    final Element element = ( (TableRow) item.getData() ).element();

                    if( property( element ).enabled() )
                    {
                        final boolean value = getPropertyValueAsBoolean( element );
                       
                        final Image image = getImageCache().image( value ? IMG_CHECKBOX_ON : IMG_CHECKBOX_OFF );
                        final Rectangle cellBounds = item.getBounds( event.index );
                        final Rectangle imageBounds = image.getBounds();
                        final int x = event.x + ( cellBounds.width - imageBounds.width ) / 2;
                        final int y = event.y + ( cellBounds.height - imageBounds.height ) / 2;
                       
                        event.gc.drawImage( image, x, y );
                    }
                }
            };
        }
       
        @Override
        protected AbstractColumnEditingSupport createEditingSupport()
        {
            return new AbstractColumnEditingSupport( this )
            {
                private CheckboxCellEditor cellEditor;

                @Override
                public CellEditor getCellEditor( final Object element )
                {
                    if( this.cellEditor == null )
                    {
                        this.cellEditor = new CheckboxCellEditor( getTable() );
                    }
                   
                    return this.cellEditor;
                }

                @Override
                public Object getValue( final Object obj )
                {
                    final Boolean val = (Boolean) property( ( (TableRow) obj ).element() ).content();
                    return ( val != null ? val : Boolean.FALSE );
                }

                @Override
                public void setValue( final Object obj,
                                      final Object value )
                {
                    final String str = String.valueOf( ( (Boolean) value ).booleanValue() );
                    property( ( (TableRow) obj ).element() ).write( str, true );
                }
            };
        }
       
        @Override
        public boolean isEditorActivationEvent( final ColumnViewerEditorActivationEvent event )
        {
            if( event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION )
            {
                final Rectangle cellBounds = ( (ViewerCell) event.getSource() ).getBounds();
               
                final Rectangle checkBoxBounds
                    = new Rectangle( cellBounds.x + ( cellBounds.width - CHECKBOX_IMAGE_WIDTH ) / 2,
                                     cellBounds.y + ( cellBounds.height - CHECKBOX_IMAGE_HEIGHT ) / 2,
                                     CHECKBOX_IMAGE_WIDTH, CHECKBOX_IMAGE_HEIGHT );
               
                final MouseEvent evt = (MouseEvent) event.sourceEvent;

                return checkBoxBounds.contains( evt.x, evt.y );
            }
           
            return false;
        }

        private boolean getPropertyValueAsBoolean( final Element element )
        {
            final Boolean val = (Boolean) property( element ).content();
            return ( val != null && val.booleanValue() == true );
        }
    }
   
    private static final class TableSorter extends ViewerComparator
    {
        private final ColumnHandler columnHandler;
        private final int direction;
       
        public TableSorter( final ColumnHandler columnHandler,
                            final int direction )
        {
            this.columnHandler = columnHandler;
            this.direction = direction;
        }
       
        @Override

        public int compare( final Viewer viewer, final Object x, final Object y )
        {
            int result = this.columnHandler.comparePropertyValues( ( (TableRow) x ).element(), ( (TableRow) y ).element() );
           
            if( this.direction == SWT.UP )
            {
                result = result * -1;
            }
           
            return result;
        }
    }
   
    private abstract class TablePropertyEditorActionHandler extends PropertyEditorActionHandler
    {
        @Override
        protected final Object run( final Presentation context )
        {
            TablePropertyEditorPresentation.this.tableViewer.applyEditorValue();
            return executeTablePropertyEditorAction( context );
        }
       
        protected abstract Object executeTablePropertyEditorAction( Presentation context );
    }
   
    private final class AddActionHandler extends TablePropertyEditorActionHandler
    {
        private final ElementType type;
       
        public AddActionHandler( final ElementType type )
        {
            this.type = type;
           
            setId( "Sapphire.Add." + this.type.getSimpleName() );
        }
       
        @Override
        public void init( final SapphireAction action,
                          final ActionHandlerDef def )
        {
            super.init( action, def );
           
            final ImageData typeSpecificAddImage = this.type.image();
           
            if( typeSpecificAddImage != null )
            {
                addImage( typeSpecificAddImage );
            }

            setLabel( this.type.getLabel( false, CapitalizationType.TITLE_STYLE, false ) );
        }

        @Override
        protected Object executeTablePropertyEditorAction( final Presentation context )
        {
            TablePropertyEditorPresentation.this.tableViewer.applyEditorValue();
            return list().insert( this.type );
        }
    }
   
    private abstract class SelectionBasedActionHandler extends TablePropertyEditorActionHandler
    {
        @Override
        public void init( final SapphireAction action,
                          final ActionHandlerDef def )
        {
            super.init( action, def );
           
            final ListSelectionService selectionService = action.getPart().service( ListSelectionService.class );
           
            final Listener selectionListener = new Listener()
            {
                @Override
                public void handle( final Event event )
                {
                    refreshEnablementState();
                }
            };
           
            selectionService.attach( selectionListener );
           
            attach
            (
                new Listener()
                {
                    @Override
                    public void handle( final Event event )
                    {
                        if( event instanceof DisposeEvent )
                        {
                            selectionService.detach( selectionListener );
                        }
                    }
                }
            );
        }
    }
   
    private final class DeleteActionHandler extends SelectionBasedActionHandler
    {
        @Override
        protected final Object executeTablePropertyEditorAction( final Presentation context )
        {
            final List<TableRow> rowsToDelete = getSelectedRows();
           
            final TableRow selectionPostDelete
                = findSelectionPostDelete( TablePropertyEditorPresentation.this.rows.values(), rowsToDelete );
           
            final ElementList<?> list = list();

            for( TableRow row : rowsToDelete )
            {
                list.remove( row.element() );
            }
           
            TablePropertyEditorPresentation.this.refreshOperation.run();
           
            if( selectionPostDelete != null )
            {
                TablePropertyEditorPresentation.this.tableViewer.setSelection( new StructuredSelection( selectionPostDelete ) );
            }
           
            return null;
        }
       
        @Override
        protected boolean computeEnablementState()
        {
            if( super.computeEnablementState() == true )
            {
                return ! getSelectedElements().isEmpty();
            }
           
            return false;
        }
    }

    private final class MoveUpActionHandler extends SelectionBasedActionHandler
    {
        @Override
        protected boolean computeEnablementState()
        {
            if( super.computeEnablementState() == true )
            {
                if( getSelectedElements().size() == 1 && TablePropertyEditorPresentation.this.tableViewer.getComparator() == null )
                {
                    final Element modelElement = getSelectedElement();
                    return ( list().indexOf( modelElement ) > 0 );
                }
            }
           
            return false;
        }

        @Override
        protected final Object executeTablePropertyEditorAction( final Presentation context )
        {
            final Element element = getSelectedElement();

            list().moveUp( element );
            TablePropertyEditorPresentation.this.tableViewer.reveal( findTableRow( element ) );
           
            return null;
        }
    }
   
    private final class MoveDownActionHandler extends SelectionBasedActionHandler
    {
        @Override
        protected boolean computeEnablementState()
        {
            if( super.computeEnablementState() == true )
            {
                if( getSelectedElements().size() == 1 && TablePropertyEditorPresentation.this.tableViewer.getComparator() == null )
                {
                    final Element modelElement = getSelectedElement();
                    final ElementList<?> list = list();
                    return ( list.indexOf( modelElement ) < ( list.size() - 1 ) );
                }
            }
           
            return false;
        }
   
        @Override
        protected final Object executeTablePropertyEditorAction( final Presentation context )
        {
            final Element element = getSelectedElement();

            list().moveDown( element );
            TablePropertyEditorPresentation.this.tableViewer.reveal( findTableRow( element ) );
           
            return null;
        }
    }
   
    public static final class SelectionProvider implements ISelectionProvider
    {
        private final TableViewer tableViewer;
        private final Set<ISelectionChangedListener> listeners;
        private ISelection fakeSelection;
       
        public SelectionProvider( final TableViewer tableViewer )
        {
            this.tableViewer = tableViewer;
            this.listeners = new CopyOnWriteArraySet<ISelectionChangedListener>();
            this.fakeSelection = null;
           
            this.tableViewer.addSelectionChangedListener
            (
                new ISelectionChangedListener()
                {
                    public void selectionChanged( final SelectionChangedEvent event )
                    {
                        notifySelectionChangedListeners();
                    }
                }
            );
        }
       
        public IStructuredSelection getSelectedRows()
        {
            return (IStructuredSelection) ( this.fakeSelection != null ? this.fakeSelection : this.tableViewer.getSelection() );
        }

        public ISelection getSelection()
        {
            final ListFactory<Element> elements = ListFactory.start();
           
            for( Iterator<?> itr = getSelectedRows().iterator(); itr.hasNext(); )
            {
                final TableRow row = (TableRow) itr.next();
                elements.add( row.element() );
            }
           
            return new StructuredSelection( elements.result() );
        }
       
        public void setSelection( final ISelection selection )
        {
            throw new UnsupportedOperationException();
        }

        public void setFakeSelection( final ISelection selection )
        {
            this.fakeSelection = selection;
            notifySelectionChangedListeners();
        }
       
        public final void addSelectionChangedListener( final ISelectionChangedListener listener )
        {
            this.listeners.add( listener );
        }

        public final void removeSelectionChangedListener( final ISelectionChangedListener listener )
        {
            this.listeners.remove( listener );
        }
       
        private final void notifySelectionChangedListeners()
        {
            final SelectionChangedEvent event = new SelectionChangedEvent( this, getSelection() );
           
            for( ISelectionChangedListener listener : this.listeners )
            {
                listener.selectionChanged( event );
            }
        }
    }
   
    private abstract class ImageProvider
    {
        private TableRow row;
       
        public final void init( final TableRow row )
        {
            this.row = row;
        }
       
        protected final TableRow row()
        {
            return this.row;
        }
       
        public abstract ImageData image();
        public void dispose() {}
    }
   
    private final class TableRow
    {
        private final Element element;
        private final ImageProvider imageProvider;
        private Listener validationListener;
        private Status.Severity validationSeverity;
       
        public TableRow( final Element element, final ImageProvider imageProvider )
        {
            this.element = element;
           
            this.imageProvider = imageProvider;
           
            if( this.imageProvider != null )
            {
                this.imageProvider.init( this );
            }
        }
       
        public Element element()
        {
            return this.element;
        }
       
        public Image image()
        {
            final ImageData image;
           
            if( this.imageProvider == null )
            {
                image = null;
            }
            else
            {
                image = this.imageProvider.image();
            }
           
            if( image == null )
            {
                return null;
            }
            else
            {
                if( this.validationListener == null )
                {
                    this.validationListener = new FilteredListener<PropertyValidationEvent>()
                    {
                        @Override
                        protected void handleTypedEvent( final PropertyValidationEvent event )
                        {
                            refreshValidation();
                        }
                    };
                   
                    this.element.attach( this.validationListener, "*" );
                   
                    this.validationSeverity = this.element.validation().severity();
                }
               
                return part().getSwtResourceCache().image( image, this.validationSeverity );
            }
        }
       
        private void refreshValidation()
        {
            final Status.Severity freshValidationSeverity = this.element.validation().severity();
           
            if( this.validationSeverity != freshValidationSeverity )
            {
                this.validationSeverity = freshValidationSeverity;
                update( this );
            }
        }
       
        public void dispose()
        {
            if( this.imageProvider != null )
            {
                this.imageProvider.dispose();
            }
           
            if( this.validationListener != null )
            {
                this.element.detach( this.validationListener, "*" );
            }
        }
    }
   
}
TOP

Related Classes of org.eclipse.sapphire.ui.forms.swt.TablePropertyEditorPresentation$CustomTableViewer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.