/*******************************************************************************
* Copyright (c) 2009, 2015 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.internal.design.example.managers;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.internal.provisional.action.CoolBarManager2;
import org.eclipse.jface.internal.provisional.action.IToolBarContributionItem;
import org.eclipse.rap.internal.design.example.ILayoutSetConstants;
import org.eclipse.rap.internal.design.example.builder.CoolbarLayerBuilder;
import org.eclipse.rap.internal.design.example.builder.DummyBuilder;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.ui.interactiondesign.layout.ElementBuilder;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Button;
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.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.menus.CommandContributionItem;
public class CoolBarManager extends CoolBarManager2 {
private static final String HEADER_TOOLBAR_VARIANT = "header-toolbar"; //$NON-NLS-1$
private static final String HEADER_OVERFLOW_VARIANT = "header-overflow"; //$NON-NLS-1$
private static final String ACTIVE = "toolbarOverflowActive"; //$NON-NLS-1$
private static final String INACTIVE = "toolbarOverflowInactive"; //$NON-NLS-1$
private static final int WAVE_SPACING = 20;
private Composite overflowParent;
private Image preservedWave;
private ToolBar toolbar;
private final List overflowItems = new ArrayList();
private Button overflowOpenButton;
private Button overflowCloseButton;
private Image newWave;
private Composite overflowLayer;
private final ElementBuilder dummyBuilder;
private ToolBar overflowToolbar;
private ScrolledComposite overflowToolbarParent;
private final FocusListener focusListener = new FocusAdapter() {
@Override
public void focusLost( FocusEvent event ) {
// close the overflow if the toolbar focus is lost
closeOverflow();
toggleImages();
}
};
/*
* Class for accessing a pull down item's menu to set a custom variant.
*/
private class StylingSelectionAdapter extends SelectionAdapter {
private final String variant;
public StylingSelectionAdapter( final String variant ) {
this.variant = variant;
}
private void styleMenuItems( final Menu menu ) {
MenuItem[] items = menu.getItems();
if( items != null && items.length > 0 && variant != null ) {
for( int i = 0; i < items.length; i++ ) {
items[ i ].setData( RWT.CUSTOM_VARIANT, variant );
}
}
}
@Override
public void widgetSelected( final SelectionEvent e ) {
Widget widget = e.widget;
if( widget != null
&& widget instanceof ToolItem
&& !widget.isDisposed() )
{
if( widget.getData( RWT.CUSTOM_VARIANT ) != null ) {
IContributionItem item = ( IContributionItem ) widget.getData();
if( item instanceof CommandContributionItem ) {
CommandContributionItem commandItem
= ( CommandContributionItem ) item;
MenuManager manager = commandItem.getMenuManager();
if( manager != null ) {
Menu menu = manager.getMenu();
if( menu != null ) {
menu.setData( RWT.CUSTOM_VARIANT, variant );
styleMenuItems( menu );
}
}
} else if( item instanceof ActionContributionItem ) {
ActionContributionItem actionItem = ( ActionContributionItem ) item;
IAction action = actionItem.getAction();
IMenuCreator menuCreator = action.getMenuCreator();
if( menuCreator != null ) {
Menu menu = menuCreator.getMenu( toolbar );
if( menu != null ) {
menu.setData( RWT.CUSTOM_VARIANT, variant );
styleMenuItems( menu );
}
}
}
}
}
}
}
public CoolBarManager() {
dummyBuilder
= new DummyBuilder( null, ILayoutSetConstants.SET_ID_COOLBAR );
}
@Override
public Control createControl2( final Composite parent ) {
toolbar = new ToolBar( parent, SWT.RIGHT );
toolbar.setData( RWT.CUSTOM_VARIANT, HEADER_TOOLBAR_VARIANT );
toolbar.getParent().getParent().addControlListener( new ControlAdapter() {
@Override
public void controlResized( final ControlEvent e ) {
// close the overflow and update the ToolBar if the browser has resized
closeOverflow();
update( true );
}
} );
return toolbar;
}
@Override
public Control getControl2() {
return toolbar;
}
@Override
public void update( final boolean force ) {
if( ( isDirty() || force ) && getControl2() != null ) {
refresh();
boolean changed = false;
/*
* Make a list of items including only those items that are
* visible. Separators are being removed. Because we use only one Toolbar
* all ToolBarContributionItems will be extracted in their IContribution
* Items.
*/
final IContributionItem[] items = getItems();
final List visibleItems = new ArrayList( items.length );
for( int i = 0; i < items.length; i++ ) {
final IContributionItem item = items[i];
if( item.isVisible() ) {
if( item instanceof IToolBarContributionItem ) {
IToolBarContributionItem toolbarItem
= ( IToolBarContributionItem ) item;
IToolBarManager toolBarManager = toolbarItem.getToolBarManager();
IContributionItem[] toolbarItems = toolBarManager.getItems();
for( int j = 0; j < toolbarItems.length; j++ ) {
final IContributionItem toolItem = toolbarItems[ j ];
if( toolItem.isVisible() && !toolItem.isSeparator() ) {
visibleItems.add( toolItem );
}
}
}
}
}
/*
* Make a list of ToolItem widgets in the tool bar for which there
* is no current visible contribution item. These are the widgets
* to be disposed. Dynamic items are also removed.
*/
ToolItem[] toolItems = toolbar.getItems();
final ArrayList toolItemsToRemove = new ArrayList( toolItems.length );
for( int i = 0; i < toolItems.length; i++ ) {
final Object data = toolItems[i].getData();
if( ( data == null )
|| ( !visibleItems.contains( data ) )
|| ( ( data instanceof IContributionItem )
&& ( ( IContributionItem ) data ).isDynamic() ) ) {
toolItemsToRemove.add( toolItems[i] );
}
}
// Dispose of any items in the list to be removed.
for( int i = toolItemsToRemove.size() - 1; i >= 0; i-- ) {
ToolItem toolItem = ( ToolItem ) toolItemsToRemove.get(i);
if( !toolItem.isDisposed() ) {
Control control = toolItem.getControl();
if( control != null ) {
toolItem.setControl( null );
control.dispose();
}
toolItem.dispose();
}
}
// Add any new items by telling them to fill.
toolItems = toolbar.getItems();
IContributionItem sourceItem;
IContributionItem destinationItem;
int sourceIndex = 0;
int destinationIndex = 0;
final Iterator visibleItemItr = visibleItems.iterator();
while( visibleItemItr.hasNext() ) {
sourceItem = ( IContributionItem ) visibleItemItr.next();
// Retrieve the corresponding contribution item from SWT's
// data.
if( sourceIndex < toolItems.length ) {
destinationItem
= ( IContributionItem ) toolItems[ sourceIndex ].getData();
} else {
destinationItem = null;
}
// The items match if they are equal or both separators.
if( destinationItem != null ) {
if( sourceItem.equals( destinationItem ) ) {
sourceIndex++;
destinationIndex++;
sourceItem.update();
continue;
} else if( ( destinationItem.isSeparator() )
&& ( sourceItem.isSeparator() ) ) {
toolItems[ sourceIndex ].setData( sourceItem );
sourceIndex++;
destinationIndex++;
sourceItem.update();
continue;
}
}
// Otherwise, a new item has to be added.
final int start = toolbar.getItemCount();
sourceItem.fill( toolbar, destinationIndex );
final int newItems = toolbar.getItemCount() - start;
// add the selection listener for the styling
StylingSelectionAdapter listener
= new StylingSelectionAdapter( HEADER_TOOLBAR_VARIANT );
for( int i = 0; i < newItems; i++ ) {
ToolItem item = toolbar.getItem( destinationIndex++ );
item.setData( sourceItem );
item.addSelectionListener( listener );
}
changed = true;
}
// Remove any old widgets not accounted for.
for( int i = toolItems.length - 1; i >= sourceIndex; i-- ) {
final ToolItem item = toolItems[ i ];
if( !item.isDisposed() ) {
Control control = item.getControl();
if( control != null ) {
item.setControl( null );
control.dispose();
}
item.dispose();
changed = true;
}
}
// Update wrap indices. only needed by a coolbar
//updateWrapIndices();
// Update the sizes.
for( int i = 0; i < items.length; i++ ) {
IContributionItem item = items[ i ];
item.update( SIZE );
}
if (changed) {
updateToolbarTabOrder();
}
// We are no longer dirty.
setDirty( false );
styleToolItems();
toolbar.pack();
toolbar.layout( true, true );
manageOverflow();
}
}
/*
* This method manages the items which can not be shown in the coolbar because
* it is to small. So an overflow will be shown including these items.
*/
private void manageOverflow() {
int coolbarWidth = toolbar.getParent().getSize().x - WAVE_SPACING;
int childrenLength = toolbar.getItemCount() - 1;
overflowItems.clear();
for( int i = childrenLength; i >= 0; i-- ) {
int childrenSize = getChildrenSize( toolbar );
if( childrenSize > coolbarWidth ) {
ToolItem toolItem = toolbar.getItem( i );
IContributionItem item = ( IContributionItem ) toolItem.getData();
addOverflowItem( item );
activateOverflowOpenButton();
Control control = toolItem.getControl();
toolItem.setControl( null );
if( control != null ) {
control.dispose();
}
toolItem.dispose();
}
}
// check if the overflow button should be activated or not
checkOverflowActivation();
}
private void checkOverflowActivation() {
// If every item has a representation in the toolbar, the overflow button
// should be invisible
if( overflowItems.size() > 0 ) {
activateOverflowOpenButton();
} else {
deactivateOverflowButton();
}
}
private void addOverflowItem( final IContributionItem item ) {
// add the contrib item to the overflow items if it's not allready in
if( !overflowItems.contains( item ) ) {
overflowItems.add( item );
}
}
private void deactivateOverflowButton() {
if( overflowOpenButton != null ) {
overflowOpenButton.setVisible( false );
}
}
/*
* This method calculates the size of all children of the coolbar. This is
* necessary to compare the correct sizes for the overflow.
*/
private int getChildrenSize( final ToolBar toolbar ) {
int result = 0;
FormData spacing
= dummyBuilder.getPosition( ILayoutSetConstants.COOLBAR_SPACING );
if( spacing != null ) {
ToolItem[] items = toolbar.getItems();
for( int i = 0; i < items.length; i++ ) {
result += items[ i ].getWidth() + spacing.width;
}
}
return result;
}
/*
* Creates and activates the overflow button
*/
private void activateOverflowOpenButton() {
if( overflowParent != null && overflowOpenButton == null ) {
overflowOpenButton = new Button( overflowParent, SWT.PUSH );
overflowOpenButton.setData( RWT.CUSTOM_VARIANT, INACTIVE );
overflowOpenButton.setLayoutData( getOverflowButtonLayoutData() );
overflowOpenButton.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( final SelectionEvent e ) {
// open the overflow and toggle the chefron icon
createOverflowLayer();
toggleImages();
}
} );
}
overflowOpenButton.setVisible( true );
// create the close button
if( overflowCloseButton == null ) {
overflowCloseButton = new Button( overflowParent, SWT.PUSH );
overflowCloseButton.setData( RWT.CUSTOM_VARIANT, ACTIVE );
overflowCloseButton.setLayoutData( getOverflowButtonLayoutData() );
overflowCloseButton.addSelectionListener( new SelectionAdapter() {
@Override
public void widgetSelected( final SelectionEvent e ) {
closeOverflow();
toggleImages();
};
} );
}
overflowCloseButton.setVisible( false );
}
/*
* Change the images, this includes the chefron icon and the wave image
*/
private void toggleImages() {
Image wave = null;
if( overflowOpenButton.isVisible() ) {
// The button was inactive so active it
overflowOpenButton.setVisible( false );
overflowCloseButton.setVisible( true );
wave = newWave;
overflowLayer.getParent().setVisible( true );
overflowLayer.setFocus();
} else {
overflowCloseButton.setVisible( false );
overflowOpenButton.setVisible( true );
overflowLayer.getParent().setVisible( false );
wave = preservedWave;
}
overflowParent.setBackgroundImage( wave );
}
private FormData getOverflowButtonLayoutData() {
String imageId = ILayoutSetConstants.COOLBAR_OVERFLOW_ACTIVE;
Image image = dummyBuilder.getImage( imageId );
FormData fdOverFlowButton
= dummyBuilder.getPosition( ILayoutSetConstants.COOLBAR_BUTTON_POS );
if( image != null ) {
fdOverFlowButton.width = image.getBounds().width;
fdOverFlowButton.height = image.getBounds().height;
}
return fdOverFlowButton;
}
private void createOverflowLayer() {
ElementBuilder layerBuilder
= new CoolbarLayerBuilder( overflowParent.getParent(),
ILayoutSetConstants.SET_ID_OVERFLOW );
if( overflowLayer == null ) {
layerBuilder.build();
overflowLayer = ( Composite ) layerBuilder.getControl();
overflowLayer.addFocusListener( focusListener );
newWave = layerBuilder.getImage( ILayoutSetConstants.OVERFLOW_WAVE );
}
Object adapter = layerBuilder.getAdapter( CoolBarManager.class );
if( adapter != null ) {
// position the layer
FormData fdLayer = ( FormData ) overflowLayer.getParent().getLayoutData();
Display display = overflowLayer.getDisplay();
Point location = display.map( overflowOpenButton, null, 20, 0 );
fdLayer.left = new FormAttachment( 0, location.x);
fdLayer.top = new FormAttachment( 0, 37 );
overflowParent.getParent().getParent().layout( true );
} else {
FormData fdParent = ( FormData ) overflowParent.getLayoutData();
FormData fdLayer = ( FormData ) overflowLayer.getParent().getLayoutData();
fdLayer.left = fdParent.left;
}
// fill the vertical overflow toolbar with the overflow items
fillOverflowToolbar();
overflowParent.getParent().layout( true );
overflowLayer.getParent().moveAbove( null );
overflowLayer.getParent().moveBelow( overflowParent );
}
private void closeOverflow( ) {
if( overflowLayer != null && preservedWave != null ) {
boolean opened = overflowLayer.getParent().isVisible();
if( opened ) {
overflowLayer.getParent().setVisible( false );
overflowParent.setBackgroundImage( preservedWave );
overflowOpenButton.setData( RWT.CUSTOM_VARIANT, INACTIVE );
clearOverflowToolbar();
}
}
}
/*
* Dispose all Items in the overflow
*/
private void clearOverflowToolbar() {
if( overflowToolbar != null ) {
ToolItem[] items = overflowToolbar.getItems();
for( int i = 0; i < items.length; i++ ) {
ToolItem toolItem = items[ i ];
if( toolItem != null && !toolItem.isDisposed() ) {
toolItem.setData( null );
toolItem.dispose();
}
}
}
}
/*
* Take all overflow items and fill the vertical overflow toolbar.
*/
private void fillOverflowToolbar() {
if( overflowToolbar == null ) {
// scrolled toolbar parent
overflowToolbarParent
= new ScrolledComposite( overflowLayer, SWT.V_SCROLL );
DummyBuilder builder
= new DummyBuilder( null, ILayoutSetConstants.SET_ID_OVERFLOW );
FormData pos = builder.getPosition( ILayoutSetConstants.OVERFLOW_POS );
overflowToolbarParent.setLayoutData( pos );
// parent for the toolbar
Composite parent = new Composite( overflowToolbarParent, SWT.NONE );
parent.setLayout( new FillLayout() );
// toolbar
overflowToolbar = new ToolBar( parent, SWT.VERTICAL );
overflowToolbar.setBackgroundMode( SWT.INHERIT_FORCE );
overflowToolbar.setData( RWT.CUSTOM_VARIANT,
HEADER_OVERFLOW_VARIANT );
overflowLayer.getParent().addFocusListener( focusListener );
// configure the ScrolledComposite
overflowToolbarParent.setContent( parent );
overflowToolbarParent.setExpandVertical( true );
overflowToolbarParent.setExpandHorizontal( true );
overflowToolbarParent.setOrigin( 0, 0 );
overflowToolbarParent.setAlwaysShowScrollBars( false );
}
// clear the old overflow if items exist
clearOverflowToolbar();
// fill the toolbar
int maxWidth = 0;
for( int i = 0; i < overflowItems.size(); i++ ) {
IContributionItem item = ( IContributionItem ) overflowItems.get( i );
item.fill( overflowToolbar, i );
final ToolItem toolItem = overflowToolbar.getItem( i );
// add a selection listener for the styling
StylingSelectionAdapter listener
= new StylingSelectionAdapter( HEADER_OVERFLOW_VARIANT );
toolItem.addSelectionListener( listener );
toolItem.setData( RWT.CUSTOM_VARIANT, HEADER_OVERFLOW_VARIANT );
if( toolItem.getWidth() > maxWidth ) {
maxWidth = toolItem.getWidth();
}
}
// layout the controls
overflowLayer.getParent().layout( true, true );
overflowLayer.getParent().pack( true );
overflowToolbarParent.setMinSize( maxWidth, overflowItems.size() * 25 );
// bring the scroll position back to it's origin every time the overflow
// has opened
overflowToolbarParent.setOrigin( 0, 0 );
overflowToolbarParent.layout();
overflowToolbarParent.setFocus();
}
private void styleToolItems() {
if( toolbar != null ) {
ToolItem[] items = toolbar.getItems();
for( int i = 0; i < items.length; i++ ) {
ToolItem toolItem = items[ i ];
final IContributionItem item = ( IContributionItem ) toolItem.getData();
if( toolItem.getText() == "" ) { //$NON-NLS-1$
modifyModeForceText( item );
}
toolItem.setData( RWT.CUSTOM_VARIANT, HEADER_TOOLBAR_VARIANT );
}
}
}
/*
* This method changes the modes from ActionContributionItems and
* CommandContributionItems to display the text within a ToolItem.
*/
private void modifyModeForceText( final IContributionItem item ) {
if( item instanceof ActionContributionItem ) {
ActionContributionItem actionItem = ( ActionContributionItem ) item;
actionItem.setMode( ActionContributionItem.MODE_FORCE_TEXT );
} else if( item instanceof CommandContributionItem ) {
CommandContributionItem commandItem = ( CommandContributionItem ) item;
commandItem.setMode( CommandContributionItem.MODE_FORCE_TEXT );
}
}
private void updateToolbarTabOrder() {
if( toolbar != null ) {
ToolItem[] items = toolbar.getItems();
if( items != null ) {
ArrayList children = new ArrayList( items.length );
for( int i = 0; i < items.length; i++ ) {
if( ( items[ i ].getControl() != null )
&& ( !items[ i ].getControl().isDisposed() ) )
{
children.add( items[ i ].getControl() );
}
}
// Convert array
Control[] childrenArray = new Control[ 0 ];
childrenArray = ( Control[] ) children.toArray( childrenArray );
if( childrenArray != null ) {
toolbar.setTabList( childrenArray );
}
}
}
}
/*
* Method to set the parent for the overflow. This method is called within
* the WindowComposers.
*/
public void setOverflowParent( final Composite overflowParent ) {
this.overflowParent = overflowParent;
preservedWave = overflowParent.getBackgroundImage();
}
}