/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.view.remote;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jspresso.framework.action.IActionHandler;
import org.jspresso.framework.application.frontend.command.remote.RemoteSelectionCommand;
import org.jspresso.framework.binding.ICollectionConnector;
import org.jspresso.framework.binding.ICompositeValueConnector;
import org.jspresso.framework.binding.IValueConnector;
import org.jspresso.framework.binding.remote.RemoteValueConnector;
import org.jspresso.framework.gui.remote.RAction;
import org.jspresso.framework.gui.remote.RActionable;
import org.jspresso.framework.gui.remote.RBorderContainer;
import org.jspresso.framework.gui.remote.RComponent;
import org.jspresso.framework.gui.remote.RConstrainedGridContainer;
import org.jspresso.framework.gui.remote.REvenGridContainer;
import org.jspresso.framework.gui.remote.RLabel;
import org.jspresso.framework.gui.remote.RSplitContainer;
import org.jspresso.framework.gui.remote.RTabContainer;
import org.jspresso.framework.gui.remote.RTable;
import org.jspresso.framework.model.descriptor.IBooleanPropertyDescriptor;
import org.jspresso.framework.model.descriptor.ICollectionDescriptorProvider;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.security.EAuthorization;
import org.jspresso.framework.state.remote.IRemoteStateOwner;
import org.jspresso.framework.state.remote.IRemoteStateValueMapper;
import org.jspresso.framework.state.remote.RemoteCompositeValueState;
import org.jspresso.framework.state.remote.RemoteValueState;
import org.jspresso.framework.util.event.IValueChangeListener;
import org.jspresso.framework.util.gui.CellConstraints;
import org.jspresso.framework.util.gui.Dimension;
import org.jspresso.framework.util.gui.Font;
import org.jspresso.framework.util.gui.FontHelper;
import org.jspresso.framework.view.BasicCompositeView;
import org.jspresso.framework.view.ICompositeView;
import org.jspresso.framework.view.IView;
import org.jspresso.framework.view.descriptor.IConstrainedGridViewDescriptor;
import org.jspresso.framework.view.descriptor.IEvenGridViewDescriptor;
import org.jspresso.framework.view.descriptor.IPropertyViewDescriptor;
import org.jspresso.framework.view.descriptor.ISplitViewDescriptor;
import org.jspresso.framework.view.descriptor.ITableViewDescriptor;
import org.jspresso.framework.view.descriptor.IViewDescriptor;
/**
* Default factory for remote views.
*
* @author Vincent Vandenschrick
*/
@SuppressWarnings("UnusedParameters")
public class DefaultRemoteViewFactory extends AbstractRemoteViewFactory {
private static final IRemoteStateValueMapper FONT_MAPPER = new IRemoteStateValueMapper() {
@Override
public Object getValueFromState(RemoteValueState state, Object originalValue) {
if (originalValue instanceof Font) {
return FontHelper.toString((Font) originalValue);
}
return null;
}
@Override
public Object getValueForState(RemoteValueState state, Object originalValue) {
if (originalValue instanceof String && FontHelper.isFontSpec((String) originalValue)) {
return FontHelper.fromString((String) originalValue);
}
return null;
}
};
/**
* Constructs a new {@code DefaultRemoteViewFactory} instance.
*/
public DefaultRemoteViewFactory() {
super();
}
/**
* {@inheritDoc}
*/
@Override
protected ICompositeView<RComponent> createConstrainedGridView(IConstrainedGridViewDescriptor viewDescriptor,
IActionHandler actionHandler, Locale locale) {
RConstrainedGridContainer viewComponent = createRConstrainedGridContainer(viewDescriptor);
List<RComponent> cells = new ArrayList<>();
List<CellConstraints> cellConstraints = new ArrayList<>();
BasicCompositeView<RComponent> view = constructCompositeView(viewComponent, viewDescriptor);
List<IView<RComponent>> childrenViews = new ArrayList<>();
for (IViewDescriptor childViewDescriptor : viewDescriptor.getChildViewDescriptors()) {
IView<RComponent> childView = createView(childViewDescriptor, actionHandler, locale);
cellConstraints.add(viewDescriptor.getCellConstraints(childViewDescriptor));
cells.add(childView.getPeer());
childrenViews.add(childView);
}
viewComponent.setCells(cells.toArray(new RComponent[cells.size()]));
viewComponent.setCellConstraints(cellConstraints.toArray(new CellConstraints[cellConstraints.size()]));
view.setChildren(childrenViews);
return view;
}
/**
* {@inheritDoc}
*/
@Override
protected ICompositeView<RComponent> createEvenGridView(IEvenGridViewDescriptor viewDescriptor,
IActionHandler actionHandler, Locale locale) {
REvenGridContainer viewComponent = createREvenGridContainer(viewDescriptor);
viewComponent.setDrivingDimension(viewDescriptor.getDrivingDimension().name());
viewComponent.setDrivingDimensionCellCount(viewDescriptor.getDrivingDimensionCellCount());
List<RComponent> cells = new ArrayList<>();
BasicCompositeView<RComponent> view = constructCompositeView(viewComponent, viewDescriptor);
List<IView<RComponent>> childrenViews = new ArrayList<>();
for (IViewDescriptor childViewDescriptor : viewDescriptor.getChildViewDescriptors()) {
IView<RComponent> childView = createView(childViewDescriptor, actionHandler, locale);
cells.add(childView.getPeer());
childrenViews.add(childView);
}
viewComponent.setCells(cells.toArray(new RComponent[cells.size()]));
view.setChildren(childrenViews);
return view;
}
/**
* Creates a remote constrained grid container.
*
* @param viewDescriptor
* the component view descriptor.
* @return the created remote component.
*/
protected RConstrainedGridContainer createRConstrainedGridContainer(IConstrainedGridViewDescriptor viewDescriptor) {
return new RConstrainedGridContainer(getGuidGenerator().generateGUID());
}
/**
* Creates a remote even grid container.
*
* @param viewDescriptor
* the component view descriptor.
* @return the created remote component.
*/
protected REvenGridContainer createREvenGridContainer(IEvenGridViewDescriptor viewDescriptor) {
return new REvenGridContainer(getGuidGenerator().generateGUID());
}
/**
* Creates a remote split container.
*
* @param viewDescriptor
* the component view descriptor.
* @return the created remote component.
*/
protected RSplitContainer createRSplitContainer(ISplitViewDescriptor viewDescriptor) {
return new RSplitContainer(getGuidGenerator().generateGUID());
}
/**
* Creates a remote table.
*
* @param viewDescriptor
* the component view descriptor.
* @return the created remote component.
*/
protected RTable createRTable(ITableViewDescriptor viewDescriptor) {
RTable component = new RTable(getGuidGenerator().generateGUID());
return component;
}
/**
* {@inheritDoc}
*/
@Override
protected ICompositeView<RComponent> createSplitView(ISplitViewDescriptor viewDescriptor,
IActionHandler actionHandler, Locale locale) {
RSplitContainer viewComponent = createRSplitContainer(viewDescriptor);
viewComponent.setOrientation(viewDescriptor.getOrientation().name());
BasicCompositeView<RComponent> view = constructCompositeView(viewComponent, viewDescriptor);
List<IView<RComponent>> childrenViews = new ArrayList<>();
if (viewDescriptor.getLeftTopViewDescriptor() != null) {
IView<RComponent> leftTopView = createView(viewDescriptor.getLeftTopViewDescriptor(), actionHandler, locale);
viewComponent.setLeftTop(leftTopView.getPeer());
childrenViews.add(leftTopView);
}
if (viewDescriptor.getRightBottomViewDescriptor() != null) {
IView<RComponent> rightBottomView = createView(viewDescriptor.getRightBottomViewDescriptor(), actionHandler,
locale);
viewComponent.setRightBottom(rightBottomView.getPeer());
childrenViews.add(rightBottomView);
}
view.setChildren(childrenViews);
return view;
}
/**
* {@inheritDoc}
*/
@Override
protected RComponent decorateWithPaginationView(RComponent viewPeer, RComponent paginationViewPeer) {
RBorderContainer decorator = new RBorderContainer(getGuidGenerator().generateGUID());
decorator.setCenter(viewPeer);
decorator.setSouth(paginationViewPeer);
return decorator;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("ConstantConditions")
@Override
protected IView<RComponent> createTableView(ITableViewDescriptor viewDescriptor, IActionHandler actionHandler,
Locale locale) {
ICollectionDescriptorProvider<?> modelDescriptor = ((ICollectionDescriptorProvider<?>) viewDescriptor
.getModelDescriptor());
IComponentDescriptor<?> rowDescriptor = modelDescriptor.getCollectionDescriptor().getElementDescriptor();
ICompositeValueConnector rowConnectorPrototype = getConnectorFactory().createCompositeValueConnector(
modelDescriptor.getName() + "Element", rowDescriptor.getToHtmlProperty());
ICollectionConnector connector = getConnectorFactory().createCollectionConnector(modelDescriptor.getName(),
getMvcBinder(), rowConnectorPrototype);
RTable viewComponent = createRTable(viewDescriptor);
viewComponent.setColumnReorderingAllowed(viewDescriptor.isColumnReorderingAllowed());
IView<RComponent> view = constructView(viewComponent, viewDescriptor, connector);
viewComponent.setSortable(viewDescriptor.isSortable());
if (viewDescriptor.getSortingAction() != null) {
viewComponent.setSortingAction(getActionFactory().createAction(viewDescriptor.getSortingAction(), actionHandler,
view, locale));
}
viewComponent.setHorizontallyScrollable(viewDescriptor.isHorizontallyScrollable());
List<RComponent> columns = new ArrayList<>();
List<RComponent> columnHeaders = new ArrayList<>();
List<String> columnIds = new ArrayList<>();
List<IView<RComponent>> propertyViews = new ArrayList<>();
Map<IPropertyViewDescriptor, Object[]> userColumnViewDescriptors = getUserColumnViewDescriptors(viewDescriptor,
actionHandler);
for (Map.Entry<IPropertyViewDescriptor, Object[]> columnViewDescriptorEntry : userColumnViewDescriptors.entrySet()) {
IPropertyViewDescriptor columnViewDescriptor = columnViewDescriptorEntry.getKey();
if (actionHandler.isAccessGranted(columnViewDescriptor)) {
IView<RComponent> column = createView(columnViewDescriptor, actionHandler, locale);
column.setParent(view);
// Do not use standard createColumnConnector method to preserve
// formatted value connectors.
// IValueConnector columnConnector = createColumnConnector(columnId,
// rowDescriptor);
IValueConnector columnConnector = column.getConnector();
String propertyName = columnViewDescriptor.getModelDescriptor().getName();
rowConnectorPrototype.addChildConnector(propertyName, columnConnector);
boolean locallyWritable = !columnViewDescriptor.isReadOnly();
if (locallyWritable) {
try {
actionHandler.pushToSecurityContext(EAuthorization.ENABLED);
locallyWritable = actionHandler.isAccessGranted(columnViewDescriptor);
} finally {
actionHandler.restoreLastSecurityContextSnapshot();
}
}
if (columnViewDescriptor.getAction() != null && !columnViewDescriptor.isReadOnly()) {
for (IValueChangeListener listener : columnConnector.getValueChangeListeners()) {
if (listener instanceof ConnectorActionAdapter) {
// to avoid the action to be fired by the editor.
columnConnector.removeValueChangeListener(listener);
}
}
// We must listen for incoming connector value change to trigger the
// action.
columnConnector.addValueChangeListener(new ConnectorActionAdapter<>(columnViewDescriptor.getAction(),
getActionFactory(), actionHandler, view));
}
columnConnector.setLocallyWritable(locallyWritable);
IPropertyDescriptor propertyDescriptor = rowDescriptor.getPropertyDescriptor(propertyName);
columns.add(column.getPeer());
RLabel headerLabel = createPropertyLabel(columnViewDescriptor, column.getPeer(), actionHandler, locale);
columnHeaders.add(headerLabel);
if (!columnViewDescriptor.isReadOnly() && propertyDescriptor.isMandatory()
&& !(propertyDescriptor instanceof IBooleanPropertyDescriptor)) {
if (columnViewDescriptor.getLabelForeground() == null) {
headerLabel.setForeground(getTableHeaderMandatoryPropertyColorHex());
}
headerLabel.setLabel(decorateMandatoryPropertyLabel(headerLabel.getLabel()));
}
columnIds.add(computeColumnIdentifier(viewDescriptor, columnViewDescriptor));
if (column.getPeer() instanceof RActionable && columnViewDescriptor.getAction() != null) {
RAction action = getActionFactory().createAction(columnViewDescriptor.getAction(), actionHandler, view,
locale);
configurePropertyViewAction(columnViewDescriptor, action);
((RActionable) column.getPeer()).setAction(action);
}
final Object[] columnCaracteristics = columnViewDescriptorEntry.getValue();
if (columnCaracteristics != null) {
int visibilityHeight = ((boolean) columnCaracteristics[1]) ? 1 : -1;
Dimension columnPreferredSize = new Dimension((Integer) columnCaracteristics[0], visibilityHeight);
column.getPeer().setPreferredSize(columnPreferredSize);
}
propertyViews.add(column);
}
}
completePropertyViewsWithDynamicToolTips(rowConnectorPrototype, propertyViews, rowDescriptor);
completePropertyViewsWithDynamicBackgrounds(rowConnectorPrototype, propertyViews, rowDescriptor);
completePropertyViewsWithDynamicForegrounds(rowConnectorPrototype, propertyViews, rowDescriptor);
completePropertyViewsWithDynamicFonts(rowConnectorPrototype, propertyViews, rowDescriptor);
viewComponent.setColumns(columns.toArray(new RComponent[columns.size()]));
viewComponent.setColumnHeaders(columnHeaders.toArray(new RComponent[columnHeaders.size()]));
viewComponent.setColumnIds(columnIds.toArray(new String[columnIds.size()]));
viewComponent.setSelectionMode(viewDescriptor.getSelectionMode().name());
if (viewDescriptor.getRowAction() != null) {
viewComponent.setRowAction(getActionFactory().createAction(viewDescriptor.getRowAction(), actionHandler, view,
locale));
}
completeViewWithDynamicBackground(viewComponent, viewDescriptor, rowDescriptor, rowConnectorPrototype);
completeViewWithDynamicForeground(viewComponent, viewDescriptor, rowDescriptor, rowConnectorPrototype);
completeViewWithDynamicFont(viewComponent, viewDescriptor, rowDescriptor, rowConnectorPrototype);
if (rowConnectorPrototype instanceof IRemoteStateOwner) {
viewComponent.setRowPrototype((RemoteCompositeValueState) ((IRemoteStateOwner) rowConnectorPrototype).getState());
}
return view;
}
private void completeViewWithDynamicBackground(RTable viewComponent, ITableViewDescriptor viewDescriptor,
IComponentDescriptor<?> rowDescriptor,
ICompositeValueConnector rowConnectorPrototype) {
String dynamicBackgroundProperty = computeComponentDynamicBackground(viewDescriptor, rowDescriptor);
if (dynamicBackgroundProperty != null) {
IValueConnector backgroundConnector = rowConnectorPrototype.getChildConnector(dynamicBackgroundProperty);
if (backgroundConnector == null) {
backgroundConnector = getConnectorFactory().createValueConnector(dynamicBackgroundProperty);
rowConnectorPrototype.addChildConnector(dynamicBackgroundProperty, backgroundConnector);
}
if (backgroundConnector instanceof IRemoteStateOwner) {
viewComponent.setBackgroundState(((IRemoteStateOwner) backgroundConnector).getState());
}
}
}
private void completeViewWithDynamicForeground(RTable viewComponent, ITableViewDescriptor viewDescriptor,
IComponentDescriptor<?> rowDescriptor,
ICompositeValueConnector rowConnectorPrototype) {
String dynamicForegroundProperty = computeComponentDynamicForeground(viewDescriptor, rowDescriptor);
if (dynamicForegroundProperty != null) {
IValueConnector foregroundConnector = rowConnectorPrototype.getChildConnector(dynamicForegroundProperty);
if (foregroundConnector == null) {
foregroundConnector = getConnectorFactory().createValueConnector(dynamicForegroundProperty);
rowConnectorPrototype.addChildConnector(dynamicForegroundProperty, foregroundConnector);
}
if (foregroundConnector instanceof IRemoteStateOwner) {
viewComponent.setForegroundState(((IRemoteStateOwner) foregroundConnector).getState());
}
}
}
private void completeViewWithDynamicFont(RTable viewComponent, ITableViewDescriptor viewDescriptor,
IComponentDescriptor<?> rowDescriptor,
ICompositeValueConnector rowConnectorPrototype) {
String dynamicFontProperty = computeComponentDynamicFont(viewDescriptor, rowDescriptor);
if (dynamicFontProperty != null) {
IValueConnector fontConnector = rowConnectorPrototype.getChildConnector(dynamicFontProperty);
if (fontConnector == null) {
fontConnector = getConnectorFactory().createValueConnector(dynamicFontProperty);
((RemoteValueConnector) fontConnector).setRemoteStateValueMapper(FONT_MAPPER);
rowConnectorPrototype.addChildConnector(dynamicFontProperty, fontConnector);
}
if (fontConnector instanceof IRemoteStateOwner) {
viewComponent.setFontState(((IRemoteStateOwner) fontConnector).getState());
}
}
}
@Override
protected void completePropertyViewsWithDynamicToolTips(ICompositeValueConnector connector,
List<IView<RComponent>> propertyViews,
IComponentDescriptor<?> modelDescriptor) {
// Compute dynamic tooltips
for (IView<RComponent> propertyView : propertyViews) {
IPropertyViewDescriptor propertyViewDescriptor = (IPropertyViewDescriptor) propertyView.getDescriptor();
IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
String dynamicToolTipProperty = computePropertyDynamicToolTip(modelDescriptor, propertyViewDescriptor,
propertyDescriptor);
// Dynamic tooltip
if (dynamicToolTipProperty != null) {
IValueConnector tooltipConnector = connector.getChildConnector(dynamicToolTipProperty);
if (tooltipConnector == null) {
tooltipConnector = getConnectorFactory().createValueConnector(dynamicToolTipProperty);
connector.addChildConnector(dynamicToolTipProperty, tooltipConnector);
}
if (tooltipConnector instanceof IRemoteStateOwner) {
propertyView.getPeer().setToolTipState(((IRemoteStateOwner) tooltipConnector).getState());
}
}
}
}
@Override
protected void completePropertyViewsWithDynamicBackgrounds(ICompositeValueConnector connector,
List<IView<RComponent>> propertyViews,
IComponentDescriptor<?> modelDescriptor) {
// Compute dynamic background
for (IView<RComponent> propertyView : propertyViews) {
IPropertyViewDescriptor propertyViewDescriptor = (IPropertyViewDescriptor) propertyView.getDescriptor();
IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
String dynamicBackgroundProperty = computePropertyDynamicBackground(modelDescriptor, propertyViewDescriptor,
propertyDescriptor);
// Dynamic background
if (dynamicBackgroundProperty != null) {
IValueConnector backgroundConnector = connector.getChildConnector(dynamicBackgroundProperty);
if (backgroundConnector == null) {
backgroundConnector = getConnectorFactory().createValueConnector(dynamicBackgroundProperty);
connector.addChildConnector(dynamicBackgroundProperty, backgroundConnector);
}
if (backgroundConnector instanceof IRemoteStateOwner) {
propertyView.getPeer().setBackgroundState(((IRemoteStateOwner) backgroundConnector).getState());
}
}
}
}
@Override
protected void completePropertyViewsWithDynamicForegrounds(ICompositeValueConnector connector,
List<IView<RComponent>> propertyViews,
IComponentDescriptor<?> modelDescriptor) {
// Compute dynamic foreground
for (IView<RComponent> propertyView : propertyViews) {
IPropertyViewDescriptor propertyViewDescriptor = (IPropertyViewDescriptor) propertyView.getDescriptor();
IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
String dynamicForegroundProperty = computePropertyDynamicForeground(modelDescriptor, propertyViewDescriptor,
propertyDescriptor);
// Dynamic foreground
if (dynamicForegroundProperty != null) {
IValueConnector foregroundConnector = connector.getChildConnector(dynamicForegroundProperty);
if (foregroundConnector == null) {
foregroundConnector = getConnectorFactory().createValueConnector(dynamicForegroundProperty);
connector.addChildConnector(dynamicForegroundProperty, foregroundConnector);
}
if (foregroundConnector instanceof IRemoteStateOwner) {
propertyView.getPeer().setForegroundState(((IRemoteStateOwner) foregroundConnector).getState());
}
}
}
}
@Override
protected void completePropertyViewsWithDynamicFonts(ICompositeValueConnector connector,
List<IView<RComponent>> propertyViews,
IComponentDescriptor<?> modelDescriptor) {
// Compute dynamic font
for (IView<RComponent> propertyView : propertyViews) {
IPropertyViewDescriptor propertyViewDescriptor = (IPropertyViewDescriptor) propertyView.getDescriptor();
IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
String dynamicFontProperty = computePropertyDynamicFont(modelDescriptor, propertyViewDescriptor,
propertyDescriptor);
// Dynamic font
if (dynamicFontProperty != null) {
IValueConnector fontConnector = connector.getChildConnector(dynamicFontProperty);
if (fontConnector == null) {
fontConnector = getConnectorFactory().createValueConnector(dynamicFontProperty);
((RemoteValueConnector) fontConnector).setRemoteStateValueMapper(FONT_MAPPER);
connector.addChildConnector(dynamicFontProperty, fontConnector);
}
if (fontConnector instanceof IRemoteStateOwner) {
propertyView.getPeer().setFontState(((IRemoteStateOwner) fontConnector).getState());
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void selectChildViewIndex(RComponent viewComponent, int index) {
if (viewComponent instanceof RTabContainer) {
RTabContainer rTab = ((RTabContainer) viewComponent);
if (rTab.getSelectedIndex() != index) {
rTab.setSelectedIndex(index);
RemoteSelectionCommand selectionCommand = new RemoteSelectionCommand();
selectionCommand.setTargetPeerGuid(rTab.getGuid());
selectionCommand.setLeadingIndex(index);
getRemoteCommandHandler().registerCommand(selectionCommand);
}
}
}
}