blob: a464be0532fc0ca4f28833fc5ce044621eb5387c [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.examples.examples._103_Events;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.nebula.widgets.nattable.util.ObjectUtils.getRandomNumber;
import java.io.Serializable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataFixture;
import org.eclipse.nebula.widgets.nattable.dataset.fixture.data.RowDataListFixture;
import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample;
import org.eclipse.nebula.widgets.nattable.examples.fixtures.GlazedListsGridLayer;
import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.selection.RowSelectionModel;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.selection.config.RowOnlySelectionBindings;
import org.eclipse.nebula.widgets.nattable.selection.config.RowOnlySelectionConfiguration;
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
/**
* Example to demonstrate rows being added/deleted as the backing data source is
* updated.
*/
public class Real_time_data_updates extends AbstractNatExample {
private int updatesPerSecond = 1;
private final int defaultDatasetSize = 20;
private ScheduledExecutorService scheduledThreadPool;
private NatTable nattable;
private EventList<RowDataFixture> eventList;
public static void main(String[] args) {
StandaloneNatExampleRunner.run(500, 700, new Real_time_data_updates());
}
@Override
public String getDescription() {
return "Grid demonstrates data being added/removed. You can experiment with different data set sizes and update speeds.\n"
+ "Select row by clicking on row header. Row selection is preserved when data is updated and sorted.";
}
/**
* @see GlazedListsGridLayer to see the required stack setup. Basically the
* {@link DataLayer} needs to be wrapped up with a
* {@link GlazedListsEventLayer} and the backing list needs to be an
* {@link EventList}
*/
@Override
public Control createExampleControl(Composite parent) {
this.eventList = GlazedLists.eventList(RowDataListFixture
.getList(this.defaultDatasetSize));
ConfigRegistry configRegistry = new ConfigRegistry();
GlazedListsGridLayer<RowDataFixture> glazedListsGridLayer = new GlazedListsGridLayer<>(
this.eventList, RowDataListFixture.getPropertyNames(),
RowDataListFixture.getPropertyToLabelMap(), configRegistry);
this.nattable = new NatTable(parent, glazedListsGridLayer, false);
this.nattable.setConfigRegistry(configRegistry);
this.nattable.addConfiguration(new DefaultNatTableStyleConfiguration());
this.nattable.addConfiguration(new SingleClickSortConfiguration());
SelectionLayer selectionLayer = glazedListsGridLayer
.getBodyLayerStack()
.getSelectionLayer();
ListDataProvider<RowDataFixture> bodyDataProvider = glazedListsGridLayer
.getBodyDataProvider();
// Select complete rows
RowOnlySelectionConfiguration selectionConfig = new RowOnlySelectionConfiguration();
selectionLayer.addConfiguration(selectionConfig);
this.nattable.addConfiguration(new RowOnlySelectionBindings());
// Preserve selection on updates and sort
selectionLayer.setSelectionModel(new RowSelectionModel<>(
selectionLayer, bodyDataProvider,
new IRowIdAccessor<RowDataFixture>() {
@Override
public Serializable getRowId(RowDataFixture rowObject) {
return rowObject.getSecurity_id();
}
}));
this.nattable.configure();
// Layout widgets
parent.setLayout(new GridLayout(1, true));
this.nattable.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true,
true));
setupTextArea(parent);
setupButtons(parent);
return this.nattable;
}
@Override
public void onStart() {
this.scheduledThreadPool = Executors.newScheduledThreadPool(1);
this.scheduledThreadPool.scheduleAtFixedRate(new ListEventsPublisher(),
100L, 1000L / this.updatesPerSecond, MILLISECONDS);
}
@Override
public void onStop() {
this.scheduledThreadPool.shutdown();
this.nattable.dispose();
}
/**
* Utility class to make periodic modifications to the EventList. Each time
* its run, it randomly either removes an element or adds one.
*/
class ListEventsPublisher implements Runnable {
@Override
public void run() {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
try {
Real_time_data_updates.this.eventList.getReadWriteLock().writeLock().lock();
// Delete a random entry
int elementIndexToRemove = getRandomNumber(Real_time_data_updates.this.eventList
.size() - 1);
elementIndexToRemove = elementIndexToRemove < 0 ? 0
: elementIndexToRemove;
// Random decision
boolean removeElements = (getRandomNumber(1000) % 2) == 0;
if (removeElements) {
final RowDataFixture remove = Real_time_data_updates.this.eventList
.remove(elementIndexToRemove);
log("Removed record: " + remove.getSecurity_id());
} else {
final RowDataFixture added = RowDataFixture
.getInstance("Added by test", ("AAA"));
Real_time_data_updates.this.eventList.add(added);
log("Added record: " + added.getSecurity_id());
}
} catch (Exception e) {
log("Ignoring exception: " + e.getMessage());
} finally {
Real_time_data_updates.this.eventList.getReadWriteLock().writeLock().unlock();
}
}
});
}
}
/**
* Adds the 'clear' button at the bottom
*/
private void setupButtons(Composite parent) {
Composite buttonComposite = new Composite(parent, SWT.NONE);
buttonComposite.setLayout(new GridLayout(3, false));
buttonComposite.setLayoutData(new GridData(GridData.FILL,
GridData.FILL, true, false));
new Label(buttonComposite, SWT.NONE).setText("Clear and add records");
final Text clearText = new Text(buttonComposite, SWT.BORDER);
clearText.setText(Integer.toString(this.defaultDatasetSize));
clearText.setLayoutData(new GridData(100, 14));
clearText.setSize(100, 20);
Button clearButton = new Button(buttonComposite, SWT.PUSH);
clearButton.setText("Apply");
clearButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
final Integer newRecordCount = Integer.valueOf(clearText
.getText());
try {
Real_time_data_updates.this.eventList.getReadWriteLock().writeLock().lock();
Real_time_data_updates.this.eventList.clear();
Real_time_data_updates.this.eventList.addAll(RowDataListFixture.getList(newRecordCount));
} finally {
Real_time_data_updates.this.eventList.getReadWriteLock().writeLock().unlock();
log(">> List cleared. Added " + newRecordCount
+ " new records.");
}
}
});
new Label(buttonComposite, SWT.PUSH).setText("Updates/sec");
final Text updateSpeedText = new Text(buttonComposite, SWT.BORDER);
updateSpeedText.setLayoutData(new GridData(100, 14));
updateSpeedText.setText(Integer.toString(this.updatesPerSecond));
Button updateSpeedButton = new Button(buttonComposite, SWT.PUSH);
updateSpeedButton.setText("Apply");
updateSpeedButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Real_time_data_updates.this.updatesPerSecond = Integer.parseInt(updateSpeedText.getText());
log(">> Update speed: " + Real_time_data_updates.this.updatesPerSecond);
Real_time_data_updates.this.scheduledThreadPool.shutdownNow();
onStart();
}
});
final Button pauseButton = new Button(buttonComposite, SWT.PUSH);
pauseButton.setLayoutData(new GridData(100, 20));
pauseButton.setText("Pause updates");
pauseButton.addSelectionListener(new SelectionAdapter() {
boolean updatesPaused = false;
@Override
public void widgetSelected(SelectionEvent e) {
if (this.updatesPaused) {
log(">> Starting updates");
this.updatesPaused = false;
pauseButton.setText("Pause updates");
onStart();
} else {
log(">> Pausing updates");
this.updatesPaused = true;
pauseButton.setText("Start updates");
Real_time_data_updates.this.scheduledThreadPool.shutdownNow();
}
}
});
}
}