/* * Chrysalix * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * See the AUTHORS.txt file in the distribution for a full listing of * individual contributors. * * Chrysalix is free software. Unless otherwise indicated, all code in Chrysalix * is licensed to you under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Chrysalix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.chrysalix.eclipse.focustree; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.RoundedRectangle; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.RowLayoutFactory; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DragDetectEvent; import org.eclipse.swt.events.DragDetectListener; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.chrysalix.common.CheckArg; import org.chrysalix.common.I18n; import org.chrysalix.common.ChrysalixException; import org.chrysalix.eclipse.EclipseI18n; import org.chrysalix.eclipse.Util; import org.chrysalix.eclipse.focustree.FocusTreeCanvas.Cell; import org.chrysalix.eclipse.focustree.FocusTreeCanvas.CellColumn; //TODO move position of cells //TODO zoom, search //TODO separate view from controller //TODO color/font registry //TODO context menu contributions; indicator context menu contributions //TODO keyboard arrows //TODO tab traversal //TODO rotate //TODO user override double-click cell //TODO listen for model/view model updates //TODO tutorial //TODO filter mapped properties //TODO filter referencing operations in sync'd tree //TODO renderers for cells, add and delete buttons (background column, cell columns, headers?) to save memory //TODO ability to sort columns /** * */ public class FocusTree extends Composite { static final Clipboard CLIPBOARD = new Clipboard( Display.getCurrent() ); static final int HEADER_MARGIN = 2; private static final String HIDE_BUTTON_PROPERTY = "org.chrysalix.hideButton"; private static final String COLUMN_PROPERTY = "org.chrysalix.column"; Object root; Model model; ViewModel viewModel = new ViewModel(); final Composite pathButtonBar; final Composite headerBar; final FocusTreeCanvas focusTreeCanvas; final Composite headeredCanvas; final ScrolledComposite scroller; final List< Column > columns = new ArrayList<>(); final Label leftPathBarButton, rightPathBarButton; final MenuItem iconViewMenuItem; final PaintListener pathButtonPaintListener = new PaintListener() { @Override public void paintControl( final PaintEvent event ) { event.gc.setBackground( pathButtonBar.getBackground() ); event.gc.fillRectangle( event.x, event.y, event.width, event.height ); final Label label = ( Label ) event.widget; event.gc.setBackground( label.getBackground() ); event.gc.fillRoundRectangle( event.x, event.y, event.width, event.height, event.height, event.height ); event.gc.drawString( label.getText(), event.height / 2, event.y ); } }; final PaintListener headerPaintListener = new PaintListener() { @Override public void paintControl( final PaintEvent event ) { final Rectangle bounds = ( ( Composite ) event.widget ).getBounds(); event.gc.drawRectangle( 0, 0, bounds.width - 1, bounds.height - 1 ); } }; /** * @param parent * parent composite * @param closable * <code>true</code> if this tree can be closed by the user * @param root * the root of this tree * @param model * the model for this tree */ @SuppressWarnings( "unused" ) public FocusTree( final Composite parent, final boolean closable, final Object root, final Model model ) { super( parent, SWT.NONE ); this.root = root; this.model = model; ( ( FillLayout ) parent.getLayout() ).type = SWT.VERTICAL; GridLayoutFactory.fillDefaults().spacing( 0, 0 ).applyTo( this ); // Construct pop-up menu final Menu popup = new Menu( getShell(), SWT.POP_UP ); setMenu( popup ); final MenuItem collapseAllMenuItem = new MenuItem( popup, SWT.PUSH ); collapseAllMenuItem.setText( EclipseI18n.focusTreeCollapseAllMenuItem.text() ); collapseAllMenuItem.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { focusTreeCanvas.collapseAllSelected(); } } ); final MenuItem duplicateMenuItem = new MenuItem( popup, SWT.PUSH ); duplicateMenuItem.setText( EclipseI18n.focusTreeDuplicateMenuItem.text() ); duplicateMenuItem.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { duplicate( root ); } } ); iconViewMenuItem = new MenuItem( popup, SWT.PUSH ); iconViewMenuItem.setText( EclipseI18n.focusTreeShowIconViewMenuItem.text() ); addMenuDetectListener( new MenuDetectListener() { @Override public void menuDetected( final MenuDetectEvent event ) { if ( focusTreeCanvas.iconViewShown() ) { iconViewMenuItem.setText( EclipseI18n.focusTreeHideIconViewMenuItem.text() ); return; } iconViewMenuItem.setText( EclipseI18n.focusTreeShowIconViewMenuItem.text() ); final Control control = getDisplay().getCursorControl(); Column column = null; if ( control instanceof FocusTreeCanvas ) for ( final Column col : columns ) { if ( col.bounds.contains( event.x, event.y ) ) { column = col; break; } } else column = ( Column ) control.getData( COLUMN_PROPERTY ); iconViewMenuItem.setEnabled( column != null ); iconViewMenuItem.setData( COLUMN_PROPERTY, column ); } } ); iconViewMenuItem.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { toggleIconView( ( Column ) iconViewMenuItem.getData( COLUMN_PROPERTY ) ); } } ); new MenuItem( popup, SWT.SEPARATOR ); final MenuItem copyPathMenuItem = new MenuItem( popup, SWT.PUSH ); copyPathMenuItem.setText( EclipseI18n.focusTreeCopyPathMenuItem.text() ); // // Construct zoom slider // final Composite sliderPanel = new Composite( toolBar, SWT.BORDER ); // GridDataFactory.swtDefaults().applyTo( sliderPanel ); // GridLayoutFactory.fillDefaults().applyTo( sliderPanel ); // final Slider zoomSlider = new Slider( sliderPanel, SWT.NONE ); // zoomSlider.setSelection( 50 ); // zoomSlider.setThumb( 1 ); // zoomSlider.setToolTipText( EclipseI18n.focusTreeZoomToolTip.text() ); // // Construct search field // final Text searchText = new Text( toolBar, SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL ); // GridDataFactory.swtDefaults().align( SWT.FILL, SWT.CENTER ).grab( true, false ).applyTo( searchText ); // searchText.setToolTipText( EclipseI18n.focusTreeSearchToolTip.text() ); // Construct path bar final Composite pathBar = new Composite( this, SWT.NONE ); GridDataFactory.swtDefaults().align( SWT.FILL, SWT.CENTER ).grab( true, false ).applyTo( pathBar ); GridLayoutFactory.fillDefaults().numColumns( 5 ).applyTo( pathBar ); ToolBar subToolBar = new ToolBar( pathBar, SWT.NONE ); final SelectionAdapter copyPathSelectionListener = new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { final TextTransfer textTransfer = TextTransfer.getInstance(); final StringBuilder path = new StringBuilder(); for ( final Control control : pathButtonBar.getChildren() ) path.append( '/' ).append( ( ( Label ) control ).getText() ); CLIPBOARD.setContents( new Object[] { path.toString() }, new Transfer[] { textTransfer } ); } }; newToolBarButton( subToolBar, SWT.PUSH, "copy.gif", EclipseI18n.focusTreeCopyPathToolTip, copyPathSelectionListener ); copyPathMenuItem.addSelectionListener( copyPathSelectionListener ); leftPathBarButton = new Label( pathBar, SWT.NONE ); final int arrowSize = leftPathBarButton.computeSize( SWT.DEFAULT, SWT.DEFAULT ).y * 2 / 3; leftPathBarButton.setImage( newArrowImage( arrowSize, true ) ); leftPathBarButton.setToolTipText( EclipseI18n.focusTreePreviousPathButtonToolTip.text() ); leftPathBarButton.setVisible( false ); pathButtonBar = new Composite( pathBar, SWT.NONE ); GridDataFactory.swtDefaults().align( SWT.FILL, SWT.CENTER ).grab( true, false ).applyTo( pathButtonBar ); RowLayoutFactory.fillDefaults().fill( true ).wrap( false ).applyTo( pathButtonBar ); rightPathBarButton = new Label( pathBar, SWT.NONE ); rightPathBarButton.setImage( newArrowImage( arrowSize, false ) ); rightPathBarButton.setVisible( false ); rightPathBarButton.setToolTipText( EclipseI18n.focusTreeNextPathButtonToolTip.text() ); leftPathBarButton.addMouseListener( new MouseAdapter() { @Override public void mouseUp( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) scrollPathBarLeft(); } } ); rightPathBarButton.addMouseListener( new MouseAdapter() { @Override public void mouseUp( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) scrollPathBarRight(); } } ); pathButtonBar.addListener( SWT.Resize, new Listener() { @Override public void handleEvent( final Event event ) { // Show all path buttons final Control[] pathButtons = pathButtonBar.getChildren(); for ( final Control pathButton : pathButtons ) { pathButton.setVisible( true ); ( ( RowData ) pathButton.getLayoutData() ).exclude = false; } // Hide first shown path button on left until all buttons fit in button bar hideExcessiveLeftMostPathButtons(); } } ); subToolBar = new ToolBar( pathBar, SWT.NONE ); if ( closable ) { final SelectionAdapter closeSelectionListener = new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { final Composite parent = getParent(); dispose(); parent.layout(); } }; newToolBarButton( subToolBar, SWT.PUSH, "close.gif", EclipseI18n.focusTreeCloseTreeToolTip, closeSelectionListener ); new MenuItem( popup, SWT.SEPARATOR ); final MenuItem closeMenuItem = new MenuItem( popup, SWT.PUSH ); closeMenuItem.setText( EclipseI18n.focusTreeCloseTreeMenuItem.text() ); closeMenuItem.addSelectionListener( closeSelectionListener ); } // Construct horizontally-scrolling diagram area scroller = new ScrolledComposite( this, SWT.H_SCROLL | SWT.V_SCROLL ); GridDataFactory.fillDefaults().grab( true, true ).applyTo( scroller ); scroller.setExpandVertical( true ); scroller.setExpandHorizontal( true ); scroller.setBackgroundMode( SWT.INHERIT_FORCE ); // Construct canvas with header headeredCanvas = new Composite( scroller, SWT.NONE ); GridLayoutFactory.fillDefaults().spacing( 0, 0 ).applyTo( headeredCanvas ); scroller.setContent( headeredCanvas ); scroller.setBackground( getDisplay().getSystemColor( SWT.COLOR_WHITE ) ); // Construct header bar headerBar = new Composite( headeredCanvas, SWT.NONE ); GridDataFactory.swtDefaults().align( SWT.FILL, SWT.CENTER ).grab( true, false ).applyTo( headerBar ); GridLayoutFactory.fillDefaults().spacing( 0, 0 ).numColumns( 0 ).applyTo( headerBar ); // Construct inner canvas focusTreeCanvas = new FocusTreeCanvas( this, headeredCanvas, SWT.BORDER | SWT.DOUBLE_BUFFERED ); GridDataFactory.fillDefaults().grab( true, true ).applyTo( focusTreeCanvas ); focusTreeCanvas.addMouseTrackListener( new MouseTrackAdapter() { @Override public void mouseExit( final MouseEvent event ) { focusTreeCanvas.toolBar.setVisible( false ); } @Override public void mouseHover( final MouseEvent event ) { final Point origin = scroller.getOrigin(); if ( !focusTreeCanvas.iconViewShown() && event.x - origin.x < focusTreeCanvas.toolBar.getSize().width ) { focusTreeCanvas.moveToolBar( origin.x ); focusTreeCanvas.toolBar.setVisible( true ); } else focusTreeCanvas.toolBar.setVisible( false ); } } ); addListener( SWT.Resize, new Listener() { @Override public void handleEvent( final Event event ) { if ( focusTreeCanvas.iconViewShown() ) focusTreeCanvas.updateIconViewBounds(); else focusTreeCanvas.updateBounds(); } } ); initialize(); } /** * Creates a non-closable focus tree * * @param parent * parent composite * @param root * the root of this tree * @param model * the model for this tree */ public FocusTree( final Composite parent, final Object root, final Model model ) { this( parent, false, root, model ); } void addColumn( final Object item ) { // Save previous column for use when resizing column final Column previousColumn = columns.isEmpty() ? null : columns.get( columns.size() - 1 ); // Add a new column final Column column = new Column(); columns.add( column ); column.item = item; final Color pathButtonBackgroundColor = viewModel.pathButtonBackgroundColor( column.item ); final Color pathButtonForegroundColor = viewModel.pathButtonForegroundColor( column.item ); // Add button for column to path button bar createPathBarButton( column, pathButtonBackgroundColor, pathButtonForegroundColor ); // Construct header createHeader( column, previousColumn, pathButtonBackgroundColor, pathButtonForegroundColor ); // Add column to inner canvas focusTreeCanvas.addColumn( column ); // Update width of header to match column width GridDataFactory.swtDefaults().hint( column.bounds.width, SWT.DEFAULT ).applyTo( column.header ); headerBar.layout(); } boolean columnShown( final Column column ) { return ( ( GridLayout ) column.header.getLayout() ).numColumns > 1; } private void createHeader( final Column column, final Column previousColumn, final Color pathButtonBackgroundColor, final Color pathButtonForegroundColor ) { ( ( GridLayout ) headerBar.getLayout() ).numColumns++; column.header = new Composite( headerBar, SWT.NONE ); GridLayoutFactory.fillDefaults().numColumns( 3 ).margins( HEADER_MARGIN, 1 ).applyTo( column.header ); column.header.setBackground( getDisplay().getSystemColor( SWT.COLOR_GRAY ) ); column.header.addPaintListener( headerPaintListener ); final ColumnHeaderMouseListener columnHeaderMouseListener = new ColumnHeaderMouseListener( column, previousColumn ); column.header.addMouseListener( columnHeaderMouseListener ); column.header.addMouseMoveListener( columnHeaderMouseListener ); column.header.addMouseTrackListener( columnHeaderMouseListener ); column.header.addDragDetectListener( columnHeaderMouseListener ); column.header.setToolTipText( EclipseI18n.focusTreeHeaderToolTip.text() ); final Label childCount = new Label( column.header, SWT.NONE ); GridDataFactory.swtDefaults().applyTo( childCount ); try { childCount.setText( String.valueOf( model.childCount( column.item ) ) ); } catch ( final ChrysalixException e ) { childCount.setText( "?" ); } childCount.setToolTipText( EclipseI18n.focusTreeChildCountToolTip.text() ); childCount.setVisible( false ); final Label itemName = new Label( column.header, SWT.CENTER ); GridDataFactory.swtDefaults().align( SWT.FILL, SWT.FILL ).grab( true, true ).applyTo( itemName ); String name; try { name = model.name( column.item ).toString(); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetName, column.item ); name = EclipseI18n.focusTreeErrorText.text( e.getMessage() ); } itemName.setText( name ); try { itemName.setToolTipText( EclipseI18n.focusTreeParentNameToolTip.text( model.qualifiedName( column.item ) ) ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetQualifiedName, column.item ); itemName.setToolTipText( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } final FontData fontData = itemName.getFont().getFontData()[ 0 ]; fontData.setStyle( SWT.BOLD ); itemName.setFont( new Font( getDisplay(), fontData ) ); final Composite hideButtonPanel = new Composite( column.header, SWT.NONE ); GridDataFactory.swtDefaults().applyTo( hideButtonPanel ); GridLayoutFactory.fillDefaults().applyTo( hideButtonPanel ); hideButtonPanel.setToolTipText( EclipseI18n.focusTreeHeaderToolTip.text() ); final Label hideButton = new Label( hideButtonPanel, SWT.NONE ); GridDataFactory.swtDefaults().align( SWT.RIGHT, SWT.CENTER ).grab( true, false ).applyTo( hideButton ); hideButton.setImage( Util.image( "minimize.gif" ) ); hideButton.setAlignment( SWT.RIGHT ); hideButton.setToolTipText( EclipseI18n.focusTreeHideColumnToolTip.text() ); hideButton.setVisible( false ); column.header.setData( HIDE_BUTTON_PROPERTY, hideButton ); final Label showButton = new Label( column.header, SWT.NONE ); GridDataFactory.swtDefaults().exclude( true ).applyTo( showButton ); showButton.setImage( Util.image( "maximize.gif" ) ); showButton.setToolTipText( EclipseI18n.focusTreeShowColumnToolTip.text( name ) ); showButton.setVisible( false ); // Save column in header components column.header.setData( COLUMN_PROPERTY, column ); itemName.setData( COLUMN_PROPERTY, column ); childCount.setData( COLUMN_PROPERTY, column ); hideButtonPanel.setData( COLUMN_PROPERTY, column ); hideButton.setData( COLUMN_PROPERTY, column ); // Wire extra header info to show on mouse over final MouseTrackListener headerMouseTrackListener = new MouseTrackAdapter() { @Override public void mouseEnter( final MouseEvent event ) { if ( columnShown( column ) ) { childCount.setVisible( true ); if ( !focusTreeCanvas.iconViewShown() ) hideButton.setVisible( true ); } } @Override public void mouseExit( final MouseEvent event ) { childCount.setVisible( false ); hideButton.setVisible( false ); } }; column.header.addMouseTrackListener( headerMouseTrackListener ); itemName.addMouseTrackListener( headerMouseTrackListener ); childCount.addMouseTrackListener( headerMouseTrackListener ); hideButtonPanel.addMouseTrackListener( headerMouseTrackListener ); hideButton.addMouseTrackListener( headerMouseTrackListener ); // Wire drill-into ability final MouseListener headerMouseListener = new MouseAdapter() { @Override public void mouseDoubleClick( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) toggleIconView( column ); } @Override public void mouseUp( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) focusTreeCanvas.scrollToFocusLine(); } }; column.header.addMouseListener( headerMouseListener ); itemName.addMouseListener( headerMouseListener ); childCount.addMouseListener( headerMouseListener ); hideButtonPanel.addMouseListener( headerMouseListener ); // Wire show and hide buttons hideButton.addMouseListener( new MouseAdapter() { @Override public void mouseUp( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) hideColumn( column, childCount, itemName, hideButtonPanel, showButton ); } } ); showButton.addMouseListener( new MouseAdapter() { @Override public void mouseUp( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) showColumn( column, childCount, itemName, hideButtonPanel, showButton, pathButtonBackgroundColor, pathButtonForegroundColor ); } } ); // Make child count and hide buttons the same size so arrow is centered final int width = Math.max( childCount.computeSize( SWT.DEFAULT, SWT.DEFAULT ).x, hideButtonPanel.computeSize( SWT.DEFAULT, SWT.DEFAULT ).x ); GridDataFactory.swtDefaults().hint( width, SWT.DEFAULT ).applyTo( childCount ); GridDataFactory.swtDefaults().hint( width, SWT.DEFAULT ).applyTo( hideButtonPanel ); } private void createPathBarButton( final Column column, final Color pathButtonBackgroundColor, final Color pathButtonForegroundColor ) { column.pathButton = new Label( pathButtonBar, SWT.NONE ); column.pathButton.setBackground( pathButtonBackgroundColor ); column.pathButton.setForeground( pathButtonForegroundColor ); try { column.pathButton.setText( model.name( column.item ).toString() ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetName, column.item ); column.pathButton.setText( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } final Point size = column.pathButton.computeSize( SWT.DEFAULT, SWT.DEFAULT ); column.pathButton.setAlignment( SWT.CENTER ); column.pathButton.setLayoutData( new RowData( size.x + 10, size.y ) ); column.pathButton.addPaintListener( pathButtonPaintListener ); column.pathButton.addMouseListener( new MouseAdapter() { @Override public void mouseUp( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) ) { if ( focusTreeCanvas.iconViewShown() ) { hideIconView( column ); focusTreeCanvas.hideIconView( column ); } scroller.setOrigin( column.bounds.x, column.bounds.y ); focusTreeCanvas.scrollToFocusLine(); } } } ); try { column.pathButton.setToolTipText( EclipseI18n.focusTreePathButtonToolTip.text( model.qualifiedName( column.item ) ) ); } catch ( final ChrysalixException e ) { Util.logError( e, EclipseI18n.focusTreeUnableToGetName, column.item ); column.pathButton.setToolTipText( EclipseI18n.focusTreeErrorText.text( e.getMessage() ) ); } hideExcessiveLeftMostPathButtons(); } void duplicate( final Object root ) { final FocusTree tree = new FocusTree( getParent(), true, root, model ); tree.setModel( model ); tree.setRoot( root ); tree.moveBelow( this ); getParent().layout(); } void hideColumn( final Column column, final Control childCount, final Control itemName, final Control hideButtonPanel, final Control showButton ) { // Hide column header ( ( GridData ) childCount.getLayoutData() ).exclude = true; childCount.setVisible( false ); ( ( GridData ) itemName.getLayoutData() ).exclude = true; itemName.setVisible( false ); ( ( GridData ) hideButtonPanel.getLayoutData() ).exclude = true; hideButtonPanel.setVisible( false ); ( ( GridData ) showButton.getLayoutData() ).exclude = false; showButton.setVisible( true ); // Save current width for later re-show final GridData gridData = ( ( GridData ) column.header.getLayoutData() ); column.widthBeforeHiding = gridData.widthHint; // Change layout to use new preferred width ( ( GridLayout ) column.header.getLayout() ).numColumns = 1; final int width = column.header.computeSize( SWT.DEFAULT, SWT.DEFAULT ).x; gridData.widthHint = width; headerBar.layout(); // Hide column's canvas figures focusTreeCanvas.hideColumn( column, width ); column.pathButton.setBackground( viewModel.pathButtonHiddenBackgroundColor( column.item ) ); column.pathButton.setForeground( viewModel.pathButtonHiddenForegroundColor( column.item ) ); } void hideExcessiveLeftMostPathButtons() { final Control[] pathButtons = pathButtonBar.getChildren(); final int width = pathButtonBar.getBounds().width; for ( int ndx = 0; ndx < pathButtons.length && pathButtonBar.computeSize( SWT.DEFAULT, SWT.DEFAULT ).x > width; ++ndx ) { leftPathBarButton.setVisible( true ); final Control pathButton = pathButtons[ ndx ]; if ( pathButton.isVisible() ) { pathButton.setVisible( false ); ( ( RowData ) pathButton.getLayoutData() ).exclude = true; } } leftPathBarButton.setVisible( pathButtons.length == 0 ? false : ( ( RowData ) ( ( Label ) pathButtons[ 0 ] ).getLayoutData() ).exclude ); rightPathBarButton.setVisible( pathButtons.length == 0 ? false : ( ( RowData ) ( ( Label ) pathButtons[ pathButtons.length - 1 ] ).getLayoutData() ).exclude ); pathButtonBar.layout(); pathButtonBar.getParent().layout(); } void hideIconView( final Column column ) { for ( final Control control : headerBar.getChildren() ) { ( ( GridData ) control.getLayoutData() ).exclude = false; control.setVisible( true ); } final GridData gridData = ( GridData ) column.header.getLayoutData(); gridData.grabExcessHorizontalSpace = false; gridData.horizontalAlignment = SWT.BEGINNING; headerBar.layout(); } private void initialize() { // Dispose of all controls dependent upon old model for ( final Control control : pathButtonBar.getChildren() ) control.dispose(); for ( final Control control : headerBar.getChildren() ) control.dispose(); columns.clear(); focusTreeCanvas.modelChanged(); // Add new first column for root if ( root != null && model != null ) addColumn( root ); } boolean leftMouseButtonClicked( final MouseEvent event ) { return event.button == 1 && ( event.stateMask & SWT.MODIFIER_MASK ) == 0; } /** * @return the focus tree model */ public Model model() { return model; } private Image newArrowImage( final int size, final boolean leftArrow ) { final Image image = new Image( getDisplay(), size / 2 + 1, size ); final GC gc = new GC( image ); gc.setAntialias( SWT.ON ); gc.setForeground( getDisplay().getSystemColor( SWT.COLOR_BLACK ) ); if ( leftArrow ) for ( int x = 0, y = size / 2; y >= 0; x++, y-- ) gc.drawLine( x, y, x, y + x * 2 ); else for ( int y = 0, x = 0; y <= size / 2; x++, y++ ) gc.drawLine( x, y, x, y + size - 1 - x * 2 ); gc.dispose(); final ImageData data = image.getImageData(); image.dispose(); data.transparentPixel = data.palette.getPixel( new RGB( 255, 255, 255 ) ); return new Image( getDisplay(), data ); } private ToolItem newToolBarButton( final ToolBar toolBar, final int style, final String iconName, final I18n toolTip, final SelectionListener selectionListener ) { final ToolItem item = new ToolItem( toolBar, style ); item.setImage( Util.image( iconName ) ); item.setToolTipText( toolTip.text() ); if ( selectionListener == null ) item.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( final SelectionEvent event ) { final Shell dialog = new Shell( getShell(), SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL ); dialog.setLayout( new RowLayout() ); final Label label = new Label( dialog, SWT.NONE ); label.setText( "Not yet implemented" ); dialog.pack(); final Rectangle itemBounds = getDisplay().map( toolBar, null, item.getBounds() ); dialog.setLocation( itemBounds.x, itemBounds.y ); dialog.open(); } } ); else item.addSelectionListener( selectionListener ); return item; } void removeColumnsAfter( final Column column ) { int ndx = columns.size() - 1; for ( Column col = columns.get( ndx ); col != column; col = columns.get( --ndx ) ) { col.header.dispose(); ( ( GridLayout ) headerBar.getLayout() ).numColumns--; col.pathButton.dispose(); focusTreeCanvas.removeColumn( col ); columns.remove( ndx ); } column.focusCellExpanded = false; headerBar.layout(); pathButtonBar.layout(); } /** * @return the root item for this tree */ public Object root() { return root; } void scrollPathBarLeft() { final Control[] pathButtons = pathButtonBar.getChildren(); // Show last hidden path button on left for ( int ndx = 0; ndx < pathButtons.length; ++ndx ) { Control pathButton = pathButtons[ ndx ]; if ( pathButton.isVisible() ) { // Show previous path button pathButton = pathButtons[ --ndx ]; pathButton.setVisible( true ); ( ( RowData ) pathButton.getLayoutData() ).exclude = false; break; } } // Hide last shown path button on right until all buttons fit in button bar final int width = pathButtonBar.getBounds().width; for ( int ndx = pathButtons.length; --ndx >= 0 && pathButtonBar.computeSize( SWT.DEFAULT, SWT.DEFAULT ).x > width; ) { final Control pathButton = pathButtons[ ndx ]; if ( pathButton.isVisible() ) { pathButton.setVisible( false ); ( ( RowData ) pathButton.getLayoutData() ).exclude = true; } } pathButtonBar.layout(); leftPathBarButton.setVisible( !pathButtons[ 0 ].isVisible() ); rightPathBarButton.setVisible( true ); } void scrollPathBarRight() { final Control[] pathButtons = pathButtonBar.getChildren(); // Show first hidden path button on right for ( int ndx = pathButtons.length; --ndx >= 0; ) { Control pathButton = pathButtons[ ndx ]; if ( pathButton.isVisible() ) { // Show next path button pathButton = pathButtons[ ++ndx ]; pathButton.setVisible( true ); ( ( RowData ) pathButton.getLayoutData() ).exclude = false; break; } } // Hide first shown path button on left until all buttons fit in button bar hideExcessiveLeftMostPathButtons(); } /** * @param model * the model for this tree */ public void setModel( final Model model ) { this.model = model; initialize(); } /** * @param root * the root item for this tree */ public void setRoot( final Object root ) { this.root = root; initialize(); } /** * @param viewModel * the view model for this tree */ public void setViewModel( final ViewModel viewModel ) { this.viewModel = viewModel == null ? new ViewModel() : viewModel; focusTreeCanvas.setViewModel(); initialize(); } void showColumn( final Column column, final Control childCount, final Control itemName, final Control hideButtonPanel, final Control showButton, final Color pathButtonForegroundColor, final Color pathButtonBackgroundColor ) { // Show column header ( ( GridData ) childCount.getLayoutData() ).exclude = false; childCount.setVisible( true ); ( ( GridData ) itemName.getLayoutData() ).exclude = false; itemName.setVisible( true ); ( ( GridData ) hideButtonPanel.getLayoutData() ).exclude = false; hideButtonPanel.setVisible( true ); ( ( GridData ) showButton.getLayoutData() ).exclude = true; showButton.setVisible( false ); // Change layout to use new preferred width ( ( GridLayout ) column.header.getLayout() ).numColumns = 3; ( ( GridData ) column.header.getLayoutData() ).widthHint = column.widthBeforeHiding; headerBar.layout(); // Show column's canvas figures focusTreeCanvas.showColumn( column, column.widthBeforeHiding ); column.pathButton.setBackground( pathButtonBackgroundColor ); column.pathButton.setForeground( pathButtonForegroundColor ); } void showIconView( final Column column ) { for ( final Control control : headerBar.getChildren() ) if ( control != column.header ) { ( ( GridData ) control.getLayoutData() ).exclude = true; control.setVisible( false ); } final GridData gridData = ( GridData ) column.header.getLayoutData(); gridData.grabExcessHorizontalSpace = true; gridData.horizontalAlignment = SWT.FILL; ( ( Control ) column.header.getData( HIDE_BUTTON_PROPERTY ) ).setVisible( false ); headerBar.layout(); } void toggleIconView( final Column column ) { if ( focusTreeCanvas.iconViewShown() ) { focusTreeCanvas.hideIconView( column ); hideIconView( column ); } else if ( column.header.getCursor() != getDisplay().getSystemCursor( SWT.CURSOR_SIZEWE ) ) { focusTreeCanvas.showIconView( column ); showIconView( column ); } } /** * @return the view model for this tree */ public ViewModel viewModel() { return viewModel; } class Column { Object item; Composite header; Label pathButton; Rectangle bounds = new Rectangle( 0, 0, 0, 0 ); CellColumn cellColumn; Cell focusCell; boolean focusCellExpanded; int cellPreferredWidth; int widthBeforeHiding; int cellWidthBeforeIconView; } private class ColumnHeaderMouseListener extends MouseAdapter implements MouseMoveListener, MouseTrackListener, DragDetectListener { private final Column column, previousColumn; private Column targetColumn; private boolean dragging; private int offset; ColumnHeaderMouseListener( final Column column, final Column previousColumn ) { this.column = column; this.previousColumn = previousColumn; } @Override public void dragDetected( final DragDetectEvent event ) { if ( targetColumn == null ) return; if ( targetColumn == column ) offset = column.header.getSize().x - event.x; else offset = -event.x; dragging = true; } @Override public void mouseDoubleClick( final MouseEvent event ) { if ( leftMouseButtonClicked( event ) && targetColumn != null ) { focusTreeCanvas.updateColumnWidth( targetColumn, targetColumn.cellPreferredWidth, true ); ( ( GridData ) targetColumn.header.getLayoutData() ).widthHint = targetColumn.bounds.width; headerBar.layout(); } } @Override public void mouseEnter( final MouseEvent event ) { if ( focusTreeCanvas.iconViewShown() ) return; if ( event.x >= column.header.getSize().x - 1 - HEADER_MARGIN ) setTargetColumn( column ); else if ( event.x <= HEADER_MARGIN && previousColumn != null ) setTargetColumn( previousColumn ); } @Override public void mouseExit( final MouseEvent event ) { if ( !dragging ) { column.header.setCursor( getDisplay().getSystemCursor( SWT.CURSOR_ARROW ) ); column.header.setToolTipText( null ); targetColumn = null; } } @Override public void mouseHover( final MouseEvent event ) {} @Override public void mouseMove( final MouseEvent event ) { if ( dragging ) { if ( targetColumn == previousColumn ) { final Point point = column.header.toDisplay( event.x, event.y ); event.x = previousColumn.header.toControl( point ).x; } final int width = event.x + offset; ( ( GridData ) targetColumn.header.getLayoutData() ).widthHint = width; headerBar.layout(); focusTreeCanvas.updateColumnWidth( targetColumn, width, true ); } } @Override public void mouseUp( final MouseEvent event ) { if ( !leftMouseButtonClicked( event ) ) return; if ( dragging ) { dragging = false; if ( getDisplay().getCursorControl() != column.header ) targetColumn = null; } else focusTreeCanvas.scrollToFocusLine(); } private void setTargetColumn( final Column targetColumn ) { this.targetColumn = targetColumn; column.header.setCursor( getDisplay().getSystemCursor( SWT.CURSOR_SIZEWE ) ); column.header.setToolTipText( EclipseI18n.focusTreeResizeColumnToolTip.text() ); } } /** * */ public static class Indicator { final Image image; final String toolTip; /** * @param image * the image for this indicator's button * @param toolTip * the tool tip for this indicator's button */ public Indicator( final Image image, final String toolTip ) { CheckArg.notNull( image, "image" ); CheckArg.notEmpty( toolTip, "toolTip" ); this.image = image; this.toolTip = toolTip; } /** * Does nothing by default. * * @param item * the item containing this indicator */ protected void selected( final Object item ) {} } /** * The default model for a {@link FocusTree} */ public static class Model { /** * */ public static final Indicator[] NO_INDICATORS = new Indicator[ 0 ]; /** * @param parent * a parent item in the tree * @param index * the index within the supplied parent where a new item is to be added * @return The newly added item. Must not be <code>null</code> unless {@link #childrenAddable(Object) children can not be * added to the supplied parent}. Default is <code>null</code> * @throws ChrysalixException * if an error occurs */ public Object add( final Object parent, final int index ) throws ChrysalixException { return true; } /** * @param item * an item in the tree * @return the number of children of the supplied item. Default is 0. * @throws ChrysalixException * if an error occurs */ public int childCount( final Object item ) throws ChrysalixException { return 0; } /** * @param item * an item in the tree * @return the children of the supplied item. Must not be <code>null</code>. Default is an empty array. * @throws ChrysalixException * if an error occurs */ public Object[] children( final Object item ) throws ChrysalixException { return Util.EMPTY_ARRAY; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item can be added. Default is <code>true</code> for collections and arrays. */ public boolean childrenAddable( final Object item ) { return item.getClass().isArray() || item instanceof Collection< ? >; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item can be deleted. Default is <code>false</code> */ public boolean deletable( final Object item ) { return false; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item was successfully deleted. Default is <code>true</code> */ public boolean delete( final Object item ) { return true; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item has children. Default is <code>false</code>, * @throws ChrysalixException * if an error occurs */ public boolean hasChildren( final Object item ) throws ChrysalixException { return false; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item has a name. Default is <code>true</code> * @throws ChrysalixException * if an error occurs */ public boolean hasName( final Object item ) throws ChrysalixException { return true; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item has a type. Default is <code>false</code> */ public boolean hasType( final Object item ) { return false; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item has a value. Default is <code>false</code> * @throws ChrysalixException * if an error occurs */ public boolean hasValue( final Object item ) throws ChrysalixException { return false; } /** * @param item * an item in the tree * @param parent * the parent of the supplied item * @return the supplied item's index, or <code>-1</code> if not found. Default is the item's index within * {@link #children(Object)}. * @throws ChrysalixException * if an error occurs */ public int indexOf( final Object item, final Object parent ) throws ChrysalixException { int ndx = 0; for ( final Object child : children( parent ) ) { if ( child.equals( item ) ) return ndx; ndx++; } return -1; } /** * @param item * an item in the tree * @return the status indicators applicable to the supplied item. Must not be <code>null</code>. Default is an empty array. */ public Indicator[] indicators( final Object item ) { return NO_INDICATORS; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item can be moved to a different index within its parent. Default is * <code>false</code> */ public boolean movable( final Object item ) { return false; } /** * @param item * an item in the tree * @return the name of the supplied item's cell. Must not be <code>null</code>. Default is the item's * {@link Object#toString()}. * @throws ChrysalixException * if an error occurs */ public Object name( final Object item ) throws ChrysalixException { return item.toString(); } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item's name can be edited. Default is <code>false</code> */ public boolean nameEditable( final Object item ) { return false; } /** * @param item * an item in the tree * @param name * a name for the supplied item * @return the problem message for the supplied name for the supplied item, or <code>null</code> if the name is valid. * Default is <code>null</code>. */ public String nameProblem( final Object item, final Object name ) { return null; } /** * @param item * an item in the tree * @return the fully-qualified name of the supplied item's cell. Must not be <code>null</code>. Default is the item's * {@link #name(Object) name} * @throws ChrysalixException * if an error occurs */ public Object qualifiedName( final Object item ) throws ChrysalixException { return name( item ); } /** * If an item is {@link #movable(Object) movable}, called after a user changes the supplied item's index to the supplied * index. Does nothing by default. * * @param item * an item in the tree * @param parent * the parent of the supplied item * @param index * an index for the supplied item * @return an item with the supplied index. Default is the supplied item. * @throws ChrysalixException * if an error occurs */ public Object setIndex( final Object item, final Object parent, final int index ) throws ChrysalixException { return item; } /** * Called after a user changes the supplied item's name to the supplied name. Does nothing by default. * * @param item * an item in the tree * @param name * a name for the supplied item * @return an item with the supplied name. Must not be <code>null</code>. Default is the supplied item. * @throws ChrysalixException * if an error occurs */ public Object setName( final Object item, final Object name ) throws ChrysalixException { return item; } /** * Called after a user changes the supplied item's type to the supplied type. Does nothing by default. * * @param item * an item in the tree * @param type * a type for the supplied item * @return an item with the supplied type. Must not be <code>null</code>. Default is the supplied item. * @throws ChrysalixException * if an error occurs */ public Object setType( final Object item, final Object type ) throws ChrysalixException { return item; } /** * Called after a user changes the supplied item's value to the supplied value. Does nothing by default. * * @param item * an item in the tree * @param value * a value for the supplied item * @return an item with the supplied value. Default is the supplied item. * @throws ChrysalixException * if an error occurs */ public Object setValue( final Object item, final Object value ) throws ChrysalixException { return item; } /** * @param item * an item in the tree * @return the type of the supplied item's cell. Default is the item's simple class name. * @throws ChrysalixException * if an error occurs */ public Object type( final Object item ) throws ChrysalixException { return item.getClass().getSimpleName(); } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item's type can be edited. Default is <code>false</code> */ public boolean typeEditable( final Object item ) { return false; } /** * @param item * an item in the tree * @param type * a type for the supplied item * @return the problem message for the supplied type for the supplied item, or <code>null</code> if the type is valid. * Default is <code>null</code>. */ public String typeProblem( final Object item, final Object type ) { return null; } /** * @param item * an item in the tree * @return the value of the supplied item's cell. Default is <code>null</code>. * @throws ChrysalixException * if an error occurs */ public Object value( final Object item ) throws ChrysalixException { return null; } /** * @param item * an item in the tree * @return <code>true</code> if the supplied item's value can be edited. Default is <code>false</code> */ public boolean valueEditable( final Object item ) { return false; } /** * @param item * an item in the tree * @param value * a value for the supplied item * @return the problem message for the supplied value for the supplied item, or <code>null</code> if the value is valid. * Default is <code>null</code>. */ public String valueProblem( final Object item, final Object value ) { return null; } } /** * The default view model for a {@link FocusTree} */ public static class ViewModel { /** * */ public static final Color DEFAULT_CELL_BACKGROUND_COLOR = Display.getDefault().getSystemColor( SWT.COLOR_WHITE ); /** * */ public static final Color DEFAULT_CHILD_INDEX_COLOR = new Color( null, 0, 128, 255 ); /** * */ public static final TextCellEditor DEFAULT_EDITOR = new TextCellEditor(); /** * */ public static final Color DEFAULT_FOCUS_CELL_BORDER_COLOR = DEFAULT_CHILD_INDEX_COLOR; /** * */ public static final Color DEFAULT_FOCUS_LINE_COLOR = DEFAULT_CHILD_INDEX_COLOR; /** * */ public static final int DEFAULT_FOCUS_LINE_HEIGHT = 5; /** * */ public static final int DEFAULT_FOCUS_LINE_OFFSET = 100; /** * */ public static final Color DEFAULT_PATH_BUTTON_MINIMIZED_BACKGROUND_COLOR = Display.getDefault().getSystemColor( SWT.COLOR_GRAY ); /** * */ public static final Color DEFAULT_TREE_BACKGROUND_COLOR = Display.getDefault().getSystemColor( SWT.COLOR_WHITE ); static { DEFAULT_EDITOR.setStyle( SWT.BORDER ); } /** * @param item * an item in the tree * @return the background color of the supplied item's cell. Default is {link #DEFAULT_CELL_BACKGROUND_COLOR}. */ public Color cellBackgroundColor( final Object item ) { return DEFAULT_CELL_BACKGROUND_COLOR; } /** * @param item * an item in the tree * @return the foreground color of the supplied item's cell. Must not be <code>null</code>. Default is white or black, * whichever contrasts more with the {@link #cellBackgroundColor(Object) cell's background color}. */ public Color cellForegroundColor( final Object item ) { final Color color = cellBackgroundColor( item ); final double yiq = ( ( color.getRed() * 299 ) + ( color.getGreen() * 587 ) + ( color.getBlue() * 114 ) ) / 1000.0; return yiq >= 128.0 ? Display.getCurrent().getSystemColor( SWT.COLOR_BLACK ) : Display.getCurrent().getSystemColor( SWT.COLOR_WHITE ); } /** * @param item * an item in the tree * @return the color of the child index shown in the supplied item's cell. Must not be <code>null</code>. Default is * {@link #DEFAULT_CHILD_INDEX_COLOR}. */ public Color childIndexColor( final Object item ) { return DEFAULT_CHILD_INDEX_COLOR; } /** * @param item * an item in the tree * @return Creates a cell for the supplied item. Must not be <code>null</code>. Default is a {@link RoundedRectangle}. */ public IFigure createCell( final Object item ) { return new RoundedRectangle(); } /** * @return the border color of focus cells. Default is {@link #DEFAULT_FOCUS_CELL_BORDER_COLOR}. */ public Color focusCellBorderColor() { return DEFAULT_FOCUS_CELL_BORDER_COLOR; } /** * @return the color of the focus line. Default is {@link #DEFAULT_FOCUS_LINE_COLOR}. */ public Color focusLineColor() { return DEFAULT_FOCUS_LINE_COLOR; } /** * @return the height of the focus line. Default is {@value #DEFAULT_FOCUS_LINE_HEIGHT}. */ public int focusLineHeight() { return DEFAULT_FOCUS_LINE_HEIGHT; } /** * @return the initial offset of the focus line. Default is {@value #DEFAULT_FOCUS_LINE_OFFSET}. */ public int focusLineOffset() { return DEFAULT_FOCUS_LINE_OFFSET; } /** * @param item * an item in the tree * @return the icon of the cell for the supplied item. Default is <code>null</code>. */ public Image icon( final Object item ) { return null; } /** * @return the width of a cells in the icon view. Default is {@value SWT#DEFAULT}, indicating to use the largest preferred * width of all cells. */ public int iconViewCellWidth() { return SWT.DEFAULT; } /** * @param item * an item in the tree * @return the icon of the cell for the supplied item in an icon view. Default is <code>null</code>. */ public Image iconViewIcon( final Object item ) { return null; } /** * @param item * an item in the tree * @return the cell editor used to edit the supplied item's index. Must not be <code>null</code>. Default is {link * {@link TextCellEditor} . */ public CellEditor indexEditor( final Object item ) { return DEFAULT_EDITOR; } /** * @return the initial width of a cell. Default is {@value SWT#DEFAULT}, indicating to use the largest preferred width of * all cells in a column. */ public int initialCellWidth() { return SWT.DEFAULT; } /** * @return <code>true</code> if the initial index shown in columns with multiple cells is one. Default is <code>false</code> * . */ public boolean initialIndexIsOne() { return false; } /** * @param item * an item in the tree * @return the cell editor used to edit the supplied item's name. Must not be <code>null</code>. Default is {link * {@link TextCellEditor} . */ public CellEditor nameEditor( final Object item ) { return DEFAULT_EDITOR; } /** * @param item * an item in the tree * @return the background color of the supplied item's path button. Must not be <code>null</code>. Default is * {@link #cellBackgroundColor(Object) cell's background color}. */ public Color pathButtonBackgroundColor( final Object item ) { return cellBackgroundColor( item ); } /** * @param item * an item in the tree * @return the foreground color of the supplied item's cell. Must not be <code>null</code>. Default is * {@link #cellForegroundColor(Object) cell's foreground color}. */ public Color pathButtonForegroundColor( final Object item ) { return cellForegroundColor( item ); } /** * @param item * an item in the tree * @return the background color of the supplied item's path button if its column is minimized. Must not be <code>null</code> * . Default is {@link #DEFAULT_PATH_BUTTON_MINIMIZED_BACKGROUND_COLOR}. */ public Color pathButtonHiddenBackgroundColor( final Object item ) { return DEFAULT_PATH_BUTTON_MINIMIZED_BACKGROUND_COLOR; } /** * @param item * an item in the tree * @return the foreground color of the supplied item's path button if its column is minimized. Must not be <code>null</code> * . Default is {@link #cellForegroundColor(Object) cell's foreground color}. */ public Color pathButtonHiddenForegroundColor( final Object item ) { return cellForegroundColor( item ); } /** * @return the background color of the {@link FocusTree focus tree}. Default is {@link #DEFAULT_TREE_BACKGROUND_COLOR}. */ public Color treeBackgroundColor() { return DEFAULT_TREE_BACKGROUND_COLOR; } /** * @param item * an item in the tree * @return the cell editor used to edit the supplied item's type. Must not be <code>null</code>. Default is {link * {@link TextCellEditor} . */ public CellEditor typeEditor( final Object item ) { return DEFAULT_EDITOR; } /** * @param item * an item in the tree * @return the cell editor used to edit the supplied item's value. Must not be <code>null</code>. Default is * {@link TextCellEditor} . */ public CellEditor valueEditor( final Object item ) { return DEFAULT_EDITOR; } } }