/*******************************************************************************
* Copyright (c) 2013 RelationWare, Benno Luthiger
* 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:
* RelationWare, Benno Luthiger
******************************************************************************/
package org.ripla.rap.app;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.application.AbstractEntryPoint;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
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.Label;
import org.eclipse.swt.widgets.Shell;
import org.osgi.service.useradmin.Authorization;
import org.osgi.service.useradmin.User;
import org.ripla.exceptions.NoControllerFoundException;
import org.ripla.interfaces.IMenuCommand;
import org.ripla.rap.Constants;
import org.ripla.rap.interfaces.IBodyComponent;
import org.ripla.rap.interfaces.IPluggable;
import org.ripla.rap.interfaces.IRapConfiguration;
import org.ripla.rap.interfaces.IToolbarAction;
import org.ripla.rap.interfaces.IToolbarActionListener;
import org.ripla.rap.interfaces.IToolbarItemCreator;
import org.ripla.rap.internal.menu.DropDownMenu;
import org.ripla.rap.internal.menu.MenuFactory;
import org.ripla.rap.internal.services.ConfigManager;
import org.ripla.rap.internal.services.RiplaEventDispatcher;
import org.ripla.rap.internal.services.SkinRegistry;
import org.ripla.rap.internal.services.ToolbarItemRegistry;
import org.ripla.rap.internal.services.UseCaseRegistry;
import org.ripla.rap.internal.views.DefaultRiplaView;
import org.ripla.rap.internal.views.RiplaLogin;
import org.ripla.rap.services.ISkin;
import org.ripla.rap.services.IToolbarItem;
import org.ripla.rap.util.GridLayoutHelper;
import org.ripla.rap.util.ToolbarItemFactory;
import org.ripla.util.ParameterObject;
import org.ripla.util.PreferencesHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The base entry point of a RAP application based on Ripla.<br />
* This class is instantiated once for every user/session.
*
* @author Luthiger
*/
public class RiplaBase extends AbstractEntryPoint implements IBodyComponent {
private static final Logger LOG = LoggerFactory.getLogger(RiplaBase.class);
// the width of the scrollable view
private static final int CONTENT_MIN_WIDTH = 1500;
// the height of the scrollable view
private static final int CONTENT_MIN_HEIGHT = 700;
private static final int MENUBAR_HEIGTH = 38;
// the default height of the toolbar
public static final int DFT_TOOLBAR_HEIGHT = 22; // 22
private IRapConfiguration appConfig;
private ToolbarItemFactory toolbarItemFactory;
private PreferencesHelper preferences;
private ScrolledComposite scrolledArea;
private Composite bodyView;
private ContentView contentView;
private ContextView contextView;
private IMenuCommand defaultMenuCmd;
private RiplaEventDispatcher eventDispatcher;
@Override
protected Shell createShell(final Display inDisplay) {
final Shell outShell = super.createShell(inDisplay);
outShell.setData(RWT.CUSTOM_VARIANT, "ripla-main");
return outShell;
}
@Override
protected void createContents(final Composite inParent) {
eventDispatcher = new RiplaEventDispatcher();
eventDispatcher.setBodyComponent(this);
RWT.getUISession().setAttribute(Constants.RS_EVENT_DISPATCHER,
eventDispatcher);
// retrieve application attributes
preferences = retrieveAttribute(PreferencesHelper.class,
RiplaApplication.AN_PREFS);
RWT.setLocale(preferences.getLocale(RWT.getLocale()));
RWT.getUISession().setAttribute(Constants.RS_PREFS, preferences);
final ConfigManager lConfigManager = retrieveAttribute(
ConfigManager.class, RiplaApplication.AN_CONFIG);
appConfig = retrieveAttribute(IRapConfiguration.class,
RiplaApplication.AN_APP_CONFIG);
toolbarItemFactory = new ToolbarItemFactory(preferences,
lConfigManager, null);
createRiplaView(inParent, appConfig);
}
private void createRiplaView(final Composite inParent,
final IRapConfiguration inConfiguration) {
inParent.setLayout(new FillLayout());
scrolledArea = createScrolledArea(inParent);
bodyView = createContent(scrolledArea, inConfiguration);
scrolledArea.setContent(bodyView);
showDefault();
}
@Override
public void refreshBody() {
bodyView.dispose();
final ISkin lSkin = SkinRegistry.INSTANCE.getActiveSkin();
// see method createContent()
bodyView = new Composite(scrolledArea, SWT.NONE);
bodyView.setLayout(GridLayoutHelper.createGridLayout());
createAppContent(lSkin, bodyView);
if (lSkin.hasFooter()) {
final Composite footer = createComposite(bodyView,
GridLayoutHelper.createFillLayoutData());
lSkin.getFooter(footer);
}
scrolledArea.setContent(bodyView);
bodyView.pack();
showDefault();
}
private <T> T retrieveAttribute(final Class<T> inType,
final String inAttrName) {
final Object obj = RWT.getApplicationContext().getAttribute(inAttrName);
try {
return inType.cast(obj);
} catch (final ClassCastException exc) {
LOG.error("Error encounteres while retrieving application attribute!");
}
return null;
}
private ScrolledComposite createScrolledArea(final Composite inParent) {
final ScrolledComposite outScrolledComp = new ScrolledComposite(
inParent, SWT.V_SCROLL | SWT.H_SCROLL);
outScrolledComp.setMinSize(CONTENT_MIN_WIDTH, CONTENT_MIN_HEIGHT);
outScrolledComp.setExpandVertical(true);
outScrolledComp.setExpandHorizontal(true);
return outScrolledComp;
}
private Composite createContent(final ScrolledComposite inParent,
final IRapConfiguration inConfiguration) {
final ISkin lSkin = SkinRegistry.INSTANCE.getActiveSkin();
final Composite out = new Composite(inParent, SWT.NONE);
out.setLayout(GridLayoutHelper.createGridLayout());
if (inConfiguration.getLoginAuthenticator() == null
|| RWT.getUISession().getAttribute(Constants.RS_USER) != null) {
createAppContent(lSkin, out);
} else {
createLoginView(out, inConfiguration, lSkin);
}
if (lSkin.hasFooter()) {
final Composite footer = createComposite(out,
GridLayoutHelper.createFillLayoutData());
lSkin.getFooter(footer);
}
return out;
}
/**
* Creates the application's login view.<br />
* Subclasses may override.
*
* @param inParent
* {@link Composite}
* @param inConfiguration
* {@link IRapConfiguration} the application's configuration
* object
* @param inSkin
* {@link ISkin} the actual application skin
*/
protected void createLoginView(final Composite inParent,
final IRapConfiguration inConfiguration, final ISkin inSkin) {
final Composite lTop = createComposite(inParent,
GridLayoutHelper.createFillLayoutData());
inSkin.getHeader(lTop, appConfig.getAppName());
final Composite lContent = createComposite(inParent, new GridData(
GridData.FILL_BOTH));
new RiplaLogin(lContent, inConfiguration,
UseCaseRegistry.INSTANCE.getUserAdmin(), this);
}
@Override
public void showAfterLogin(final User inUser) {
toolbarItemFactory.setUser(inUser);
RWT.getUISession().setAttribute(Constants.RS_USER, inUser);
RWT.getUISession().setAttribute(Constants.RS_LOCALE,
preferences.getLocale(inUser, RWT.getLocale()));
refreshBody();
}
private void createAppContent(final ISkin inSkin, final Composite inParent) {
// top consists of header, toolbar, menubar
final Composite lTop = createComposite(inParent,
GridLayoutHelper.createFillLayoutData());
inSkin.getHeader(lTop, appConfig.getAppName());
if (inSkin.hasToolBar()) {
createToolbar(lTop, inSkin);
}
if (inSkin.hasMenuBar()) {
createMenubar(lTop, inSkin);
}
// then create page area beneath the top, i.e. the content area
final Composite lContent = createComposite(inParent, new GridData(
GridData.FILL_BOTH));
final SashForm lContentSash = new SashForm(lContent, SWT.HORIZONTAL
| SWT.SMOOTH);
lContentSash.setLayoutData(new GridData(GridData.FILL_BOTH));
lContentSash.setData(RWT.CUSTOM_VARIANT, "ripla-sash");
final Composite lContentLeft = createComposite(lContentSash,
new GridData(GridData.FILL_BOTH));
lContentLeft.setData(RWT.CUSTOM_VARIANT, "ripla-context-pane");
contextView = new ContextView(lContentLeft);
final Composite lContentRight = createComposite(lContentSash,
new GridData(GridData.FILL_BOTH));
lContentRight.setData(RWT.CUSTOM_VARIANT, "ripla-content-pane");
contentView = new ContentView(lContentRight);
lContentSash.setWeights(new int[] { 1, 6 });
}
@Override
public void showDefault() {
if (defaultMenuCmd != null) {
try {
setContentView(defaultMenuCmd.getControllerName());
} catch (final NoControllerFoundException exc) {
handleNoTaskFound(exc);
}
}
}
private Composite createMenubar(final Composite inParent, final ISkin inSkin) {
Composite out = inSkin.getMenuBarMedium(inParent);
if (out != null) {
// skin.menuBar contains menu items
final Composite lMenuBar = inSkin.getMenuBar(out);
if (lMenuBar != null) {
createMenuControls(lMenuBar, inSkin.getSubMenuIcon());
return out;
}
// skin.menuBarMedium contains menu items
createMenuControls(out, inSkin.getSubMenuIcon());
return out;
}
// skin.menuBar contains menu items
out = inSkin.getMenuBar(inParent);
// dft.menuBar contains menu items
if (out == null) {
out = createDftMenuBar(inParent);
}
createMenuControls(out, inSkin.getSubMenuIcon());
return out;
}
@Override
public void setContentView(final String inControllerName)
throws NoControllerFoundException {
contentView.recreate(inControllerName);
}
@Override
public void setContentView(final NoControllerFoundException inExc) {
handleNoTaskFound(inExc);
}
@Override
public void setContextMenu(final String inMenuSetName,
final Class<? extends IPluggable> inControllerClass)
throws NoControllerFoundException {
contextView.recreate(inMenuSetName, inControllerClass);
}
protected void handleNoTaskFound(final NoControllerFoundException inExc) {
LOG.error("Configuration error:", inExc); //$NON-NLS-1$
contentView.showDft(inExc);
}
@SuppressWarnings("serial")
private void createMenuControls(final Composite inParent,
final Image inMenuIcon) {
final SelectionListener lMenuSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent inEvent) {
final Object lAction = inEvent.widget
.getData(MenuFactory.KEY_MENU_ACTION);
if (lAction instanceof IMenuCommand) {
afterMenuClick();
try {
setContentView(((IMenuCommand) lAction)
.getControllerName());
} catch (final NoControllerFoundException exc) {
handleNoTaskFound(exc);
}
}
}
};
final Authorization lAuthorization = UseCaseRegistry.INSTANCE
.getUserAdmin().getAuthorization(getUser());
// the menu map is used for that the selected menu can be marked
final Map<String, DropDownMenu> lMenuMap = new HashMap<String, DropDownMenu>();
for (final MenuFactory lFactory : UseCaseRegistry.INSTANCE.getMenus()) {
final DropDownMenu lMenu = lFactory.createMenu(inParent,
inMenuIcon, lMenuSelectionListener, lAuthorization);
if (lMenu != null) {
lMenuMap.put(lFactory.getProviderSymbolicName(), lMenu);
if (defaultMenuCmd == null) {
defaultMenuCmd = lMenu.getDefaultCmd();
}
}
}
RWT.getUISession().setAttribute(Constants.RS_MENU_MAP, lMenuMap);
}
private User getUser() {
return (User) RWT.getUISession().getAttribute(Constants.RS_USER);
}
/**
* Hook for subclasses to provide functionality to be processed after the
* user clicked a menu item.
* <p>
* Subclasses may override.
* </p>
*
*/
protected void afterMenuClick() {
// do nothing
}
/**
* @return {@link Composite} a menu bar composite having
* <code>RowLayout(HORIZONTAL)</code>.
*/
private Composite createDftMenuBar(final Composite inParent) {
final Composite lHolder = new Composite(inParent, SWT.NONE);
lHolder.setLayout(GridLayoutHelper.createGridLayout());
lHolder.setData(RWT.CUSTOM_VARIANT, Constants.CSS_MENU_BAR);
final GridData lLayoutData = new GridData(SWT.DEFAULT, MENUBAR_HEIGTH);
lLayoutData.grabExcessHorizontalSpace = true;
lLayoutData.horizontalAlignment = GridData.FILL;
lHolder.setLayoutData(lLayoutData);
final Composite lFill = new Composite(lHolder, SWT.NONE);
lFill.setLayout(GridLayoutHelper.createGridLayout());
lFill.setLayoutData(GridLayoutHelper.createFillLayoutData());
final Composite out = new Composite(lFill, SWT.BORDER);
final RowLayout lLayout = new RowLayout(SWT.HORIZONTAL);
lLayout.marginTop = 0;
out.setLayout(lLayout);
out.setLayoutData(GridLayoutHelper.createFillLayoutData());
out.setData(RWT.CUSTOM_VARIANT, Constants.CSS_MENU_HOLDER);
return out;
}
/**
* @return {@link Composite} a toolbar composite having
* <code>RowLayout(HORIZONTAL)</code>.
*/
private Composite createToolbar(final Composite inParent, final ISkin inSkin) {
final Composite lHolder = new Composite(inParent, SWT.NONE);
lHolder.setLayout(GridLayoutHelper.createGridLayout());
lHolder.setLayoutData(GridLayoutHelper.createFillLayoutData());
lHolder.setData(RWT.CUSTOM_VARIANT, "ripla-toolbar-holder");
final Composite out = new Composite(lHolder, SWT.RIGHT);
out.setData(RWT.CUSTOM_VARIANT, "ripla-toolbar");
final RowLayout lLayout = new RowLayout(SWT.HORIZONTAL);
lLayout.marginTop = 0;
out.setLayout(lLayout);
final GridData lLayoutData = new GridData(SWT.END, SWT.CENTER, true,
true);
lLayoutData.heightHint = DFT_TOOLBAR_HEIGHT;
out.setLayoutData(lLayoutData);
toolbarItemFactory.setParent(out);
final Collection<IToolbarItem> lItems = ToolbarItemRegistry.INSTANCE
.getSortedItems();
Label lLastSep = null;
for (final IToolbarItem lItem : lItems) {
final IToolbarItemCreator lFactory = lItem.getCreator();
final Control lComponent = lFactory == null ? lItem
.getComponent(out) : lFactory.createToolbarItem(out, this,
getUser());
if (lComponent == null) {
continue;
}
lItem.registerToolbarActionListener(new IToolbarActionListener() { // NOPMD
@Override
public void processAction(final IToolbarAction inAction) {
inAction.run();
}
});
lLastSep = inSkin.getToolbarSeparator(out);
}
if (lLastSep != null) {
lLastSep.dispose();
}
return out;
}
private Composite createComposite(final Composite inParent,
final GridData inLayoutData) {
final Composite out = new Composite(inParent, SWT.NONE);
out.setLayout(GridLayoutHelper.createGridLayout());
out.setLayoutData(inLayoutData);
return out;
}
/**
* Returns the parameter object to pass context parameters that can be
* evaluated to decide whether a context menu item should be displayed or
* hidden.<br />
* By default, an empty parameter object is created.<br />
* Subclasses may override.
*
* @return {@link ParameterObject}
*/
protected ParameterObject createParametersForContextMenu() {
return new ParameterObject();
}
/**
* The factory method to create a toolbar component instance. <br />
* Toolbar items created with this factory must have a constructor with the
* following parameters:
* <ul>
* <li>org.eclipse.swt.widgets.Composite</li>
* <li>org.ripla.util.PreferencesHelper</li>
* <li>org.ripla.rap.internal.services.ConfigManager</li>
* <li>org.osgi.service.useradmin.User</li>
* </ul>
*
* @param inClass
* Class the toolbar component class
* @return {@link Composite} the created toolbar component instance or
* <code>null</code> in case of an error
*/
@Override
public <T extends Composite> T createToolbarItem(final Class<T> inClass) {
try {
return toolbarItemFactory.createToolbarComponent(inClass);
} catch (final Exception exc) {
LOG.error("Error encountered while creating the toolbar item!", exc);
}
return null;
}
// --
/**
* Subclasses may override to provide their own
* <code>PreferencesHelper</code>.
*
* @return PreferencesHelper
*/
protected PreferencesHelper createPreferencesHelper() {
return new PreferencesHelper();
}
private static abstract class ViewHandler {
private final Composite parent;
private Composite delegate;
protected ViewHandler(final Composite inParent) {
parent = inParent;
delegate = createDelegate(parent);
}
private Composite createDelegate(final Composite inParent) {
final Composite out = new Composite(inParent, SWT.NONE);
out.setData(RWT.CUSTOM_VARIANT, getStyle());
out.setLayout(new GridLayout());
out.setLayoutData(GridLayoutHelper.createFillLayoutData());
return out;
}
abstract protected String getStyle();
protected void recreate(final String inName)
throws NoControllerFoundException {
delegate.dispose();
delegate = createDelegate(parent);
manageView(inName, delegate);
delegate.pack();
}
protected void showDft(final Exception inExc) {
delegate.dispose();
delegate = createDelegate(parent);
new DefaultRiplaView(delegate, inExc);
delegate.pack();
}
abstract protected void manageView(String inControllerName,
Composite inParent) throws NoControllerFoundException;
}
private static class ContentView extends ViewHandler {
protected ContentView(final Composite inParent) {
super(inParent);
}
@Override
protected void manageView(final String inControllerName,
final Composite inParent) throws NoControllerFoundException {
UseCaseRegistry.INSTANCE.getControllerManager().getContent(
inControllerName, inParent);
}
@Override
protected String getStyle() {
return "ripla-content-view";
}
}
private class ContextView extends ViewHandler {
private Class<? extends IPluggable> controllerClass;
protected ContextView(final Composite inParent) {
super(inParent);
}
protected void recreate(final String inName,
final Class<? extends IPluggable> inControllerClass)
throws NoControllerFoundException {
controllerClass = inControllerClass;
super.recreate(inName);
}
@Override
protected void manageView(final String inMenuSetName,
final Composite inParent) throws NoControllerFoundException {
final User lUser = (User) RWT.getUISession().getAttribute(
Constants.RS_USER);
UseCaseRegistry.INSTANCE.getContextMenuManager().renderContextMenu(
inParent,
inMenuSetName,
lUser,
UseCaseRegistry.INSTANCE.getUserAdmin().getAuthorization(
null), createParametersForContextMenu(),
controllerClass);
}
@Override
protected String getStyle() {
return "ripla-context-view";
}
}
}