/****************************************************************************** * Copyright (c) 2016 Oracle and Other Contributors * 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 * Greg Amerson - [342771] Support "image+label" hint for when actions are presented in a toolbar ******************************************************************************/ package org.eclipse.sapphire.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.sapphire.DisposeEvent; import org.eclipse.sapphire.Event; import org.eclipse.sapphire.Listener; import org.eclipse.sapphire.LoggingService; import org.eclipse.sapphire.Sapphire; import org.eclipse.sapphire.modeling.el.Function; import org.eclipse.sapphire.modeling.el.FunctionContext; import org.eclipse.sapphire.modeling.el.FunctionResult; import org.eclipse.sapphire.modeling.el.Literal; import org.eclipse.sapphire.modeling.el.ModelElementFunctionContext; import org.eclipse.sapphire.modeling.localization.LocalizationService; import org.eclipse.sapphire.ui.def.ActionDef; import org.eclipse.sapphire.ui.def.ISapphireHint; import org.eclipse.sapphire.ui.def.KeyBindingBehavior; import org.eclipse.sapphire.ui.def.PartDef; import org.eclipse.sapphire.ui.def.SapphireActionType; import org.eclipse.sapphire.ui.def.SapphireKeySequence; import org.eclipse.sapphire.ui.util.TopologicalSorter; import org.eclipse.sapphire.util.ListFactory; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class SapphireAction extends SapphireActionSystemPart { private SapphireActionGroup parent; private SapphireActionType type; private String group; private SapphireKeySequence keyBinding; private KeyBindingBehavior keyBindingBehavior; private final List<SapphireActionHandler> handlers = new CopyOnWriteArrayList<SapphireActionHandler>(); private final Map<SapphireActionHandlerFactory,List<SapphireActionHandler>> handlerFactories = new LinkedHashMap<SapphireActionHandlerFactory,List<SapphireActionHandler>>(); private final List<SapphireActionHandlerFilter> filters = new CopyOnWriteArrayList<SapphireActionHandlerFilter>(); private Listener handlerListener; private Map<String,Object> hints; public void init( final SapphireActionGroup parent, final ActionDef def ) { this.parent = parent; super.init( def ); this.handlerListener = new Listener() { @Override public void handle( final Event event ) { if( event instanceof VisibilityEvent ) { refreshVisibilityState(); } else if( event instanceof EnablementChangedEvent ) { refreshEnablementState(); } else if( event instanceof CheckedStateChangedEvent ) { refreshCheckedState(); } } }; attach ( new Listener() { @Override public void handle( final Event event ) { if( event instanceof HandlersChangedEvent || event instanceof FiltersChangedEvent ) { refreshVisibilityState(); refreshEnablementState(); refreshCheckedState(); } else if( event instanceof DisposeEvent ) { for( SapphireActionHandler handler : SapphireAction.this.handlers ) { try { handler.dispose(); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } for( SapphireActionHandlerFactory factory : SapphireAction.this.handlerFactories.keySet() ) { try { factory.dispose(); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } } } } ); this.hints = new HashMap<String,Object>(); if( def != null ) { this.type = def.getType().content(); this.group = def.getGroup().content(); this.keyBinding = def.getKeyBinding().content(); this.keyBindingBehavior = def.getKeyBindingBehavior().content(); for( ISapphireHint hint : def.getHints() ) { final String name = hint.getName().text(); Object parsedValue = null; if( name.equals( PartDef.HINT_STYLE ) ) { parsedValue = hint.getValue().text(); } this.hints.put( name, parsedValue ); } } setEnabled( false ); } @Override protected FunctionContext initFunctionContext() { final ISapphirePart part = getPart(); return new ModelElementFunctionContext( part.getLocalModelElement(), part.definition().adapt( LocalizationService.class ) ) { @Override public FunctionResult property( final Object element, final String name ) { if( element == this && name.equalsIgnoreCase( "handlers" ) ) { final Function f = new Function() { @Override public String name() { return "ReadProperty"; } @Override public FunctionResult evaluate( final FunctionContext context ) { return new FunctionResult( this, context ) { private Listener listener; @Override protected void init() { super.init(); this.listener = new Listener() { @Override public void handle( final Event event ) { if( event instanceof HandlersChangedEvent ) { refresh(); } } }; SapphireAction.this.attach( this.listener ); } @Override protected Object evaluate() { return getActiveHandlers(); } @Override public void dispose() { super.dispose(); SapphireAction.this.detach( this.listener ); } }; } }; f.init(); return f.evaluate( this ); } else if( element instanceof SapphireActionHandler && name.equalsIgnoreCase( "label" ) ) { return Literal.create( ( (SapphireActionHandler) element ).getLabel() ).evaluate( this ); } return super.property( element, name ); } }; } public SapphireActionGroup getActionSet() { return this.parent; } public final ISapphirePart getPart() { return this.parent.getPart(); } public String getContext() { return this.parent.getContext(); } @SuppressWarnings( "unchecked" ) public <T> T getRenderingHint( final String name, final T defaultValue ) { final Object hintValue = this.hints == null ? null : this.hints.get( name ); return hintValue == null ? defaultValue : (T) hintValue; } public SapphireActionType getType() { synchronized( this ) { return this.type; } } public void setType( final SapphireActionType type ) { synchronized( this ) { this.type = type; } broadcast( new TypeChangedEvent() ); } public String getGroup() { synchronized( this ) { return this.group; } } public void setGroup( final String group ) { synchronized( this ) { this.group = group; } broadcast( new GroupChangedEvent() ); } public SapphireKeySequence getKeyBinding() { synchronized( this ) { return this.keyBinding; } } public void setKeyBinding( final SapphireKeySequence keyBinding ) { synchronized( this ) { this.keyBinding = keyBinding; } broadcast( new KeyBindingChangedEvent() ); } public KeyBindingBehavior getKeyBindingBehavior() { synchronized( this ) { return this.keyBindingBehavior; } } public void setKeyBindingBehavior( final KeyBindingBehavior keyBindingBehavior ) { synchronized( this ) { this.keyBindingBehavior = keyBindingBehavior; } broadcast( new KeyBindingBehaviorChangedEvent() ); } public void addHandler( final SapphireActionHandler handler ) { handler.attach( this.handlerListener ); this.handlers.add( handler ); broadcast( new HandlersChangedEvent() ); } public void removeHandler( final SapphireActionHandler handler ) { handler.detach( this.handlerListener ); this.handlers.remove( handler ); broadcast( new HandlersChangedEvent() ); } public void removeHandlers( final Collection<SapphireActionHandler> handlers ) { for( SapphireActionHandler handler : handlers ) { handler.detach( this.handlerListener ); this.handlers.remove( handler ); } broadcast( new HandlersChangedEvent() ); } public void addHandlerFactory( final SapphireActionHandlerFactory factory ) { synchronized( this ) { if( this.handlerFactories.containsKey( factory ) ) { throw new IllegalArgumentException(); } final List<SapphireActionHandler> handlers = new ArrayList<SapphireActionHandler>(); for( SapphireActionHandler handler : factory.create() ) { handler.init( this, null ); handler.attach( this.handlerListener ); handlers.add( handler ); } factory.attach ( new Listener() { @Override public void handle( final Event event ) { refreshHandlerFactory( factory ); } } ); this.handlerFactories.put( factory, handlers ); } broadcast( new HandlersChangedEvent() ); } public void removeHandlerFactory( final SapphireActionHandlerFactory factory ) { boolean changed = false; synchronized( this ) { final List<SapphireActionHandler> handlers = this.handlerFactories.remove( factory ); if( handlers != null ) { for( SapphireActionHandler handler : handlers ) { try { handler.dispose(); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } changed = true; } } if( changed ) { broadcast( new HandlersChangedEvent() ); } } private void refreshHandlerFactory( final SapphireActionHandlerFactory factory ) { synchronized( this ) { final List<SapphireActionHandler> handlers = this.handlerFactories.get( factory ); if( handlers == null ) { throw new IllegalStateException(); } for( SapphireActionHandler handler : handlers ) { try { handler.dispose(); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } handlers.clear(); for( SapphireActionHandler handler : factory.create() ) { handler.init( this, null ); handler.attach( this.handlerListener ); handlers.add( handler ); } } broadcast( new HandlersChangedEvent() ); } public List<SapphireActionHandler> getActiveHandlers() { synchronized( this ) { final List<SapphireActionHandler> handlers = new ArrayList<SapphireActionHandler>(); handlers.addAll( this.handlers ); for( List<SapphireActionHandler> factoryHandlers : this.handlerFactories.values() ) { handlers.addAll( factoryHandlers ); } final TopologicalSorter<SapphireActionHandler> sorter = new TopologicalSorter<SapphireActionHandler>(); for( SapphireActionHandler handler : handlers ) { boolean ok = true; List<SapphireActionHandlerFilter> failedFilters = null; for( SapphireActionHandlerFilter filter : this.filters ) { try { ok = filter.check( handler ); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); // Filters are booted on first failure to keep malfunctioning filters from // flooding the log, etc. if( failedFilters == null ) { failedFilters = new ArrayList<SapphireActionHandlerFilter>(); } failedFilters.add( filter ); } if( ! ok ) { break; } } if( failedFilters != null ) { this.filters.removeAll( failedFilters ); } if( ok ) { final TopologicalSorter.Entity handlerEntity = sorter.entity( handler.getId(), handler ); for( SapphireActionLocationHint locationHint : handler.getLocationHints() ) { handlerEntity.constraint( locationHint.toString() ); } } } return Collections.unmodifiableList( sorter.sort() ); } } public SapphireActionHandler getFirstActiveHandler() { final List<SapphireActionHandler> handlers = getActiveHandlers(); return ( handlers.isEmpty() ? null : handlers.get( 0 ) ); } public boolean hasActiveHandlers() { return ( getFirstActiveHandler() != null ); } public List<SapphireActionHandler> getEnabledHandlers() { final ListFactory<SapphireActionHandler> enabledHandlersListFactory = ListFactory.start(); for( final SapphireActionHandler handler : getActiveHandlers() ) { if( handler.isEnabled() ) { enabledHandlersListFactory.add( handler ); } } return enabledHandlersListFactory.result(); } public void addFilter( final SapphireActionHandlerFilter filter ) { this.filters.add( filter ); broadcast( new FiltersChangedEvent() ); } public void removeFilter( final SapphireActionHandlerFilter filter ) { this.filters.remove( filter ); broadcast( new FiltersChangedEvent() ); } private void refreshVisibilityState() { boolean visible = false; for( SapphireActionHandler handler : getActiveHandlers() ) { if( handler.isVisible() ) { visible = true; break; } } setVisible( visible ); } private void refreshEnablementState() { setEnabled( ! getEnabledHandlers().isEmpty() ); } private void refreshCheckedState() { boolean checked = false; final SapphireActionHandler handler = getFirstActiveHandler(); if( handler != null ) { checked = handler.isChecked(); } setChecked( checked ); } public static final class TypeChangedEvent extends Event {} public static final class GroupChangedEvent extends Event {} public static final class KeyBindingChangedEvent extends Event {} public static final class KeyBindingBehaviorChangedEvent extends Event {} public static final class HandlersChangedEvent extends Event {} public static final class FiltersChangedEvent extends Event {} }