blob: c003ddeb54a4f359cee185e5a2b2805fc964b7ab [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
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.blink;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.nebula.widgets.nattable.blink.command.BlinkTimerEnableCommandHandler;
import org.eclipse.nebula.widgets.nattable.blink.event.BlinkEvent;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyResolver;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;
import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
import org.eclipse.nebula.widgets.nattable.layer.event.PropertyUpdateEvent;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.swt.widgets.Display;
/**
* Blinks cells when they are updated. Returns blinking cell styles for the
* cells which have been updated.
*
* Every time its asked for config labels: Checks the UpdateEventsCache for
* changes to the cell If a cell is updated The cell is tracked as 'blinking'
* and blinking config labels are returned A TimerTask is started which will
* stop the blinking after the blink period is over
*
* @param <T>
* Type of the Bean in the backing {@linkplain IDataProvider}
*/
public class BlinkLayer<T> extends AbstractLayerTransform implements
IUniqueIndexLayer {
private final IUniqueIndexLayer dataLayer;
private final IRowDataProvider<T> rowDataProvider;
private final IConfigRegistry configRegistry;
private final IRowIdAccessor<T> rowIdAccessor;
private final IColumnPropertyResolver columnPropertyResolver;
private final ScheduledExecutorService scheduler;
protected boolean blinkingEnabled = true;
/**
* Cache all the update events allowing the layer to track what got updated
*/
private final UpdateEventsCache<T> updateEventsCache;
/** Duration of a single blink */
private int blinkDurationInMilis = 1000;
/** Track the updates which are currently blinking */
Map<String, PropertyUpdateEvent<T>> blinkingUpdates = new HashMap<>();
/** Track the blinking tasks which are currently running */
Map<String, ScheduledFuture<?>> blinkingTasks = new HashMap<>();
public BlinkLayer(IUniqueIndexLayer dataLayer,
IRowDataProvider<T> listDataProvider,
IRowIdAccessor<T> rowIdAccessor,
IColumnPropertyResolver columnPropertyResolver,
IConfigRegistry configRegistry) {
this(dataLayer, listDataProvider, rowIdAccessor,
columnPropertyResolver, configRegistry, false);
}
public BlinkLayer(IUniqueIndexLayer dataLayer,
IRowDataProvider<T> listDataProvider,
IRowIdAccessor<T> rowIdAccessor,
IColumnPropertyResolver columnPropertyResolver,
IConfigRegistry configRegistry, boolean triggerBlinkOnRowUpdate) {
this(dataLayer, listDataProvider, rowIdAccessor,
columnPropertyResolver, configRegistry,
triggerBlinkOnRowUpdate, Executors
.newSingleThreadScheduledExecutor());
}
public BlinkLayer(IUniqueIndexLayer dataLayer,
IRowDataProvider<T> listDataProvider,
IRowIdAccessor<T> rowIdAccessor,
IColumnPropertyResolver columnPropertyResolver,
IConfigRegistry configRegistry, boolean triggerBlinkOnRowUpdate,
ScheduledExecutorService scheduler) {
super(dataLayer);
this.dataLayer = dataLayer;
this.rowDataProvider = listDataProvider;
this.rowIdAccessor = rowIdAccessor;
this.columnPropertyResolver = columnPropertyResolver;
this.configRegistry = configRegistry;
this.scheduler = scheduler;
this.updateEventsCache = new UpdateEventsCache<>(rowIdAccessor,
triggerBlinkOnRowUpdate ? new RowKeyStrategyImpl()
: new CellKeyStrategyImpl(),
scheduler);
registerCommandHandler(new BlinkTimerEnableCommandHandler(this));
}
@Override
public void dispose() {
super.dispose();
this.scheduler.shutdown();
}
@Override
public LabelStack getConfigLabelsByPosition(int columnPosition,
int rowPosition) {
if (!this.blinkingEnabled) {
return getUnderlyingLayer().getConfigLabelsByPosition(
columnPosition, rowPosition);
}
ILayerCell cell = this.underlyingLayer.getCellByPosition(columnPosition,
rowPosition);
int columnIndex = getUnderlyingLayer().getColumnIndexByPosition(
columnPosition);
String columnProperty = this.columnPropertyResolver
.getColumnProperty(columnIndex);
int rowIndex = getUnderlyingLayer().getRowIndexByPosition(rowPosition);
String rowId = this.rowIdAccessor.getRowId(
this.rowDataProvider.getRowObject(rowIndex)).toString();
String key = this.updateEventsCache.getKey(columnProperty, rowId);
LabelStack underlyingLabelStack = getUnderlyingLayer()
.getConfigLabelsByPosition(columnPosition, rowPosition);
// Cell has been updated
if (this.updateEventsCache.isUpdated(key)) {
PropertyUpdateEvent<T> event = this.updateEventsCache.getEvent(key);
// Old update in middle of a blink - cancel it
ScheduledFuture<?> scheduledFuture = this.blinkingTasks.remove(key);
this.blinkingUpdates.remove(key);
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
LabelStack blinkingConfigTypes = resolveConfigTypes(cell,
event.getOldValue(), event.getNewValue());
// start blinking cell
if (blinkingConfigTypes != null) {
Runnable stopBlinkTask = getStopBlinkTask(key, this);
this.blinkingUpdates.put(key, event);
this.updateEventsCache.remove(key);
this.blinkingTasks.put(key, this.scheduler.schedule(stopBlinkTask,
this.blinkDurationInMilis, TimeUnit.MILLISECONDS));
return blinkingConfigTypes;
} else {
return underlyingLabelStack;
}
}
// Previous blink timer is still running
if (this.blinkingUpdates.containsKey(key)) {
PropertyUpdateEvent<T> event = this.blinkingUpdates.get(key);
return resolveConfigTypes(cell, event.getOldValue(),
event.getNewValue());
}
return underlyingLabelStack;
}
/**
* Checks if there is a {@link IBlinkingCellResolver} registered in the
* {@link ConfigRegistry} and use it to add config type labels associated
* with a blinking cell to the label stack.
*
* @param cell
* the cell
* @param oldValue
* the old value
* @param newValue
* the new value
* @return a LabelStack containing resolved config types associated with the
* cell
*/
public LabelStack resolveConfigTypes(ILayerCell cell, Object oldValue,
Object newValue) {
// Acquire default config types for the coordinate. Use these to search
// for the associated resolver.
LabelStack underlyingLabelStack = this.underlyingLayer
.getConfigLabelsByPosition(cell.getColumnIndex(),
cell.getRowIndex());
IBlinkingCellResolver resolver = this.configRegistry.getConfigAttribute(
BlinkConfigAttributes.BLINK_RESOLVER, DisplayMode.NORMAL,
underlyingLabelStack);
String[] blinkConfigTypes = null;
if (resolver != null) {
blinkConfigTypes = resolver.resolve(cell, this.configRegistry, oldValue,
newValue);
}
if (blinkConfigTypes != null && blinkConfigTypes.length > 0) {
for (String configType : blinkConfigTypes) {
underlyingLabelStack.addLabelOnTop(configType);
}
}
return underlyingLabelStack;
}
/**
* Stops the cell from blinking at the end of the blinking period.
*/
private Runnable getStopBlinkTask(final String key, final ILayer layer) {
return () -> Display.getDefault().asyncExec(() -> {
BlinkLayer.this.blinkingUpdates.remove(key);
BlinkLayer.this.blinkingTasks.remove(key);
fireLayerEvent(new BlinkEvent(layer));
});
}
@SuppressWarnings("unchecked")
@Override
public void handleLayerEvent(ILayerEvent event) {
if (this.blinkingEnabled && event instanceof PropertyUpdateEvent) {
this.updateEventsCache.put((PropertyUpdateEvent<T>) event);
}
super.handleLayerEvent(event);
}
public void setBlinkingEnabled(boolean enabled) {
this.blinkingEnabled = enabled;
}
@Override
public int getColumnPositionByIndex(int columnIndex) {
return this.dataLayer.getColumnPositionByIndex(columnIndex);
}
@Override
public int getRowPositionByIndex(int rowIndex) {
return this.dataLayer.getRowPositionByIndex(rowIndex);
}
public void setBlinkDurationInMilis(int blinkDurationInMilis) {
this.blinkDurationInMilis = blinkDurationInMilis;
}
}