/*******************************************************************************
* Copyright (c) 2012, 2013, 2014 Original authors 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:
* Original authors and others - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 451217
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.freeze;
import java.util.Properties;
import java.util.StringTokenizer;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.freeze.command.FreezeCommandHandler;
import org.eclipse.nebula.widgets.nattable.freeze.config.DefaultFreezeGridBindings;
import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand;
import org.eclipse.nebula.widgets.nattable.grid.layer.DimensionallyDependentIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.event.ColumnStructuralChangeEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralChangeEvent;
import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter;
import org.eclipse.nebula.widgets.nattable.persistence.IPersistable;
import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectColumnCommandHandler;
import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectRowCommandHandler;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
public class CompositeFreezeLayer extends CompositeLayer implements IUniqueIndexLayer {
private final FreezeLayer freezeLayer;
private final ViewportLayer viewportLayer;
private final SelectionLayer selectionLayer;
private final ILayerPainter layerPainter = new FreezableLayerPainter();
public CompositeFreezeLayer(FreezeLayer freezeLayer,
ViewportLayer viewportLayer, SelectionLayer selectionLayer) {
this(freezeLayer, viewportLayer, selectionLayer, true);
}
public CompositeFreezeLayer(FreezeLayer freezeLayer,
ViewportLayer viewportLayer, SelectionLayer selectionLayer,
boolean useDefaultConfiguration) {
super(2, 2);
this.freezeLayer = freezeLayer;
this.viewportLayer = viewportLayer;
this.selectionLayer = selectionLayer;
setChildLayer("FROZEN_REGION", freezeLayer, 0, 0); //$NON-NLS-1$
setChildLayer("FROZEN_ROW_REGION", //$NON-NLS-1$
new DimensionallyDependentIndexLayer(
viewportLayer.getScrollableLayer(), viewportLayer, freezeLayer), 1, 0);
setChildLayer("FROZEN_COLUMN_REGION", //$NON-NLS-1$
new DimensionallyDependentIndexLayer(
viewportLayer.getScrollableLayer(), freezeLayer, viewportLayer), 0, 1);
setChildLayer("NONFROZEN_REGION", viewportLayer, 1, 1); //$NON-NLS-1$
registerCommandHandlers();
if (useDefaultConfiguration) {
addConfiguration(new DefaultFreezeGridBindings());
}
}
@Override
public void handleLayerEvent(ILayerEvent event) {
// Bug 451217
// if structural change events are fired that carry no explicit diff
// information it is likely that the event handlers in the underlying
// layers are not executed. The following code is intended to "repair"
// possible inconsistent freeze-viewport states
if (event instanceof RowStructuralChangeEvent
&& (((RowStructuralChangeEvent) event).getRowDiffs() == null
|| ((RowStructuralChangeEvent) event).getRowDiffs().isEmpty())) {
if (this.viewportLayer.getMinimumOriginRowPosition() < this.freezeLayer.getRowCount()) {
this.viewportLayer.setMinimumOriginY(this.freezeLayer.getHeight());
}
}
if (event instanceof ColumnStructuralChangeEvent
&& (((ColumnStructuralChangeEvent) event).getColumnDiffs() == null
|| ((ColumnStructuralChangeEvent) event).getColumnDiffs().isEmpty())) {
if (this.viewportLayer.getMinimumOriginColumnPosition() < this.freezeLayer.getColumnCount()) {
this.viewportLayer.setMinimumOriginX(this.freezeLayer.getWidth());
}
}
// Bug 470061
// in case the all columns are frozen, we also need to "repair"
// the inconsistent freeze-viewport states, as the viewport is not
// able to update itself since it doesn't handle the structural change
// event
else if (event instanceof ColumnResizeEvent
&& this.freezeLayer.getColumnCount() == this.selectionLayer.getColumnCount()
&& this.viewportLayer.getMinimumOriginColumnPosition() < this.freezeLayer.getColumnCount()) {
this.viewportLayer.setMinimumOriginX(this.freezeLayer.getWidth());
}
super.handleLayerEvent(event);
}
public boolean isFrozen() {
return this.freezeLayer.isFrozen();
}
@Override
public ILayerPainter getLayerPainter() {
return this.layerPainter;
}
@Override
protected void registerCommandHandlers() {
registerCommandHandler(new FreezeCommandHandler(this.freezeLayer,
this.viewportLayer, this.selectionLayer));
final DimensionallyDependentIndexLayer frozenRowLayer =
(DimensionallyDependentIndexLayer) getChildLayerByLayoutCoordinate(1, 0);
frozenRowLayer.registerCommandHandler(
new ViewportSelectRowCommandHandler(frozenRowLayer));
final DimensionallyDependentIndexLayer frozenColumnLayer =
(DimensionallyDependentIndexLayer) getChildLayerByLayoutCoordinate(0, 1);
frozenColumnLayer.registerCommandHandler(
new ViewportSelectColumnCommandHandler(frozenColumnLayer));
}
@Override
public boolean doCommand(ILayerCommand command) {
// if this layer should handle a ClientAreaResizeCommand we have to
// ensure that it is only called on the ViewportLayer, as otherwise
// an undefined behaviour could occur because the ViewportLayer
// isn't informed about potential refreshes
if (command instanceof ClientAreaResizeCommand) {
this.viewportLayer.doCommand(command);
}
return super.doCommand(command);
}
@Override
public int getColumnPositionByIndex(int columnIndex) {
int columnPosition = this.freezeLayer.getColumnPositionByIndex(columnIndex);
if (columnPosition >= 0) {
return columnPosition;
}
return this.freezeLayer.getColumnCount()
+ this.viewportLayer.getColumnPositionByIndex(columnIndex);
}
@Override
public int getRowPositionByIndex(int rowIndex) {
int rowPosition = this.freezeLayer.getRowPositionByIndex(rowIndex);
if (rowPosition >= 0) {
return rowPosition;
}
return this.freezeLayer.getRowCount()
+ this.viewportLayer.getRowPositionByIndex(rowIndex);
}
// Persistence
@Override
public void saveState(String prefix, Properties properties) {
PositionCoordinate coord = this.freezeLayer.getTopLeftPosition();
properties.setProperty(
prefix + FreezeLayer.PERSISTENCE_TOP_LEFT_POSITION,
coord.columnPosition + IPersistable.VALUE_SEPARATOR + coord.rowPosition);
coord = this.freezeLayer.getBottomRightPosition();
properties.setProperty(
prefix + FreezeLayer.PERSISTENCE_BOTTOM_RIGHT_POSITION,
coord.columnPosition + IPersistable.VALUE_SEPARATOR + coord.rowPosition);
super.saveState(prefix, properties);
}
@Override
public void loadState(String prefix, Properties properties) {
String property = properties.getProperty(
prefix + FreezeLayer.PERSISTENCE_TOP_LEFT_POSITION);
PositionCoordinate topLeftPosition = null;
if (property != null) {
StringTokenizer tok = new StringTokenizer(property,
IPersistable.VALUE_SEPARATOR);
String columnPosition = tok.nextToken();
String rowPosition = tok.nextToken();
topLeftPosition = new PositionCoordinate(
this.freezeLayer,
Integer.valueOf(columnPosition),
Integer.valueOf(rowPosition));
}
property = properties.getProperty(
prefix + FreezeLayer.PERSISTENCE_BOTTOM_RIGHT_POSITION);
PositionCoordinate bottomRightPosition = null;
if (property != null) {
StringTokenizer tok =
new StringTokenizer(property, IPersistable.VALUE_SEPARATOR);
String columnPosition = tok.nextToken();
String rowPosition = tok.nextToken();
bottomRightPosition = new PositionCoordinate(
this.freezeLayer,
Integer.valueOf(columnPosition),
Integer.valueOf(rowPosition));
}
// only restore a freeze state if there is one persisted
if (topLeftPosition != null && bottomRightPosition != null) {
if (topLeftPosition.columnPosition == -1
&& topLeftPosition.rowPosition == -1
&& bottomRightPosition.columnPosition == -1
&& bottomRightPosition.rowPosition == -1) {
FreezeHelper.unfreeze(this.freezeLayer, this.viewportLayer);
} else {
FreezeHelper.freeze(this.freezeLayer, this.viewportLayer,
topLeftPosition, bottomRightPosition);
}
}
super.loadState(prefix, properties);
}
class FreezableLayerPainter extends CompositeLayerPainter {
public FreezableLayerPainter() {}
@Override
public void paintLayer(ILayer natLayer, GC gc, int xOffset,
int yOffset, Rectangle rectangle, IConfigRegistry configRegistry) {
super.paintLayer(natLayer, gc, xOffset, yOffset, rectangle, configRegistry);
Color separatorColor = configRegistry.getConfigAttribute(
IFreezeConfigAttributes.SEPARATOR_COLOR,
DisplayMode.NORMAL);
if (separatorColor == null) {
separatorColor = GUIHelper.COLOR_BLUE;
}
gc.setClipping(rectangle);
Color oldFg = gc.getForeground();
gc.setForeground(separatorColor);
final int freezeWidth = CompositeFreezeLayer.this.freezeLayer.getWidth() - 1;
if (freezeWidth > 0) {
gc.drawLine(
xOffset + freezeWidth,
yOffset,
xOffset + freezeWidth,
yOffset + getHeight() - 1);
}
final int freezeHeight = CompositeFreezeLayer.this.freezeLayer.getHeight() - 1;
if (freezeHeight > 0) {
gc.drawLine(
xOffset,
yOffset + freezeHeight,
xOffset + getWidth() - 1,
yOffset + freezeHeight);
}
gc.setForeground(oldFg);
}
}
}