| /******************************************************************************* |
| * 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; |
| } |
| |
| } |