/******************************************************************************* * Copyright (c) 2009, 2013 EclipseSource and others. * 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: * EclipseSource - initial API and implementation ******************************************************************************/ package org.eclipse.rap.examples.pages; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ICellModifier; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerEditor; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.rap.examples.ExampleUtil; import org.eclipse.rap.examples.IExamplePage; import org.eclipse.rap.examples.pages.internal.ImageUtil; import org.eclipse.rap.rwt.SingletonUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSourceAdapter; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.events.FocusAdapter; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; public class TreeViewerExample implements IExamplePage { private static final int EDITOR_ACTIVATE = ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION; private final static int MODERN_STYLE = 0; private final static int TABLE_STYLE = 1; private TreeViewer currentViewer; private TreeViewer simpleTree; private TreeViewer complexTree; private int newItem; ///////////////// // create widgets public void createControl( Composite parent ) { parent.setLayout( ExampleUtil.createMainLayout( 2 ) ); createTopLeft( parent ); createTopRight( parent ); createFooter( parent ); setFocus(); } private void createTopLeft( Composite parent ) { Composite composite = new Composite( parent, SWT.NONE ); composite.setLayoutData( ExampleUtil.createFillData() ); composite.setLayout( ExampleUtil.createFillLayout(true) ); simpleTree = createSimpleTree( composite ); } private void createTopRight( Composite parent ) { Composite composite = new Composite( parent, SWT.NONE ); composite.setLayoutData( ExampleUtil.createFillData() ); composite.setLayout( ExampleUtil.createFillLayout(true) ); complexTree = createComplexTree( composite ); } private void createFooter( Composite parent ) { Composite footerComp = new Composite( parent, SWT.NONE ); footerComp.setLayout( ExampleUtil.createRowLayout( SWT.HORIZONTAL, true ) ); createControlButtons( footerComp ); } private void setFocus() { Tree tree = simpleTree.getTree(); tree.forceFocus(); tree.select( tree.getItem( 0 ) ); } private void createControlButtons( Composite parent ) { Button newButton = new Button( parent, SWT.PUSH ); newButton.setText( "New Item" ); newButton.addSelectionListener( new NewButtonSelectionHandler() ); Button removeButton = new Button( parent, SWT.PUSH ); removeButton.setText( "Remove Item(s)" ); removeButton.addSelectionListener( new RemoveButtonSelectionHandler() ); } private TreeViewer createSimpleTree( Composite parent ) { Tree tree = new Tree( parent, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION ); TreeViewer result = new TreeViewer( tree ); result.setContentProvider( new TreeContentProvider() ); TreeLabelProvider labelProvider = new TreeLabelProvider( parent.getDisplay(), MODERN_STYLE ); result.setLabelProvider( labelProvider ); result.setInput( createModel() ); result.expandAll(); tree.addFocusListener( new TreeFocusGainedHandler() ); addDNDSupport( result ); addCellEditor( result ); addToolTipSupport( result ); return result; } private TreeViewer createComplexTree( Composite parent ) { int style = SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.CHECK; Tree tree = new Tree( parent, style ); createColumn( tree, "City", SWT.LEFT, 185 ); createColumn( tree, "Timezone", SWT.CENTER, 85 ); createColumn( tree, "Offset", SWT.CENTER, 65 ); tree.setLinesVisible( true ); tree.setHeaderVisible( true ); CheckboxTreeViewer result = new CheckboxTreeViewer( tree ); result.addCheckStateListener( new TreeCheckStateListener( result ) ); result.setContentProvider( new TreeContentProvider() ); TreeLabelProvider labelProvider = new TreeLabelProvider( parent.getDisplay(), TABLE_STYLE ); result.setLabelProvider( labelProvider ); result.setInput( createModel() ); result.expandAll(); tree.addFocusListener( new TreeFocusGainedHandler() ); addDNDSupport( result ); addCellEditor( result ); addToolTipSupport( result ); return result; } private static TreeColumn createColumn( Tree parent, String name, int style, int width ) { TreeColumn result = new TreeColumn( parent, style ); result.setText( name ); result.setWidth( width ); result.setMoveable( true ); result.setResizable( true ); return result; } ////////////// // Drag & Drop private static void addDNDSupport( TreeViewer viewer ) { Transfer[] types = new Transfer[] { TreeObjectTransfer.getInstance() }; TreeDragListener dragListener = new TreeDragListener( viewer ); viewer.addDragSupport( DND.DROP_MOVE, types, dragListener ); TreeDropListener dropListener = new TreeDropListener( viewer ); viewer.addDropSupport( DND.DROP_MOVE, types, dropListener ); } ////////////// // Cell-editor private static void addCellEditor( TreeViewer viewer ) { CellEditor[] editors = new CellEditor[] { new TextCellEditor( viewer.getTree() ), new TextCellEditor( viewer.getTree() ), new TextCellEditor( viewer.getTree() ) }; viewer.setCellEditors( editors ); viewer.setCellModifier( new CellModifier( viewer ) ); String[] columnProperties = new String[] { "Name", "Timezone", "Offset" }; viewer.setColumnProperties( columnProperties ); ColumnViewerEditorActivationStrategy activationStrategy = new CellEditorActivationStrategy( viewer ); TreeViewerEditor.create( viewer, activationStrategy, EDITOR_ACTIVATE ); } private static void addToolTipSupport( TreeViewer viewer ) { ColumnViewerToolTipSupport.enableFor( viewer ); } private static TreeObject createModel() { TreeObject result = new TreeObject( "" ); TreeObject asia = new TreeObject( "Asia" ); result.addChild( asia ); asia.addChild( new City( "Hong Kong", "HKT", +8 ) ); asia.addChild( new City( "Tokyo", "JST", +9 ) ); TreeObject europe = new TreeObject( "Europe" ); result.addChild( europe ); europe.addChild( new City( "Lisbon", "WET", 0 ) ); europe.addChild( new City( "Berlin", "CET", +1 ) ); europe.addChild( new City( "Sofia", "EET", +2 ) ); europe.addChild( new City( "Moscow", "MT", +3 ) ); TreeObject northAmerica = new TreeObject( "North America" ); result.addChild( northAmerica ); northAmerica.addChild( new City( "New York", "EST", -5 ) ); northAmerica.addChild( new City( "Chicago", "CST", -6 ) ); northAmerica.addChild( new City( "Los Angeles", "PST", -8 ) ); northAmerica.addChild( new City( "Anchorage", "AKST", -9 ) ); return result; } private final class TreeFocusGainedHandler extends FocusAdapter { @Override public void focusGained( FocusEvent event ) { currentViewer = null; Tree currentTree = ( Tree )event.widget; if( simpleTree.getTree() == currentTree ) { currentViewer = simpleTree; } else if( complexTree.getTree() == currentTree ) { currentViewer = complexTree; } } } private static class TreeCheckStateListener implements ICheckStateListener { private final CheckboxTreeViewer viewer; TreeCheckStateListener( CheckboxTreeViewer viewer ) { this.viewer = viewer; } public void checkStateChanged( CheckStateChangedEvent event ) { TreeObject treeObject = ( TreeObject )event.getElement(); boolean checked = event.getChecked(); updateChildren( checked, treeObject ); updateParent( treeObject ); } private void updateParent( TreeObject treeObject ) { TreeObject parent = treeObject.getParent(); if( parent != null ) { boolean parentChecked = true; TreeObject[] children = parent.getChildren(); for( int i = 0; parentChecked && i < children.length; i++ ) { TreeObject child = children[ i ]; if( !viewer.getChecked( child ) ) { parentChecked = false; } } viewer.setChecked( parent, parentChecked ); updateParent( parent ); } } private void updateChildren( boolean checked, TreeObject parent ) { TreeObject[] children = parent.getChildren(); for( int i = 0; i < children.length; i++ ) { TreeObject treeObject = children[ i ]; viewer.setChecked( treeObject, checked ); if( treeObject.hasChildren() ) { updateChildren( checked, treeObject ); } } } } private final class RemoveButtonSelectionHandler extends SelectionAdapter { @Override public void widgetSelected( SelectionEvent event ) { if( currentViewer != null && !currentViewer.getSelection().isEmpty() ) { ITreeSelection sel = ( ITreeSelection )currentViewer.getSelection(); Iterator iterator = sel.iterator(); TreeObject parent = null; while( iterator.hasNext() ) { TreeObject obj = ( TreeObject )iterator.next(); parent = obj.getParent(); if( parent != null ) { parent.removeChild( obj ); } } if( parent != null ) { TreeObject newSel = null; if( parent.getParent() == null ) { TreeObject[] children = parent.getChildren(); if( children.length > 0 ) { newSel = children[ 0 ]; } } else { newSel = parent; } if( newSel != null ) { currentViewer.setSelection( new StructuredSelection( newSel ) ); } } currentViewer.refresh(); currentViewer.getTree().forceFocus(); } } } private final class NewButtonSelectionHandler extends SelectionAdapter { @Override public void widgetSelected( SelectionEvent event ) { if( currentViewer != null && !currentViewer.getSelection().isEmpty() ) { ITreeSelection sel = ( ITreeSelection )currentViewer.getSelection(); TreeObject obj = ( TreeObject )sel.getFirstElement(); newItem++; TreeObject newObject = new City( "New Item " + newItem , "", 0 ); obj.addChild( newObject ); currentViewer.expandToLevel( obj, 1 ); currentViewer.refresh(); currentViewer.setSelection( new StructuredSelection( newObject ) ); currentViewer.getTree().forceFocus(); } } } ////////////// // Drag & Drop private static class TreeDragListener extends DragSourceAdapter { private final TreeViewer viewer; private Object dragData; TreeDragListener( TreeViewer viewer ) { this.viewer = viewer; } @Override public void dragStart( DragSourceEvent event ) { dragData = getTreeObject( event.x, event.y ); } @Override public void dragSetData( DragSourceEvent event ) { event.data = dragData; } @Override public void dragFinished( DragSourceEvent event ) { viewer.refresh(); } private TreeObject getTreeObject( int x, int y ) { TreeObject result = null; ViewerCell cell = viewer.getCell( new Point( x, y ) ); if( cell != null ) { result = ( TreeObject )cell.getElement(); } return result; } } private static class TreeDropListener extends DropTargetAdapter { private final TreeViewer viewer; public TreeDropListener( TreeViewer viewer ) { this.viewer = viewer; } @Override public void dragEnter( DropTargetEvent event ){ event.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL; } @Override public void drop( DropTargetEvent event ) { if( event.data == null || event.item == null ) { event.detail = DND.DROP_NONE; } else { TreeObject draggedObject = ( TreeObject )event.data; TreeObject targetObject = ( TreeObject )event.item.getData(); if( isValidDrop( draggedObject, targetObject ) ) { draggedObject.getParent().removeChild( draggedObject ); targetObject.addChild( draggedObject ); viewer.refresh(); } } } private static boolean isValidDrop( TreeObject draggedObject, TreeObject targetObject ) { boolean result = false; if( draggedObject != null && targetObject != null ) { result = true; TreeObject current = targetObject; while( current != null && result ) { result = current != draggedObject; current = current.getParent(); } } return result; } } private static class TreeObjectTransfer extends Transfer { private static final String TYPE_NAME = "treeObject"; private static final int TYPE_ID = registerType( TYPE_NAME ); private TreeObjectTransfer() { } public static TreeObjectTransfer getInstance() { return SingletonUtil.getSessionInstance( TreeObjectTransfer.class ); } @Override protected int[] getTypeIds() { return new int[]{ TYPE_ID }; } @Override protected String[] getTypeNames() { return new String[]{ TYPE_NAME }; } @Override public TransferData[] getSupportedTypes() { int[] types = getTypeIds(); TransferData[] result = new TransferData[ types.length ]; for( int i = 0; i < types.length; i++ ) { result[ i ] = new TransferData(); result[ i ].type = types[ i ]; } return result; } @Override public boolean isSupportedType( TransferData transferData ) { boolean result = false; if( transferData != null ) { int[] types = getTypeIds(); for( int i = 0; !result && i < types.length; i++ ) { if( transferData.type == types[ i ] ) { result = true; } } } return result; } @Override public void javaToNative( Object object, TransferData transferData ) { transferData.data = object; } @Override public Object nativeToJava( TransferData transferData ) { return transferData.data; } } private static final class CellModifier implements ICellModifier { private final TreeViewer viewer; CellModifier( TreeViewer viewer ) { this.viewer = viewer; } public boolean canModify( Object element, String property ) { return element instanceof City || property.equals( "Name" ); } public Object getValue( Object element, String property ) { String result = ""; TreeObject treeObject = ( TreeObject )element; if( property.equals( "Name" ) ) { result = treeObject.getTitle(); } else if( property.equals( "Timezone" ) ) { City city = ( City )treeObject; result = city.getTimeZone(); } else if( property.equals( "Offset" ) ) { City city = ( City )treeObject; result = String.valueOf( city.getOffset() ); } else { throw new IllegalArgumentException( "Unkown property " + property ); } return result; } public void modify( Object element, String property, Object value ) { TreeObject treeObject; if( element instanceof Item ) { treeObject = ( TreeObject )( ( Item ) element ).getData(); } else { treeObject = ( TreeObject )element; } String string = ( String )value; if( property.equals( "Name" ) ) { treeObject.setName( string ); } else if( property.equals( "Timezone" ) ) { City city = ( City )treeObject; city.setTimeZone( string ); } else if( property.equals( "Offset" ) ) { City city = ( City )treeObject; try { city.setOffset( Integer.parseInt( string ) ); } catch( NumberFormatException e ) { } } else { throw new IllegalArgumentException( "Unkown property " + property ); } viewer.update( treeObject, new String[]{ property } ); } } private static final class CellEditorActivationStrategy extends ColumnViewerEditorActivationStrategy { CellEditorActivationStrategy( ColumnViewer viewer ) { super( viewer ); } @Override protected boolean isEditorActivationEvent( ColumnViewerEditorActivationEvent event ) { boolean isTraversal = event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL; boolean isDoubleClick = event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION; boolean isProgrammatic = event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; return isTraversal || isDoubleClick || isProgrammatic; } } //////// // Model private static class TreeObject { private final List<TreeObject> children; private String name; private TreeObject parent; public TreeObject( String name ) { setName( name ); children = new ArrayList<TreeObject>(); } public void setParent( TreeObject parent ) { this.parent = parent; } public TreeObject getParent() { return parent; } public void setName( String name ) { this.name = name; } public String getTitle() { return name; } public void addChild( TreeObject child ) { children.add( child ); child.setParent( this ); } public void removeChild( TreeObject child ) { children.remove( child ); child.setParent( null ); } public TreeObject[] getChildren() { TreeObject[] result = new TreeObject[ children.size() ]; children.toArray( result ); return result; } public boolean hasChildren() { return children.size() > 0; } @Override public String toString() { return name; } } private static class City extends TreeObject { private String timezone; private int offset; public City( String name, String timezone, int offset ) { super( name ); setTimeZone( timezone ); setOffset( offset ); } private void setOffset( int offset ) { this.offset = offset; } public void setTimeZone( String tz ) { timezone = tz; } private int getOffset() { return offset; } public String getTimeZone() { return timezone; } } private static final class TreeLabelProvider extends CellLabelProvider { private static final String ICON_GREENDOT = "greendot.gif"; private static final String ICON_WORLD = "world.gif"; private static final String ICON_EARTH = "earth-icon.png"; private static final int COLUMN_TEXT = 0; private static final int COLUMN_OFFSET = 2; private static final int COLUMN_TIMEZONE = 1; private final Device device; private final Image continentImage; private final Image cityImage; private final Font cityFont; private final Font continentFont; private final Color timezoneTextColor; private final Color offsetTextColor; private final int style; TreeLabelProvider( Device device, int style ) { this.device = device; this.style = style; cityFont = createFont( "Times New Roman", 13, SWT.NONE ); continentFont = createFont( "Arial", 14, SWT.ITALIC ); timezoneTextColor = new Color( device, 239, 41, 41 ); offsetTextColor = new Color( device, 252, 175, 62 ); if( style == MODERN_STYLE ) { continentImage = ImageUtil.getImage( device, ICON_EARTH ); cityImage = ImageUtil.getImage( device, ICON_GREENDOT ); } else { continentImage = ImageUtil.getImage( device, ICON_WORLD ); cityImage = ImageUtil.getImage( device, ICON_GREENDOT ); } } @Override public void update( ViewerCell cell ) { TreeObject treeObject = ( TreeObject )cell.getElement(); int columnIndex = cell.getColumnIndex(); switch( columnIndex ) { case COLUMN_TEXT: updateName( cell, treeObject ); break; case COLUMN_TIMEZONE: updateTimeZone( cell, treeObject ); break; case COLUMN_OFFSET: updateOffset( cell, treeObject ); break; } } @Override public String getToolTipText( Object element ) { String result = ""; if( element instanceof City ) { City city = ( City )element; String name = city.getTitle(); String timeZone = city.getTimeZone(); String utcOffset = getUTCOffset( city ); result = name + " (" + timeZone + ", " + utcOffset + ")"; } return result; } private void updateName( ViewerCell cell, TreeObject treeObject ) { cell.setText( treeObject.name ); if( style == MODERN_STYLE ) { if( treeObject instanceof City ) { cell.setFont( cityFont ); } else { cell.setFont( continentFont ); } } cell.setImage( treeObject instanceof City ? cityImage : continentImage ); } private void updateTimeZone( ViewerCell cell, TreeObject treeObject ) { if( treeObject instanceof City ) { City city = ( City )treeObject; cell.setText( city.getTimeZone() ); if( style == TABLE_STYLE ) { cell.setForeground( timezoneTextColor ); } } } private void updateOffset( ViewerCell cell, TreeObject treeObject ) { if( treeObject instanceof City ) { if( style == TABLE_STYLE ) { cell.setForeground( offsetTextColor ); } City city = ( City )treeObject; cell.setText( getUTCOffset( city ) ); } } private Font createFont( String name, int size, int style ) { FontData fontData = new FontData( name, size, style ); return new Font( device, fontData ); } private static String getUTCOffset( City city ) { String sign = city.getOffset() >= 0 ? "-" : ""; return "UTC " + sign + String.valueOf( city.getOffset() ); } } private static class TreeContentProvider implements ITreeContentProvider { public Object[] getElements( Object parent ) { return getChildren( parent ); } public Object getParent( Object child ) { Object result = null; if( child instanceof City ) { result = ( ( City )child ).getParent(); } return result; } public Object[] getChildren( Object parent ) { Object[] result = new Object[ 0 ]; if( parent instanceof TreeObject ) { result = ( ( TreeObject )parent ).getChildren(); } return result; } public boolean hasChildren( Object parent ) { boolean result = false; if( parent instanceof TreeObject ) { result = ( ( TreeObject )parent ).hasChildren(); } return result; } public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) { } public void dispose() { } } }