blob: 9254ed7df8dae67e727560bcf8cb0d4b2429d77d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2020 Original authors and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Original authors and others - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - made commandHandlers and eventHandlers
* visible to subclasses for testing
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 447145
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.layer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IConfiguration;
import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelProvider;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.layer.cell.LayerCell;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEventHandler;
import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
import org.eclipse.nebula.widgets.nattable.painter.layer.GridLineCellLayerPainter;
import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter;
import org.eclipse.nebula.widgets.nattable.persistence.IPersistable;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
import org.eclipse.nebula.widgets.nattable.util.IClientAreaProvider;
import org.eclipse.swt.graphics.Rectangle;
/**
* Base layer implementation with common methods for managing listeners and
* caching, etc.
*/
public abstract class AbstractLayer implements ILayer {
private String regionName;
protected ILayerPainter layerPainter;
private IClientAreaProvider clientAreaProvider = IClientAreaProvider.DEFAULT;
private IConfigLabelAccumulator configLabelAccumulator;
protected final Map<Class<? extends ILayerCommand>, ILayerCommandHandler<? extends ILayerCommand>> commandHandlers =
new LinkedHashMap<Class<? extends ILayerCommand>, ILayerCommandHandler<? extends ILayerCommand>>();
protected Map<Class<? extends ILayerEvent>, ILayerEventHandler<? extends ILayerEvent>> eventHandlers =
new HashMap<Class<? extends ILayerEvent>, ILayerEventHandler<? extends ILayerEvent>>();
private final List<IPersistable> persistables = new LinkedList<IPersistable>();
private final Set<ILayerListener> listeners = new LinkedHashSet<ILayerListener>();
private final Collection<IConfiguration> configurations = new LinkedList<IConfiguration>();
private boolean configurationApplied = false;
/**
* {@link ReadWriteLock} that is used to ensure that no concurrent
* modifications happen on event handling
*
* @since 1.4
*/
protected ReadWriteLock eventHelperLock = new ReentrantReadWriteLock();
// Dispose
@Override
public void dispose() {
}
// Regions
@Override
public LabelStack getRegionLabelsByXY(int x, int y) {
LabelStack regionLabels = new LabelStack();
if (this.regionName != null) {
regionLabels.addLabel(this.regionName);
}
return regionLabels;
}
public String getRegionName() {
return this.regionName;
}
public void setRegionName(String regionName) {
this.regionName = regionName;
}
// Config lables
@Override
public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {
LabelStack configLabels = new LabelStack();
if (this.configLabelAccumulator != null) {
this.configLabelAccumulator.accumulateConfigLabels(configLabels, columnPosition, rowPosition);
}
if (this.regionName != null) {
configLabels.addLabel(this.regionName);
}
return configLabels;
}
public IConfigLabelAccumulator getConfigLabelAccumulator() {
return this.configLabelAccumulator;
}
public void setConfigLabelAccumulator(IConfigLabelAccumulator cellLabelAccumulator) {
this.configLabelAccumulator = cellLabelAccumulator;
}
// Persistence
@Override
public void saveState(String prefix, Properties properties) {
for (IPersistable persistable : this.persistables) {
persistable.saveState(prefix, properties);
}
}
@Override
public void loadState(String prefix, Properties properties) {
for (IPersistable persistable : this.persistables) {
persistable.loadState(prefix, properties);
}
}
@Override
public void registerPersistable(IPersistable persistable) {
this.persistables.add(persistable);
}
@Override
public void unregisterPersistable(IPersistable persistable) {
this.persistables.remove(persistable);
}
// Configuration
public void addConfiguration(IConfiguration configuration) {
this.configurations.add(configuration);
}
public void clearConfiguration() {
this.configurations.clear();
}
@Override
public void configure(IConfigRegistry configRegistry, UiBindingRegistry uiBindingRegistry) {
if (!this.configurationApplied) {
for (IConfiguration configuration : this.configurations) {
configuration.configureLayer(this);
configuration.configureRegistry(configRegistry);
configuration.configureUiBindings(uiBindingRegistry);
}
// Bug 447145
// to avoid registering configuration values multiple times, we
// remember that this layer has already been configured
this.configurationApplied = true;
}
}
// Commands
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean doCommand(ILayerCommand command) {
for (Class<? extends ILayerCommand> commandClass : this.commandHandlers.keySet()) {
if (commandClass.isInstance(command)) {
ILayerCommandHandler commandHandler = this.commandHandlers.get(commandClass);
if (commandHandler.doCommand(this, command)) {
return true;
}
}
}
return false;
}
// Command handlers
/**
* Layers should use this method to register their command handlers and call
* it from their constructor. This allows easy overriding if required of
* command handlers
*/
protected void registerCommandHandlers() {
// No op
}
@Override
public void registerCommandHandler(ILayerCommandHandler<?> commandHandler) {
this.commandHandlers.put(commandHandler.getCommandClass(), commandHandler);
}
@Override
public void unregisterCommandHandler(Class<? extends ILayerCommand> commandClass) {
this.commandHandlers.remove(commandClass);
}
// Events
@Override
public void addLayerListener(ILayerListener listener) {
this.listeners.add(listener);
}
@Override
public void removeLayerListener(ILayerListener listener) {
this.listeners.remove(listener);
}
@Override
public boolean hasLayerListener(Class<? extends ILayerListener> layerListenerClass) {
for (ILayerListener listener : this.listeners) {
if (listener.getClass().equals(layerListenerClass)) {
return true;
}
}
return false;
}
/**
* Handle layer event notification. Convert it to your context and propagate
* <i>UP</i>.
*
* If you override this method you <strong>MUST NOT FORGET</strong> to raise
* the event up the layer stack by calling
* <code>super.fireLayerEvent(event)</code> - unless you plan to eat the
* event yourself.
**/
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void handleLayerEvent(ILayerEvent event) {
Map<Class<? extends ILayerEvent>, ILayerEventHandler<? extends ILayerEvent>> currentEventHandlers;
this.eventHelperLock.readLock().lock();
try {
currentEventHandlers = this.eventHandlers;
} finally {
this.eventHelperLock.readLock().unlock();
}
for (Class<? extends ILayerEvent> eventClass : currentEventHandlers.keySet()) {
if (eventClass.isInstance(event)) {
ILayerEventHandler eventHandler = currentEventHandlers.get(eventClass);
eventHandler.handleLayerEvent(event);
}
}
// Pass on the event to our parent
if (event.convertToLocal(this)) {
fireLayerEvent(event);
}
}
public void registerEventHandler(ILayerEventHandler<?> eventHandler) {
this.eventHelperLock.writeLock().lock();
try {
this.eventHandlers = new HashMap<Class<? extends ILayerEvent>, ILayerEventHandler<? extends ILayerEvent>>(this.eventHandlers);
this.eventHandlers.put(eventHandler.getLayerEventClass(), eventHandler);
} finally {
this.eventHelperLock.writeLock().unlock();
}
}
public void unregisterEventHandler(ILayerEventHandler<?> eventHandler) {
this.eventHelperLock.writeLock().lock();
try {
this.eventHandlers = new HashMap<Class<? extends ILayerEvent>, ILayerEventHandler<? extends ILayerEvent>>(this.eventHandlers);
this.eventHandlers.remove(eventHandler.getLayerEventClass());
} finally {
this.eventHelperLock.writeLock().unlock();
}
}
/**
* Pass the event to all the {@link ILayerListener} registered on this
* layer. A cloned copy is passed to each listener.
*/
@Override
public void fireLayerEvent(ILayerEvent event) {
if (this.listeners.size() > 0) {
Iterator<ILayerListener> it = this.listeners.iterator();
boolean isLastListener = false;
do {
ILayerListener l = it.next();
isLastListener = !it.hasNext(); // Lookahead
// Fire cloned event to first n-1 listeners; fire original event
// to last listener
ILayerEvent eventToFire = isLastListener ? event : event.cloneEvent();
l.handleLayerEvent(eventToFire);
} while (!isLastListener);
}
}
/**
* @return {@link ILayerPainter}. Defaults to
* {@link GridLineCellLayerPainter}
*/
@Override
public ILayerPainter getLayerPainter() {
if (this.layerPainter == null) {
this.layerPainter = new GridLineCellLayerPainter();
}
return this.layerPainter;
}
public void setLayerPainter(ILayerPainter layerPainter) {
this.layerPainter = layerPainter;
}
// Client area
@Override
public IClientAreaProvider getClientAreaProvider() {
return this.clientAreaProvider;
}
@Override
public void setClientAreaProvider(IClientAreaProvider clientAreaProvider) {
this.clientAreaProvider = clientAreaProvider;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
@Override
public ILayerCell getCellByPosition(int columnPosition, int rowPosition) {
if (columnPosition < 0 || columnPosition >= getColumnCount()
|| rowPosition < 0 || rowPosition >= getRowCount()) {
return null;
}
return new LayerCell(this, columnPosition, rowPosition);
}
@Override
public Rectangle getBoundsByPosition(int columnPosition, int rowPosition) {
ILayerCell cell = getCellByPosition(columnPosition, rowPosition);
ILayer cellLayer = cell.getLayer();
int xOffset = -1;
int yOffset = -1;
int width = 0;
int height = 0;
{
int column = cell.getOriginColumnPosition();
int end = column + cell.getColumnSpan();
for (; column < end; column++) {
int columnOffset = cellLayer.getStartXOfColumnPosition(column);
if (column < cellLayer.getColumnCount()) {
xOffset = columnOffset;
break;
}
}
for (; column < end; column++) {
width += cellLayer.getColumnWidthByPosition(column);
}
}
{
int row = cell.getOriginRowPosition();
int end = row + cell.getRowSpan();
for (; row < end; row++) {
int rowOffset = cellLayer.getStartYOfRowPosition(row);
if (row < cellLayer.getRowCount()) {
yOffset = rowOffset;
break;
}
}
for (; row < end; row++) {
height += cellLayer.getRowHeightByPosition(row);
}
}
return new Rectangle(xOffset, yOffset, width, height);
}
@Override
public String getDisplayModeByPosition(int columnPosition, int rowPosition) {
return DisplayMode.NORMAL;
}
@Override
public ICellPainter getCellPainter(
int columnPosition, int rowPosition,
ILayerCell cell, IConfigRegistry configRegistry) {
return configRegistry.getConfigAttribute(
CellConfigAttributes.CELL_PAINTER,
cell.getDisplayMode(),
cell.getConfigLabels());
}
/**
* @return <code>true</code> if the layer has a dynamic size (e.g. viewport)
* or a fixed size.
* @since 1.4
*/
@Override
public boolean isDynamicSizeLayer() {
return false;
}
/**
* @return The collection of labels that are provided by this layer.
* @since 1.4
*/
@Override
public Collection<String> getProvidedLabels() {
Collection<String> labels = null;
if (getUnderlyingLayerByPosition(0, 0) != null) {
labels = getUnderlyingLayerByPosition(0, 0).getProvidedLabels();
} else {
labels = new LinkedHashSet<>();
}
// add the region
if (this.regionName != null) {
labels.add(this.regionName);
}
// add the labels configured via IConfigLabelAccumulator
if (this.configLabelAccumulator != null
&& this.configLabelAccumulator instanceof IConfigLabelProvider) {
labels.addAll(((IConfigLabelProvider) this.configLabelAccumulator).getProvidedLabels());
}
return labels;
}
}