/*******************************************************************************
* 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.util.Collection;
import java.util.Iterator;
import org.osgi.service.log.LogService;
import org.eclipse.core.databinding.BindingException;
import org.eclipse.equinox.log.Logger;
import org.eclipse.riena.core.Log4r;
import org.eclipse.riena.core.util.RienaConfiguration;
import org.eclipse.riena.internal.ui.ridgets.Activator;
import org.eclipse.riena.navigation.ApplicationNodeManager;
import org.eclipse.riena.navigation.IApplicationNode;
import org.eclipse.riena.navigation.IHierarchyChangeListener;
import org.eclipse.riena.navigation.IModuleNode;
import org.eclipse.riena.navigation.INavigationNode;
import org.eclipse.riena.navigation.ISubModuleNode;
import org.eclipse.riena.navigation.listener.SubModuleNodeListener;
import org.eclipse.riena.navigation.model.SubModuleNode;
import org.eclipse.riena.ui.ridgets.IActionRidget;
import org.eclipse.riena.ui.ridgets.IDefaultActionManager;
import org.eclipse.riena.ui.ridgets.IEmbeddedTitleBarRidget;
import org.eclipse.riena.ui.ridgets.IInfoFlyoutRidget;
import org.eclipse.riena.ui.ridgets.IMarkableRidget;
import org.eclipse.riena.ui.ridgets.IRidget;
import org.eclipse.riena.ui.ridgets.IRidgetContainer;
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.controller.AbstractWindowController;
import org.eclipse.riena.ui.ridgets.controller.ControllerHelper;
import org.eclipse.riena.ui.ridgets.listener.IWindowRidgetListener;
/**
* Default implementation for a SubModuleController.
*/
public class SubModuleController extends NavigationNodeController<ISubModuleNode> {
private static final Logger LOGGER = Log4r.getLogger(Activator.getDefault(), SubModuleController.class);
/**
* The ID of the window ridget in this controller ("windowRidget").
*/
public static final String WINDOW_RIDGET = "windowRidget"; //$NON-NLS-1$
private static final String TITLE_SEPARATOR = " - "; //$NON-NLS-1$
private IDefaultActionManager actionManager;
/**
* The ridget the should get the focus, the very first time this controller is activated. May be null.
*/
private IRidget initialFocus;
private final WindowListener windowListener;
/**
* <code>true</code> when the title has to be computed upon next activation
*
* @since 6.0
*/
protected boolean titleInvalid;
public SubModuleController() {
this(null);
}
public SubModuleController(final ISubModuleNode navigationNode) {
super(navigationNode);
windowListener = new WindowListener();
}
/**
* Make {@code action} the default action while the focus is within {@code focusRidget} including it's children.
* <p>
* If a default action is available and enabled, it will be invoked whenever the user presses ENTER within the window. The mapping is enabled when the
* navigation node for this controller becomes active. It is disabled when the navigation node for this controller becomes inactive.
* <p>
* Note: the algorithm stops at the first match. It will check the most specific (innermost) ridget first and check the most general (outremost) ridget
* last.
*
* @param focusRidget
* the ridget that needs to have the focus to activate this rule. Never null.
* @param action
* this ridget will become the default action, while focusRidget has the focus. Never null.
*
* @since 2.0
*/
public void addDefaultAction(final IRidget focusRidget, final IActionRidget action) {
actionManager = getWindowRidget().addDefaultAction(focusRidget, action);
// activate() can only be called, if the shell is present
if (actionManager != null && getWindowRidget().getUIControl() != null) {
actionManager.deactivate();
actionManager.activate();
}
}
@Override
public void afterBind() {
super.afterBind();
updateIcon();
updateWindowTitle();
updateCloseable();
updateActive();
if (getWindowRidget() != null) {
getWindowRidget().addWindowRidgetListener(windowListener);
}
initializeStatuslineMessageViewer();
}
private void initializeStatuslineMessageViewer() {
if (shouldEnableStatuslineMessageViewer()) {
setStatuslineToShowMarkerMessages(getStatusline());
} else {
setStatuslineToShowMarkerMessages(null);
}
}
/**
* Controls if the ridget messages from this controller are displayed in the status line.
* <p>
* This behavior is:
* <ul>
* <li>disabled by default,
* <li>can be defined globally by setting '<code>riena.showRidgetMessagesInStatusline=true</code>' using extension point
* <tt>org.eclipse.riena.core.configuration</tt>,
* <li>can be defined individually (overriding the global setting) for this controller using this method.
* </ul>
* <p>
* This method will be called by {@link #afterBind()} <strong>after</strong> {@link #configureRidgets()} was executed.
*
* @return <code>true</code> if the ridget messages should be displayed in the status line
* @since 5.0
* @see IRidgetContainer#setStatuslineToShowMarkerMessages(IStatuslineRidget)
* @see AbstractWindowController#shouldEnableStatuslineMessageViewer()
*/
protected boolean shouldEnableStatuslineMessageViewer() {
final String value = RienaConfiguration.getInstance().getProperty(RidgetToStatuslineSubscriber.SHOW_RIDGET_MESSAGES_IN_STATUSLINE_KEY);
return Boolean.parseBoolean(value);
}
/**
* Subclasses should override to configure their ridget.
* <p>
* {@inheritDoc}
*/
public void configureRidgets() {
// unused
}
/**
* Returns the ridget that should get the focus, when this controller's view is opened for the first time. If null, then the first focusable widget will get
* the focus (i.e. same behavior as in standard RCP).
* <p>
* The default value is null.
*
* @return a IRidget instance or null.
* @since 2.0
*/
public IRidget getInitialFocus() {
return initialFocus;
}
/**
* Returns the ridget that should get the focus. If a ridget is set via {@see setInitialFocus}, it will be returned. Otherwise a ridget which can receive
* the focus is searched, if none is found null is returned.
*
* @return a IRidget instance or null.
* @since 4.0
*/
public IRidget getFocusableRidget() {
if (getInitialFocus() != null) {
return getInitialFocus();
}
for (final IRidget ridget : getRidgets()) {
final boolean markable = ridget instanceof IMarkableRidget;
if (ridget.isFocusable() && ridget.isEnabled() && ridget.isVisible() && (!markable || (markable && !((IMarkableRidget) ridget).isOutputOnly()))) {
return ridget;
}
}
return null;
}
/**
* Returns the controller of the parent module.
*
* @return module controller or {@code null} if not parent module controller exists.
*/
public ModuleController getModuleController() {
final IModuleNode moduleNode = getNavigationNode().getParentOfType(IModuleNode.class);
if (moduleNode != null) {
return (ModuleController) moduleNode.getNavigationNodeController();
}
return null;
}
/**
* @return the windowRidget
*/
public IWindowRidget getWindowRidget() {
return getRidget(IEmbeddedTitleBarRidget.class, WINDOW_RIDGET);
}
/**
* Returns the {@link IInfoFlyoutRidget} for this sub module.
*
* @return an {@link IInfoFlyoutRidget}; never null
* @since 2.0
*/
public IInfoFlyoutRidget getInfoFlyout() {
final ApplicationController appController = (ApplicationController) ApplicationNodeManager.getApplicationNode().getNavigationNodeController();
return appController.getInfoFlyout();
}
/**
* Set the ridget that should get the focus, when this controller's view is opened for the first time.
* <p>
* If the value is null or if the ridget cannot receive the focus (because it is not enabled / not visible / not focusable) then the first ridget/widget
* that can receive the focus will get the focus (i.e. same behavior as in standard RCP).
*
* @param ridget
* an IRidget instance or null
*
* @since 2.0
*/
public void setInitialFocus(final IRidget ridget) {
this.initialFocus = ridget;
}
@Override
public void setBlocked(final boolean blocked) {
super.setBlocked(blocked);
restoreFocusRequestFromRidget(getRidgets());
}
/**
* Checks all ridgets recursively in this controller, if a previous call to setFocus() failed and tries to set the focus again.
* <p>
* SWT has the limitation that it doesn't set the focus if the parent composite is disabled. Therefore we have to try to restore the first previous call to
* setFocus(), while the view is blocked.
*
* @param collection
* the collection to check
* @since 4.0
*/
public void restoreFocusRequestFromRidget(final Collection<? extends IRidget> collection) {
ControllerHelper.restoreFocusRequestFromRidget(collection, getNavigationNode().isBlocked());
}
@Override
public void setNavigationNode(final ISubModuleNode navigationNode) {
super.setNavigationNode(navigationNode);
getNavigationNode().addListener(new SubModuleNodeListener() {
@Override
public void iconChanged(final ISubModuleNode source) {
updateIcon();
}
@Override
public void labelChanged(final ISubModuleNode subModuleNode) {
updateWindowTitle();
}
@Override
public void afterActivated(final ISubModuleNode source) {
if (titleInvalid) {
updateWindowTitle();
}
if (actionManager != null) {
actionManager.activate();
}
}
@Override
public void afterDeactivated(final ISubModuleNode source) {
if (actionManager != null) {
actionManager.deactivate();
}
}
@Override
public void beforeDisposed(final ISubModuleNode source) {
if (actionManager != null) {
actionManager.dispose();
actionManager = null;
}
}
});
if (navigationNode instanceof SubModuleNode) {
((SubModuleNode) navigationNode).addHierarchyChangeListener(new IHierarchyChangeListener() {
@Override
public void labelChanged(final INavigationNode<?> node) {
if (isActivated()) {
// if node is active, we have to update the title now
updateWindowTitle();
} else {
// otherwise we just invalidate the title, so it can be recomputed on next activation
titleInvalid = true;
}
// updateWindowTitle();
}
});
}
}
/**
* @param windowRidget
* the windowRidget to set
*/
public void setWindowRidget(final IWindowRidget windowRidget) {
if (getWindowRidget() != windowRidget) {
addRidget(WINDOW_RIDGET, windowRidget);
}
}
/**
* calls updateFromModel for all registered ridgets in this controller
*/
public void updateAllRidgetsFromModel() {
final Iterator<? extends IRidget> iter = getRidgets().iterator();
while (iter.hasNext()) {
final IRidget ridget = iter.next();
if (ridget.isIgnoreBindingError()) {
try {
ridget.updateFromModel();
} catch (final BindingException ex) {
String message = "Update from the model was unsuccessful for the ridget: "; //$NON-NLS-1$
message += ridget + ", with id: " + ridget.getID(); //$NON-NLS-1$
LOGGER.log(LogService.LOG_DEBUG, message);
}
} else {
ridget.updateFromModel();
}
}
}
/**
* Returns the full title of this sub-module.
* <p>
* The title is made up from the label of parent module node and all parent sub-module nodes. One exception exists: If the module has one sub-module (and
* this is not visible in the tree), then only the label of the module is returned.
*
* @return full title of the sub-module
*/
protected String getFullTitle() {
String title = getNavigationNode().getLabel();
if (isInvisibleInTree()) {
final IModuleNode moduleNode = getModuleController().getNavigationNode();
title = moduleNode.getLabel();
} else {
INavigationNode<?> parent = getNavigationNode().getParent();
if (parent != null) {
while (parent != null && !(parent instanceof IModuleNode)) {
title = parent.getLabel() + TITLE_SEPARATOR + title;
parent = parent.getParent();
}
title = parent.getLabel() + TITLE_SEPARATOR + title;
}
}
return title;
}
/**
* Re-layout all contents.
*
* @since 1.2
*/
protected void layout() {
final IWindowRidget ridget = getRidget(IEmbeddedTitleBarRidget.class, WINDOW_RIDGET);
ridget.layout();
}
@Override
protected void updateIcon(final IWindowRidget windowRidget) {
if (isInvisibleInTree()) {
if (windowRidget == null) {
return;
}
final IModuleNode moduleNode = getModuleController().getNavigationNode();
final String nodeIcon = moduleNode.getIcon();
windowRidget.setIcon(nodeIcon);
} else {
super.updateIcon(windowRidget);
}
}
/**
* Returns the status line ridget.
*
* @return the status line ridget or <code>null</code> if no status line exists
* @since 4.0
*/
protected IStatuslineRidget getStatusline() {
final ApplicationController applicationController = getApplicationController();
if (applicationController == null) {
return null;
}
return applicationController.getStatusline();
}
private ApplicationController getApplicationController() {
final IApplicationNode application = getNavigationNode().getParentOfType(IApplicationNode.class);
if (application == null) {
return null;
}
return (ApplicationController) application.getNavigationNodeController();
}
/**
* Returns whether the sub-module is hidden in the navigation tree. A sub-module is hidden if it is the only child of a module unless its parent is
* configured to show a single child.
*
* @return {@code true} if there is a navigation tree but the sub-module is not shown; otherwise {@code false}
*/
private boolean isInvisibleInTree() {
if (getModuleController() != null && getModuleController().hasSingleLeafChild()) {
return !getModuleController().getNavigationNode().isPresentSingleSubModule();
}
return false;
}
private void updateActive() {
final IWindowRidget windowRidget = getWindowRidget();
if (windowRidget != null) {
windowRidget.setActive(getNavigationNode().isActivated());
}
}
private void updateCloseable() {
final IWindowRidget windowRidget = getWindowRidget();
if (windowRidget != null) {
windowRidget.setCloseable(getNavigationNode().isClosable());
}
}
private void updateIcon() {
updateIcon(getWindowRidget());
}
void updateWindowTitle() {
final IWindowRidget windowRidget = getWindowRidget();
if (windowRidget != null) {
windowRidget.setTitle(getFullTitle());
}
if (!getNavigationNode().isActivated()) {
final ISubModuleNode subModule = ApplicationNodeManager.locateActiveSubModuleNode();
if ((subModule != null) && (subModule.getNavigationNodeController() instanceof SubModuleController)) {
((SubModuleController) subModule.getNavigationNodeController()).updateWindowTitle();
}
}
titleInvalid = false;
}
private class WindowListener implements IWindowRidgetListener {
public void closed() {
getNavigationNode().dispose();
}
public void activated() {
}
}
}