/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.navigation.ui.controllers; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.osgi.service.log.LogService; import org.eclipse.core.runtime.Assert; import org.eclipse.equinox.log.Logger; import org.eclipse.riena.core.Log4r; import org.eclipse.riena.core.RienaStatus; import org.eclipse.riena.core.annotationprocessor.DisposerList; import org.eclipse.riena.core.annotationprocessor.IDisposer; import org.eclipse.riena.core.marker.IMarker; import org.eclipse.riena.internal.navigation.ui.Activator; import org.eclipse.riena.navigation.INavigationContext; import org.eclipse.riena.navigation.INavigationNode; import org.eclipse.riena.navigation.INavigationNodeController; import org.eclipse.riena.navigation.NavigationArgument; import org.eclipse.riena.navigation.annotation.processor.NavigationNodeControllerAnnotationProcessor; import org.eclipse.riena.navigation.common.TypecastingObject; import org.eclipse.riena.navigation.listener.INavigationNodeListenerable; import org.eclipse.riena.navigation.listener.NavigationNodeListener; import org.eclipse.riena.ui.core.context.IContext; import org.eclipse.riena.ui.core.marker.ErrorMarker; import org.eclipse.riena.ui.core.marker.MandatoryMarker; import org.eclipse.riena.ui.ridgets.AbstractRidget; import org.eclipse.riena.ui.ridgets.ClassRidgetMapper; import org.eclipse.riena.ui.ridgets.ComplexRidgetResolver; import org.eclipse.riena.ui.ridgets.IBasicMarkableRidget; import org.eclipse.riena.ui.ridgets.IRidget; import org.eclipse.riena.ui.ridgets.IRidgetResolver; import org.eclipse.riena.ui.ridgets.IStatuslineRidget; import org.eclipse.riena.ui.ridgets.IWindowRidget; import org.eclipse.riena.ui.ridgets.RidgetToStatuslineSubscriber; import org.eclipse.riena.ui.ridgets.SubModuleUtils; import org.eclipse.riena.ui.ridgets.controller.IController; import org.eclipse.riena.ui.ridgets.marker.MarkerUtil; /** * An abstract controller superclass that manages the navigation node of a controller. * * @param <N> * Type of the navigation node */ public abstract class NavigationNodeController<N extends INavigationNode<?>> extends TypecastingObject implements INavigationNodeController, IController, IContext { private final static Logger LOGGER = Log4r.getLogger(Activator.getDefault(), NavigationNodeController.class); /** * @since 5.0 */ protected IRidgetResolver ridgetResolver = new ComplexRidgetResolver(); private N navigationNode; private Map<String, IRidget> ridgets; private NavigationUIFilterApplier<N> nodeListener; private PropertyChangeListener propertyChangeListener; private boolean configured = false; private final RidgetToStatuslineSubscriber ridgetToStatuslineSubscriber = new RidgetToStatuslineSubscriber(); private DisposerList annotationDisposerList; @SuppressWarnings("rawtypes") private NavigationNodeListener disposeListener; /** * Create a new Navigation Node view Controller. Set the navigation node later. */ public NavigationNodeController() { this(null); } /** * Create a new Navigation Node view Controller on the specified navigationNode. Register this controller as the presentation of the Navigation node. * * @param navigationNode * the node to work on */ public NavigationNodeController(final N navigationNode) { ridgets = new HashMap<String, IRidget>(); propertyChangeListener = new PropertyChangeHandler(); nodeListener = new NavigationUIFilterApplier<N>(); disposeListener = new DisposedListener(); if (navigationNode != null) { setNavigationNode(navigationNode); } } /** * @return the navigationNode */ public N getNavigationNode() { return navigationNode; } /** * @param navigationNode * the navigationNode to set */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void setNavigationNode(final N navigationNode) { if (getNavigationNode() instanceof INavigationNodeListenerable) { ((INavigationNodeListenerable) getNavigationNode()).removeListener(nodeListener); ((INavigationNodeListenerable) getNavigationNode()).removeListener(disposeListener); } this.navigationNode = navigationNode; navigationNode.setNavigationNodeController(this); updateNavigationNodeMarkers(); if (getNavigationNode() instanceof INavigationNodeListenerable) { ((INavigationNodeListenerable) getNavigationNode()).addListener(nodeListener); ((INavigationNodeListenerable) getNavigationNode()).addListener(disposeListener); } } /** * {@inheritDoc} * <p> * Override in concrete subclass. */ public boolean allowsActivate(final INavigationNode<?> pNode, final INavigationContext context) { return true; } /** * {@inheritDoc} * <p> * Override in concrete subclass. */ public boolean allowsDeactivate(final INavigationNode<?> pNode, final INavigationContext context) { return true; } /** * {@inheritDoc} * <p> * All methods that overrides this method must call <code>super.afterBind()</code>. */ public void afterBind() { NavigationNodeControllerAnnotationProcessor.getInstance().processAnnotations(this); updateNavigationNodeMarkers(); } /** * @return <code>true</code> if the controller is activated */ public boolean isActivated() { return getNavigationNode() != null && getNavigationNode().isActivated(); } /** * @return <code>true</code> if the node is enabled */ public boolean isEnabled() { return getNavigationNode() != null && getNavigationNode().isEnabled(); } /** * @return <code>true</code> if the node is visible */ public boolean isVisible() { return getNavigationNode() != null && getNavigationNode().isVisible(); } /** * @return true if the controller is activated */ public boolean isDeactivated() { return getNavigationNode() == null || getNavigationNode().isDeactivated(); } /** * @return <code>true</code> if the controller is created */ public boolean isCreated() { return getNavigationNode() == null || getNavigationNode().isCreated(); } /** * {@inheritDoc} * <p> * Override in concrete subclass. */ public boolean allowsDispose(final INavigationNode<?> node, final INavigationContext context) { return true; } public void addRidget(final String id, final IRidget ridget) { ridget.addPropertyChangeListener(IBasicMarkableRidget.PROPERTY_MARKER, propertyChangeListener); ridget.addPropertyChangeListener(IBasicMarkableRidget.PROPERTY_MARKER_HIDING, propertyChangeListener); ridget.addPropertyChangeListener(IRidget.PROPERTY_SHOWING, propertyChangeListener); ridget.addPropertyChangeListener(AbstractRidget.COMMAND_UPDATE, propertyChangeListener); ridgets.put(id, ridget); ridgetToStatuslineSubscriber.addRidget(ridget); } /** * {@inheritDoc} * * @since 5.0 */ public boolean removeRidget(final String id) { ridgetToStatuslineSubscriber.removeRidget(getRidget(id)); return ridgets.remove(id) != null; } /** * {@inheritDoc} * * @since 5.0 */ public void setStatuslineToShowMarkerMessages(final IStatuslineRidget statuslineToShowMarkerMessages) { ridgetToStatuslineSubscriber.setStatuslineToShowMarkerMessages(statuslineToShowMarkerMessages, getRidgets()); } /** * {@inheritDoc} * <p> * Implementation note: for ridgets of the type IRidgetContainer, this method supports two-part ids (i.e. nested ids). For example, if the ridget * "searchComposite" containts the ridget "txtName", you can request: "searchComposite.txtName": * * <pre> * ITextRidget txtName = getRidget("searchComposite.txtName"); * </pre> * * @since 3.0 */ public <R extends IRidget> R getRidget(final String id) { return (R) ridgetResolver.getRidget(id, ridgets); } /** * @since 2.0 */ public <R extends IRidget> R getRidget(final Class<R> ridgetClazz, final String id) { R ridget = getRidget(id); if (ridget != null) { if (!ridgetClazz.isInstance(ridget)) { String logMessage = "getRidget(Class, String): Actual ridget (id: "; //$NON-NLS-1$ logMessage += id; logMessage += ")"; //$NON-NLS-1$ logMessage += " is not of desired type: ridget is "; //$NON-NLS-1$ logMessage += ridget.getClass().getName(); logMessage += ", desired is "; //$NON-NLS-1$ logMessage += ridgetClazz.getName(); LOGGER.log(LogService.LOG_ERROR, logMessage); } return ridget; } if (!SubModuleUtils.isPrepareView() || RienaStatus.isTest()) { try { if (ridgetClazz.isInterface() || Modifier.isAbstract(ridgetClazz.getModifiers())) { final Class<R> mappedRidgetClazz = (Class<R>) ClassRidgetMapper.getInstance().getRidgetClass(ridgetClazz); if (mappedRidgetClazz != null) { ridget = mappedRidgetClazz.newInstance(); } Assert.isNotNull(ridget, "Could not find a corresponding implementation for " + ridgetClazz.getName() + " in " + ClassRidgetMapper.class.getName()); //$NON-NLS-1$ //$NON-NLS-2$ } else { ridget = ridgetClazz.newInstance(); } } catch (final InstantiationException e) { throw new RuntimeException(e); } catch (final IllegalAccessException e) { throw new RuntimeException(e); } ridgetResolver.addRidget(id, ridget, this, ridgets); } return ridget; } public Collection<? extends IRidget> getRidgets() { return ridgets.values(); } /** * The idea in this method is to have at most one marker of each type (to avoid firing too many events when redundantly adding/removing markers of the same * type) */ protected void updateNavigationNodeMarkers() { final Collection<ErrorMarker> errorInRidgets = new ArrayList<ErrorMarker>(); final Collection<MandatoryMarker> mandatoryInRidgets = new ArrayList<MandatoryMarker>(); // add error and/or mandatory marker, if a Ridget has an error marker and/or a (enabled) mandatory marker for (final IMarker marker : getRidgetMarkers()) { if (marker instanceof ErrorMarker) { errorInRidgets.add((ErrorMarker) marker); } else if (marker instanceof MandatoryMarker) { final MandatoryMarker mandatoryMarker = (MandatoryMarker) marker; if (!mandatoryMarker.isDisabled()) { mandatoryInRidgets.add(mandatoryMarker); } } } // handle error markers final Collection<ErrorMarker> errorInNode = getNavigationNode().getMarkersOfType(ErrorMarker.class); // case 1: // marker in node - no // marker in ridgets - yes // => we have to add a marker to the node if (errorInNode.isEmpty() && !errorInRidgets.isEmpty()) { // error markers are unique, so just add the first one getNavigationNode().addMarker(errorInRidgets.iterator().next()); } // case 2: // marker in node - yes // marker in ridgets - no // => we have to remove the marker from the node if (!errorInNode.isEmpty() && errorInRidgets.isEmpty()) { getNavigationNode().removeMarker(errorInNode.iterator().next()); } // now we repeat the same for the mandatory markers final Collection<MandatoryMarker> mandatoryInNode = getNavigationNode().getMarkersOfType(MandatoryMarker.class); // case 1: // marker in node - no // marker in ridgets - yes // => we have to add a marker to the node if (mandatoryInNode.isEmpty() && !mandatoryInRidgets.isEmpty()) { getNavigationNode().addMarker(mandatoryInRidgets.iterator().next()); } // case 2: // marker in node - yes // marker in ridgets - no // => we have to remove the marker from the node if (!mandatoryInNode.isEmpty() && mandatoryInRidgets.isEmpty()) { getNavigationNode().removeMarker(mandatoryInNode.iterator().next()); } // case 3: // enabled marker in node - no // disabled marker in node - yes // (enabled) marker in ridgets - yes // => we have to remove the disabled marker from the node and add the enabled marker from the ridgets final List<MandatoryMarker> enabledMandatoryInNode = new ArrayList<MandatoryMarker>(); final List<MandatoryMarker> disabledMandatoryInNode = new ArrayList<MandatoryMarker>(); for (final MandatoryMarker m : new ArrayList<MandatoryMarker>(mandatoryInNode)) { if (m.isDisabled()) { disabledMandatoryInNode.add(m); } else { enabledMandatoryInNode.add(m); } } if (enabledMandatoryInNode.isEmpty() && !disabledMandatoryInNode.isEmpty() && !mandatoryInRidgets.isEmpty()) { getNavigationNode().removeMarker(disabledMandatoryInNode.iterator().next()); getNavigationNode().addMarker(mandatoryInRidgets.iterator().next()); } } /** * this method is invoked by test code, so do not inline it */ private List<IMarker> getRidgetMarkers() { return MarkerUtil.getRidgetMarkers(this); } protected void updateIcon(final IWindowRidget windowRidget) { if (windowRidget == null) { return; } final String nodeIcon = getNavigationNode().getIcon(); windowRidget.setIcon(nodeIcon); } public void setBlocked(final boolean blocked) { if (getNavigationNode() != null) { getNavigationNode().setBlocked(blocked); } } public boolean isBlocked() { return getNavigationNode() != null && getNavigationNode().isBlocked(); } public NavigationNodeController<?> getParentController() { if ((getNavigationNode() != null) && (getNavigationNode().getParent() == null)) { return null; } else { return (NavigationNodeController<?>) navigationNode.getParent().getNavigationNodeController(); } } private class PropertyChangeHandler implements PropertyChangeListener { public void propertyChange(final PropertyChangeEvent evt) { updateNavigationNodeMarkers(); } } /** * @since 1.2 */ public void setContext(final String key, final Object value) { Assert.isNotNull(getNavigationNode(), "NavigationNode may not be null"); //$NON-NLS-1$ getNavigationNode().setContext(key, value); } /** * @since 1.2 */ public Object getContext(final String key) { Assert.isNotNull(getNavigationNode(), "NavigationNode may not be null"); //$NON-NLS-1$ return getNavigationNode().getContext(key); } public void navigationArgumentChanged(final NavigationArgument argument) { } /** * {@inheritDoc} * * @since 4.0 */ public void setConfigured(final boolean configured) { this.configured = configured; } /** * {@inheritDoc} * * @since 4.0 */ public boolean isConfigured() { return configured; } /** * @since 6.1 */ public void disposeAnnotations() { if (annotationDisposerList != null) { annotationDisposerList.dispose(); } } /** * @since 6.1 */ public void addAnnotationDisposer(final IDisposer disposer) { if (annotationDisposerList == null) { annotationDisposerList = new DisposerList(); } annotationDisposerList.add(disposer); } /** * Listen for a dispose event and dispose all {@link IDisposer}, which where generated by Annotations. * * @return the annotationDisposerList * @since 6.1 */ public DisposerList getAnnotationDisposerList() { return annotationDisposerList; } @SuppressWarnings("rawtypes") private class DisposedListener extends NavigationNodeListener { @Override public void beforeDisposed(final INavigationNode source) { disposeAnnotations(); } }; }