| /*=============================================================================# |
| # Copyright (c) 2010, 2019 Stephan Wahlbrink 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, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.r.ui.dataeditor; |
| |
| import static org.eclipse.statet.ecommons.waltable.coordinate.Orientation.HORIZONTAL; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Collection; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jface.operation.IRunnableContext; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StackLayout; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.ui.progress.IProgressService; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.ts.core.SystemRunnable; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| import org.eclipse.statet.jcommons.ts.core.ToolRunnable; |
| import org.eclipse.statet.jcommons.ts.core.ToolService; |
| |
| import org.eclipse.statet.ecommons.collections.FastList; |
| import org.eclipse.statet.ecommons.ui.util.LayoutUtils; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.waltable.NatTable; |
| import org.eclipse.statet.ecommons.waltable.command.ILayerCommandHandler; |
| import org.eclipse.statet.ecommons.waltable.config.CellConfigAttributes; |
| import org.eclipse.statet.ecommons.waltable.config.IConfigRegistry; |
| import org.eclipse.statet.ecommons.waltable.config.LayoutSizeConfig; |
| import org.eclipse.statet.ecommons.waltable.coordinate.LRange; |
| import org.eclipse.statet.ecommons.waltable.coordinate.LRangeList; |
| import org.eclipse.statet.ecommons.waltable.coordinate.Orientation; |
| import org.eclipse.statet.ecommons.waltable.coordinate.PositionCoordinate; |
| import org.eclipse.statet.ecommons.waltable.coordinate.PositionId; |
| import org.eclipse.statet.ecommons.waltable.copy.CopyToClipboardCommandHandler; |
| import org.eclipse.statet.ecommons.waltable.data.ControlData; |
| import org.eclipse.statet.ecommons.waltable.data.IDataProvider; |
| import org.eclipse.statet.ecommons.waltable.data.ISpanningDataProvider; |
| import org.eclipse.statet.ecommons.waltable.freeze.CompositeFreezeLayer; |
| import org.eclipse.statet.ecommons.waltable.freeze.FreezeLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.GridRegion; |
| import org.eclipse.statet.ecommons.waltable.grid.cell.AlternatingRowConfigLabelAccumulator; |
| import org.eclipse.statet.ecommons.waltable.grid.data.DefaultCornerDataProvider; |
| import org.eclipse.statet.ecommons.waltable.grid.labeled.ExtColumnHeaderLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.labeled.ExtGridLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.labeled.ExtRowHeaderLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.labeled.LabelCornerLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.layer.ColumnHeaderLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.layer.CornerLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.layer.GridLayer; |
| import org.eclipse.statet.ecommons.waltable.grid.layer.RowHeaderLayer; |
| import org.eclipse.statet.ecommons.waltable.layer.DataLayer; |
| import org.eclipse.statet.ecommons.waltable.layer.ILayerListener; |
| import org.eclipse.statet.ecommons.waltable.layer.SpanningDataLayer; |
| import org.eclipse.statet.ecommons.waltable.layer.cell.AggregrateConfigLabelAccumulator; |
| import org.eclipse.statet.ecommons.waltable.layer.event.ILayerEvent; |
| import org.eclipse.statet.ecommons.waltable.layer.event.IVisualChangeEvent; |
| import org.eclipse.statet.ecommons.waltable.layer.event.RowStructuralRefreshEvent; |
| import org.eclipse.statet.ecommons.waltable.layer.event.RowUpdateEvent; |
| import org.eclipse.statet.ecommons.waltable.resize.InitializeAutoResizeCommandHandler; |
| import org.eclipse.statet.ecommons.waltable.selection.ISelectionEvent; |
| import org.eclipse.statet.ecommons.waltable.selection.SelectAllCommand; |
| import org.eclipse.statet.ecommons.waltable.selection.SelectDimPositionsCommand; |
| import org.eclipse.statet.ecommons.waltable.selection.SelectRelativeCommandHandler; |
| import org.eclipse.statet.ecommons.waltable.selection.SelectionLayer; |
| import org.eclipse.statet.ecommons.waltable.sort.ClearSortCommand; |
| import org.eclipse.statet.ecommons.waltable.sort.ClearSortCommandHandler; |
| import org.eclipse.statet.ecommons.waltable.sort.ISortModel; |
| import org.eclipse.statet.ecommons.waltable.sort.SortDimPositionCommand; |
| import org.eclipse.statet.ecommons.waltable.sort.SortDirection; |
| import org.eclipse.statet.ecommons.waltable.sort.SortHeaderLayer; |
| import org.eclipse.statet.ecommons.waltable.sort.SortPositionCommandHandler; |
| import org.eclipse.statet.ecommons.waltable.style.DisplayMode; |
| import org.eclipse.statet.ecommons.waltable.tickupdate.config.DefaultTickUpdateConfiguration; |
| import org.eclipse.statet.ecommons.waltable.ui.ITableUIContext; |
| import org.eclipse.statet.ecommons.waltable.viewport.IViewportDim; |
| import org.eclipse.statet.ecommons.waltable.viewport.ViewportLayer; |
| |
| import org.eclipse.statet.internal.r.ui.dataeditor.AbstractRDataProvider; |
| import org.eclipse.statet.internal.r.ui.dataeditor.FTableDataProvider; |
| import org.eclipse.statet.internal.r.ui.dataeditor.FindTask; |
| import org.eclipse.statet.internal.r.ui.dataeditor.IFindFilter; |
| import org.eclipse.statet.internal.r.ui.dataeditor.IFindListener; |
| import org.eclipse.statet.internal.r.ui.dataeditor.RDataFormatter; |
| import org.eclipse.statet.internal.r.ui.dataeditor.RDataFormatterConverter; |
| import org.eclipse.statet.internal.r.ui.dataeditor.RDataFrameDataProvider; |
| import org.eclipse.statet.internal.r.ui.dataeditor.RDataTableContentDescription; |
| import org.eclipse.statet.internal.r.ui.dataeditor.RMatrixDataProvider; |
| import org.eclipse.statet.internal.r.ui.dataeditor.RVectorDataProvider; |
| import org.eclipse.statet.internal.r.ui.dataeditor.ResolveCellIndexes; |
| import org.eclipse.statet.internal.r.ui.intable.PresentationConfig; |
| import org.eclipse.statet.internal.r.ui.intable.RDataLayer; |
| import org.eclipse.statet.internal.r.ui.intable.TableLayers; |
| import org.eclipse.statet.internal.r.ui.intable.UIBindings; |
| import org.eclipse.statet.r.ui.RUI; |
| import org.eclipse.statet.rj.data.RArray; |
| import org.eclipse.statet.rj.data.RCharacterStore; |
| import org.eclipse.statet.rj.data.RDataFrame; |
| import org.eclipse.statet.rj.data.RDataUtils; |
| import org.eclipse.statet.rj.data.RObject; |
| import org.eclipse.statet.rj.data.RObjectFactory; |
| import org.eclipse.statet.rj.data.RVector; |
| import org.eclipse.statet.rj.data.UnexpectedRDataException; |
| import org.eclipse.statet.rj.services.FunctionCall; |
| import org.eclipse.statet.rj.ts.core.RToolService; |
| |
| |
| public class RDataTableComposite extends Composite implements ISelectionProvider { |
| |
| |
| private class FindListener implements IFindListener { |
| |
| @Override |
| public void handleFindEvent(final FindEvent event) { |
| if (RDataTableComposite.this.tableLayers != null) { |
| if (event.rowIdx >= 0) { |
| RDataTableComposite.this.tableLayers.setAnchor(event.colIdx, event.rowIdx, true); |
| } |
| for (final IFindListener listener : RDataTableComposite.this.findListeners.toArray()) { |
| listener.handleFindEvent(event); |
| } |
| } |
| } |
| |
| } |
| |
| private class SelectionFindFilter implements IFindFilter { |
| |
| @Override |
| public boolean match(final long rowIdx, final long columnIdx) { |
| if (RDataTableComposite.this.tableLayers != null) { |
| if (columnIdx >= 0) { |
| return RDataTableComposite.this.tableLayers.selectionLayer.isCellPositionSelected(columnIdx, rowIdx); |
| } |
| else { |
| return RDataTableComposite.this.tableLayers.selectionLayer.isRowPositionSelected(rowIdx); |
| } |
| } |
| return false; |
| } |
| |
| } |
| |
| private class SetAnchorByDataIndexes extends ResolveCellIndexes { |
| |
| public SetAnchorByDataIndexes(final AbstractRDataProvider<?> dataProvider) { |
| super(dataProvider); |
| } |
| |
| |
| @Override |
| protected void execute(final long columnIndex, final long rowIndex) { |
| RDataTableComposite.this.display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (getDataProvider() != RDataTableComposite.this.dataProvider) { |
| return; |
| } |
| setAnchorViewIdxs(columnIndex, rowIndex); |
| } |
| }); |
| } |
| |
| } |
| |
| private class RUIContext implements ITableUIContext { |
| |
| |
| public RUIContext() { |
| } |
| |
| |
| @Override |
| public void run(final boolean fork, final boolean cancelable, |
| final IRunnableWithProgress runnable) |
| throws InvocationTargetException, InterruptedException { |
| final IRunnableContext runnableContext= RDataTableComposite.this |
| .callbacks.getServiceLocator().getService(IProgressService.class); |
| runnableContext.run(fork, cancelable, new IRunnableWithProgress() { |
| @Override |
| public void run(final IProgressMonitor monitor) |
| throws InvocationTargetException, InterruptedException { |
| RDataTableComposite.this.dataProvider.beginOperation(this); |
| try { |
| runnable.run(monitor); |
| } |
| finally { |
| RDataTableComposite.this.dataProvider.endOperation(this); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void show(final IStatus status) { |
| StatusManager.getManager().handle(status, StatusManager.SHOW); |
| } |
| |
| } |
| |
| |
| private final Display display; |
| |
| private final StackLayout layout; |
| |
| private final Label messageControl; |
| |
| private Composite reloadControl; |
| |
| private final IRDataTableCallbacks callbacks; |
| |
| private IRDataTableInput input; |
| |
| private AbstractRDataProvider<?> dataProvider; |
| private TableLayers tableLayers; |
| private boolean tableInitialized; |
| |
| private PositionCoordinate currentAnchor; |
| private PositionCoordinate currentLastSelectedCell; |
| private RDataTableSelection selection; |
| private final Object selectionLock= new Object(); |
| private boolean selectionUpdateScheduled; |
| private long selectionUpdateScheduleStamp; |
| private boolean selectionCheckLabel; |
| private final Runnable selectionUpdateRunnable= new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed()) { |
| return; |
| } |
| updateSelection(); |
| } |
| }; |
| private final FastList<ISelectionChangedListener> selectionListeners= new FastList<>(ISelectionChangedListener.class); |
| private final FastList<IFindListener> findListeners= new FastList<>(IFindListener.class); |
| private final FastList<IRDataTableListener> tableListeners= new FastList<>(IRDataTableListener.class); |
| |
| private final RDataFormatter formatter= new RDataFormatter(); |
| |
| private ResolveCellIndexes setAnchorByData; |
| |
| private final FindListener findListener= new FindListener(); |
| |
| |
| /** |
| * @param parent a widget which will be the parent of the new instance (cannot be null) |
| */ |
| public RDataTableComposite(final Composite parent, final IRDataTableCallbacks callbacks) { |
| super(parent, SWT.NONE); |
| |
| if (callbacks == null) { |
| throw new NullPointerException("callbacks"); //$NON-NLS-1$ |
| } |
| |
| |
| this.display= getDisplay(); |
| this.callbacks= callbacks; |
| |
| this.layout= new StackLayout(); |
| setLayout(this.layout); |
| |
| this.messageControl= new Label(this, SWT.NONE); |
| showDummy("Preparing..."); |
| } |
| |
| |
| protected void initTable(final IRDataTableInput input, |
| final AbstractRDataProvider<? extends RObject> dataProvider) { |
| this.input= input; |
| this.dataProvider= dataProvider; |
| |
| final PresentationConfig presentation= PresentationConfig.getInstance(this.display); |
| |
| final TableLayers layers= new TableLayers(); |
| |
| layers.dataLayer= new RDataLayer(dataProvider, presentation.getBaseSizeConfig()); |
| |
| if (!this.dataProvider.getAllColumnsEqual()) { |
| // final ColumnOverrideLabelAccumulator columnLabelAccumulator = |
| // new ColumnOverrideLabelAccumulator(dataLayer); |
| // for (long i= 0; i < fDataProvider.getColumnCount(); i++) { |
| // columnLabelAccumulator.registerColumnOverrides(i, ColumnLabelAccumulator.COLUMN_LABEL_PREFIX + i); |
| // } |
| final AggregrateConfigLabelAccumulator aggregateLabelAccumulator = |
| new AggregrateConfigLabelAccumulator(); |
| // aggregateLabelAccumulator.add(columnLabelAccumulator); |
| // dataLayer.setConfigLabelAccumulator(aggregateLabelAccumulator); |
| } |
| |
| // final WColumnReorderLayer columnReorderLayer= new WColumnReorderLayer(dataLayer); |
| // final ColumnHideShowLayer columnHideShowLayer= new ColumnHideShowLayer(columnReorderLayer); |
| |
| layers.selectionLayer= new SelectionLayer(layers.dataLayer, false); |
| layers.selectionLayer.addConfiguration(new UIBindings.SelectionConfiguration()); |
| layers.selectionLayer.addConfiguration(new DefaultTickUpdateConfiguration()); |
| |
| layers.viewportLayer= new ViewportLayer(layers.selectionLayer); |
| |
| { final FreezeLayer freezeLayer= new FreezeLayer(layers.selectionLayer); |
| final CompositeFreezeLayer compositeFreezeLayer= new CompositeFreezeLayer(freezeLayer, |
| layers.viewportLayer, layers.selectionLayer, true); |
| layers.topBodyLayer= compositeFreezeLayer; |
| } |
| |
| { final IDataProvider headerDataProvider= dataProvider.getColumnDataProvider(); |
| layers.dataColumnHeaderLayer= (headerDataProvider instanceof ISpanningDataProvider) ? |
| new SpanningDataLayer((ISpanningDataProvider) headerDataProvider, |
| PositionId.BODY_CAT, 10, |
| PositionId.HEADER_CAT, 10 ) : |
| new DataLayer(headerDataProvider, |
| PositionId.BODY_CAT, 10, |
| PositionId.HEADER_CAT, 10 ); |
| |
| final ColumnHeaderLayer layer= new ColumnHeaderLayer( |
| layers.dataColumnHeaderLayer, |
| layers.topBodyLayer, layers.selectionLayer, |
| false, presentation.getHeaderLayerPainter() ); |
| layer.addConfiguration(new UIBindings.ColumnHeaderConfiguration()); |
| layers.topColumnHeaderLayer= layer; |
| } |
| final ISortModel sortModel= dataProvider.getSortModel(); |
| if (sortModel != null) { |
| final SortHeaderLayer<?> sortHeaderLayer= new SortHeaderLayer<>( |
| layers.topColumnHeaderLayer, sortModel, false); |
| sortHeaderLayer.addConfiguration(new UIBindings.SortConfiguration()); |
| layers.topColumnHeaderLayer= sortHeaderLayer; |
| } |
| { final IDataProvider headerDataProvider= dataProvider.getRowDataProvider(); |
| layers.dataRowHeaderLayer= (headerDataProvider instanceof ISpanningDataProvider) ? |
| new SpanningDataLayer((ISpanningDataProvider) headerDataProvider, |
| PositionId.HEADER_CAT, 10, |
| PositionId.BODY_CAT, 10 ) : |
| new DataLayer(headerDataProvider, |
| PositionId.HEADER_CAT, 10, |
| PositionId.BODY_CAT, 10 ); |
| |
| layers.topRowHeaderLayer= new RowHeaderLayer( |
| layers.dataRowHeaderLayer, |
| layers.topBodyLayer, layers.selectionLayer, |
| false, presentation.getHeaderLayerPainter() ); |
| } |
| final IDataProvider cornerDataProvider= new DefaultCornerDataProvider( |
| layers.dataColumnHeaderLayer.getDataProvider(), |
| layers.dataRowHeaderLayer.getDataProvider() ); |
| |
| final GridLayer gridLayer; |
| if (dataProvider.getColumnLabelProvider() != null || dataProvider.getRowLabelProvider() != null) { |
| layers.topColumnHeaderLayer= new ExtColumnHeaderLayer(layers.topColumnHeaderLayer); |
| layers.topRowHeaderLayer= new ExtRowHeaderLayer(layers.topRowHeaderLayer); |
| final CornerLayer cornerLayer= new LabelCornerLayer( |
| new DataLayer(cornerDataProvider, |
| PositionId.HEADER_CAT, 10, |
| PositionId.HEADER_CAT, 10 ), |
| layers.topRowHeaderLayer, layers.topColumnHeaderLayer, |
| dataProvider.getColumnLabelProvider(), dataProvider.getRowLabelProvider(), |
| false, presentation.getHeaderLabelLayerPainter() ); |
| gridLayer= new ExtGridLayer(layers.topBodyLayer, |
| layers.topColumnHeaderLayer, layers.topRowHeaderLayer, |
| cornerLayer, false ); |
| } |
| else { |
| final CornerLayer cornerLayer= new CornerLayer( |
| new DataLayer(cornerDataProvider, PositionId.HEADER_CAT), |
| layers.topRowHeaderLayer, layers.topColumnHeaderLayer, |
| false, presentation.getHeaderLayerPainter() ); |
| gridLayer= new GridLayer(layers.topBodyLayer, |
| layers.topColumnHeaderLayer, layers.topRowHeaderLayer, cornerLayer, false); |
| } |
| gridLayer.addConfigLabelAccumulatorForRegion(GridRegion.BODY, new AlternatingRowConfigLabelAccumulator()); |
| |
| final Runnable configRunnable= new Runnable() { |
| @Override |
| public void run() { |
| final TableLayers layers= RDataTableComposite.this.tableLayers; |
| if (layers == null) { |
| return; |
| } |
| |
| final LayoutSizeConfig sizeConfig= presentation.getBaseSizeConfig(); |
| |
| layers.dataLayer.setSizeConfig(sizeConfig); |
| layers.dataColumnHeaderLayer.setDefaultRowHeight(sizeConfig.getRowHeight()); |
| layers.dataRowHeaderLayer.setDefaultColumnWidth(sizeConfig.getCharWidth() * 8 + sizeConfig.getDefaultSpace() * 2); |
| if (layers.topColumnHeaderLayer instanceof ExtColumnHeaderLayer) { |
| ((ExtColumnHeaderLayer) layers.topColumnHeaderLayer).setSpaceSize(sizeConfig.getRowHeight()); |
| ((ExtRowHeaderLayer) layers.topRowHeaderLayer).setSpaceSize(sizeConfig.getRowHeight()); |
| } |
| |
| presentation.configureRegistry(layers.table.getConfigRegistry()); |
| |
| layers.table.updateResize(); |
| } |
| }; |
| presentation.addListener(configRunnable); |
| |
| final ITableUIContext uiContext= new RUIContext(); |
| // { final ILayerCommandHandler<?> commandHandler= new ScrollCommandHandler(fTableLayers.viewportLayer); |
| // fTableLayers.viewportLayer.registerCommandHandler(commandHandler); |
| // } |
| { final ILayerCommandHandler<?> commandHandler= new SelectRelativeCommandHandler( |
| layers.selectionLayer ); |
| layers.viewportLayer.registerCommandHandler(commandHandler); |
| layers.selectionLayer.registerCommandHandler(commandHandler); |
| } |
| { final ILayerCommandHandler<?> commandHandler= new CopyToClipboardCommandHandler( |
| layers.selectionLayer, uiContext ); |
| layers.selectionLayer.registerCommandHandler(commandHandler); |
| } |
| { final ILayerCommandHandler<?> commandHandler= new InitializeAutoResizeCommandHandler( |
| layers.selectionLayer ); |
| gridLayer.registerCommandHandler(commandHandler); |
| } |
| if (sortModel != null) { |
| final ILayerCommandHandler<?> commandHandler= new SortPositionCommandHandler(sortModel); |
| layers.dataLayer.registerCommandHandler(commandHandler); |
| } |
| if (sortModel != null) { |
| final ILayerCommandHandler<?> commandHandler= new ClearSortCommandHandler(sortModel); |
| layers.dataLayer.registerCommandHandler(commandHandler); |
| } |
| |
| layers.table= new NatTable(this, gridLayer, false); |
| layers.table.addConfiguration(presentation); |
| layers.table.addConfiguration(new UIBindings.HeaderContextMenuConfiguration(layers.table)); |
| layers.table.addConfiguration(new UIBindings.BodyContextMenuConfiguration(layers.table, |
| this.callbacks.getServiceLocator() )); |
| |
| layers.table.addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(final DisposeEvent e) { |
| dataProvider.dispose(); |
| } |
| }); |
| |
| final IConfigRegistry registry= layers.table.getConfigRegistry(); |
| registry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, |
| new RDataFormatterConverter(dataProvider)); |
| registry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, |
| new RDataFormatterConverter.RowHeader(dataProvider), |
| DisplayMode.NORMAL, GridRegion.ROW_HEADER); |
| |
| this.tableLayers= layers; |
| this.tableInitialized= false; |
| |
| configRunnable.run(); |
| |
| this.tableLayers.table.addLayerListener(new ILayerListener() { |
| @Override |
| public void handleLayerEvent(final ILayerEvent event) { |
| if (event instanceof ISelectionEvent) { |
| scheduleUpdateSelection(false, 100); |
| return; |
| } |
| if (event instanceof IVisualChangeEvent) { |
| scheduleUpdateSelection(true, 100); |
| return; |
| } |
| } |
| }); |
| dataProvider.addDataChangedListener(new AbstractRDataProvider.IDataProviderListener() { |
| @Override |
| public void onInputInitialized(final boolean structChanged) { |
| if (layers != RDataTableComposite.this.tableLayers || layers.table.isDisposed()) { |
| return; |
| } |
| |
| if (layers.table != RDataTableComposite.this.layout.topControl) { |
| layers.table.configure(); |
| } |
| |
| if (layers.table != RDataTableComposite.this.layout.topControl) { |
| RDataTableComposite.this.layout.topControl= layers.table; |
| layout(); |
| } |
| if (!RDataTableComposite.this.tableInitialized) { |
| layers.selectionLayer.setSelectedCell(0, 0); |
| } |
| else if (structChanged) { |
| layers.dataLayer.fireLayerEvent(new RowStructuralRefreshEvent(layers.dataLayer)); |
| } |
| else { |
| layers.dataLayer.fireLayerEvent(new RowUpdateEvent(layers.dataLayer, |
| new LRange(0, layers.dataLayer.getRowCount()) )); |
| } |
| |
| RDataTableComposite.this.selection= null; |
| scheduleUpdateSelection(true, 0); |
| |
| for (final IRDataTableListener listener : RDataTableComposite.this.tableListeners.toArray()) { |
| listener.inputChanged(input, dataProvider.getDescription()); |
| } |
| } |
| |
| @Override |
| public void onInputFailed(final int error) { |
| if (error == ERROR_STRUCT_CHANGED) { |
| showReload(); |
| } |
| else { |
| showDummy("An error occurred when loading the table input."); |
| for (final IRDataTableListener listener : RDataTableComposite.this.tableListeners.toArray()) { |
| listener.inputChanged(null, null); |
| } |
| } |
| } |
| |
| @Override |
| public void onRowCountChanged() { |
| final TableLayers layers= RDataTableComposite.this.tableLayers; |
| if (layers == null || layers.table != layers.table || layers.table.isDisposed()) { |
| return; |
| } |
| |
| layers.dataLayer.fireLayerEvent(new RowStructuralRefreshEvent(layers.dataLayer)); |
| |
| RDataTableComposite.this.selection= null; |
| scheduleUpdateSelection(true, 0); |
| } |
| |
| @Override |
| public void onRowsChanged(final long beginIdx, final long endIdx) { |
| RDataTableComposite.this.display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| final TableLayers layers= RDataTableComposite.this.tableLayers; |
| if (layers == null || layers.table != layers.table || layers.table.isDisposed()) { |
| return; |
| } |
| layers.dataLayer.fireLayerEvent(new RowUpdateEvent(layers.dataLayer, |
| new LRange(beginIdx, endIdx) )); |
| } |
| }); |
| } |
| |
| }); |
| dataProvider.addFindListener(this.findListener); |
| } |
| |
| public NatTable getTable() { |
| return (this.tableLayers != null) ? this.tableLayers.table : null; |
| } |
| |
| protected void scheduleUpdateSelection(final boolean checkLabel, final int delay) { |
| synchronized (this.selectionLock) { |
| this.selectionUpdateScheduleStamp= System.nanoTime() + delay * 1000000L; |
| if (checkLabel) { |
| this.selectionCheckLabel= true; |
| } |
| if (this.selectionUpdateScheduled) { |
| return; |
| } |
| this.selectionUpdateScheduled= true; |
| } |
| this.display.asyncExec(this.selectionUpdateRunnable); |
| } |
| |
| protected void updateSelection() { |
| final boolean checkLabel; |
| synchronized (this.selectionLock) { |
| final long diff= (this.selectionUpdateScheduleStamp - System.nanoTime()) / 1000000L; |
| if (diff > 5) { |
| this.display.timerExec((int) diff, this.selectionUpdateRunnable); |
| return; |
| } |
| checkLabel= this.selectionCheckLabel; |
| this.selectionCheckLabel= false; |
| this.selectionUpdateScheduled= false; |
| } |
| final RDataTableSelection selection; |
| if (this.tableLayers == null) { |
| selection= new RDataTableSelection(null, null, null, null); |
| } |
| else { |
| final SelectionLayer selectionLayer= this.tableLayers.selectionLayer; |
| final PositionCoordinate anchor= selectionLayer.getSelectionAnchor(); |
| PositionCoordinate lastSelected= selectionLayer.getLastSelectedCellPosition(); |
| if (lastSelected.equals(anchor)) { |
| lastSelected= null; |
| } |
| |
| final boolean anchorChanged; |
| if ((anchorChanged= !anchor.equals(this.currentAnchor))) { |
| this.currentAnchor= new PositionCoordinate(anchor); |
| } |
| final boolean lastSelectedChanged; |
| if ((lastSelectedChanged= !((lastSelected != null) ? |
| lastSelected.equals(this.currentLastSelectedCell) : null == this.currentLastSelectedCell ))) { |
| this.currentLastSelectedCell= (lastSelected != null) ? new PositionCoordinate(lastSelected) : null; |
| } |
| |
| if (!checkLabel && !anchorChanged && !lastSelectedChanged) { |
| return; |
| } |
| |
| String anchorRowLabel= null; |
| String anchorColumnLabel= null; |
| if (this.currentAnchor.columnPosition >= 0 && this.currentAnchor.rowPosition >= 0) { |
| if (anchorChanged || checkLabel) { |
| anchorRowLabel= getRowLabel(this.currentAnchor.rowPosition); |
| if (anchorRowLabel != null) { |
| anchorColumnLabel= getColumnLabel(this.currentAnchor.columnPosition); |
| if (anchorColumnLabel == null) { |
| anchorRowLabel= null; |
| } |
| } |
| } |
| else if (this.selection != null) { |
| anchorRowLabel= this.selection.getAnchorRowLabel(); |
| anchorColumnLabel= this.selection.getAnchorColumnLabel(); |
| } |
| } |
| |
| if (anchorRowLabel == null) { |
| return; |
| } |
| |
| String lastSelectedRowLabel= null; |
| String lastSelectedColumnLabel= null; |
| if (this.currentLastSelectedCell != null |
| && this.currentLastSelectedCell.columnPosition >= 0 && this.currentLastSelectedCell.rowPosition >= 0) { |
| if (lastSelectedChanged || checkLabel) { |
| lastSelectedRowLabel= getRowLabel(this.currentLastSelectedCell.rowPosition); |
| if (lastSelectedRowLabel != null) { |
| lastSelectedColumnLabel= getColumnLabel(this.currentLastSelectedCell.columnPosition); |
| if (lastSelectedColumnLabel == null) { |
| lastSelectedRowLabel= null; |
| } |
| } |
| } |
| else if (this.selection != null) { |
| lastSelectedRowLabel= this.selection.getLastSelectedCellRowLabel(); |
| lastSelectedColumnLabel= this.selection.getLastSelectedCellColumnLabel(); |
| } |
| } |
| selection= new RDataTableSelection( |
| anchorRowLabel, anchorColumnLabel, |
| lastSelectedRowLabel, lastSelectedColumnLabel); |
| } |
| |
| if (selection.equals(this.selection)) { |
| return; |
| } |
| this.selection= selection; |
| final SelectionChangedEvent event= new SelectionChangedEvent(this, this.selection); |
| |
| for (final ISelectionChangedListener listener : this.selectionListeners.toArray()) { |
| listener.selectionChanged(event); |
| } |
| } |
| |
| protected String getRowLabel(final long row) { |
| if (!this.dataProvider.hasRealRows()) { |
| return ""; //$NON-NLS-1$ |
| } |
| final IDataProvider dataProvider= this.dataProvider.getRowDataProvider(); |
| if (dataProvider.getColumnCount() <= 1) { |
| return getHeaderLabel(dataProvider.getDataValue(0, row, 0, null)); |
| } |
| final StringBuilder sb= new StringBuilder(); |
| for (long i= 0; i < dataProvider.getColumnCount(); i++) { |
| final String label= getHeaderLabel(dataProvider.getDataValue(i, row, 0, null)); |
| if (label == null) { |
| return null; |
| } |
| sb.append(label); |
| sb.append(", "); //$NON-NLS-1$ |
| } |
| return sb.substring(0, sb.length()-2); |
| } |
| |
| protected String getColumnLabel(final long column) { |
| if (!this.dataProvider.hasRealColumns()) { |
| return ""; //$NON-NLS-1$ |
| } |
| final IDataProvider dataProvider= this.dataProvider.getColumnDataProvider(); |
| if (dataProvider.getRowCount() <= 1) { |
| return getHeaderLabel(dataProvider.getDataValue(column, 0, 0, null)); |
| } |
| final StringBuilder sb= new StringBuilder(); |
| for (long i= 0; i < dataProvider.getRowCount(); i++) { |
| final String label= getHeaderLabel(dataProvider.getDataValue(column, i, 0, null)); |
| if (label == null) { |
| return null; |
| } |
| sb.append(label); |
| sb.append(", "); //$NON-NLS-1$ |
| } |
| return sb.substring(0, sb.length()-2); |
| } |
| |
| private String getHeaderLabel(final Object value) { |
| if (value != null) { |
| if (value instanceof ControlData && value != AbstractRDataProvider.NA) { |
| return null; |
| } |
| final Object displayValue= this.formatter.modelToDisplayValue(value); |
| if (displayValue.getClass() == String.class) { |
| return (String) displayValue; |
| } |
| if (displayValue == AbstractRDataProvider.DUMMY) { |
| return ""; //$NON-NLS-1$ |
| } |
| return null; |
| } |
| else { |
| return this.formatter.modelToDisplayValue(null).toString(); |
| } |
| } |
| |
| protected void showDummy(final String message) { |
| if (isDisposed()) { |
| return; |
| } |
| this.messageControl.setText(message); |
| this.layout.topControl= this.messageControl; |
| layout(); |
| } |
| |
| |
| public long[] getTableDimension() { |
| if (this.tableLayers != null) { |
| return new long[] { this.dataProvider.getRowCount(), this.dataProvider.getColumnCount() }; |
| } |
| return null; |
| } |
| |
| public boolean isOK() { |
| return (this.tableLayers != null && this.layout.topControl == this.tableLayers.table); |
| } |
| |
| public IViewportDim getViewport(final Orientation orientation) { |
| return this.tableLayers.viewportLayer.getDim(orientation); |
| } |
| |
| @Override |
| public ISelection getSelection() { |
| return null; |
| } |
| |
| @Override |
| public void setSelection(final ISelection selection) { |
| } |
| |
| @Override |
| public void addSelectionChangedListener(final ISelectionChangedListener listener) { |
| this.selectionListeners.add(listener); |
| } |
| |
| @Override |
| public void removeSelectionChangedListener(final ISelectionChangedListener listener) { |
| this.selectionListeners.remove(listener); |
| } |
| |
| |
| public void find(final String expression, final boolean selectedOnly, final boolean firstInRow, final boolean forward) { |
| if (this.tableLayers != null) { |
| final PositionCoordinate anchor= this.tableLayers.selectionLayer.getSelectionAnchor(); |
| final FindTask task= new FindTask(expression, |
| anchor.getRowPosition(), anchor.getColumnPosition(), firstInRow, forward, |
| (selectedOnly) ? new SelectionFindFilter() : null); |
| this.dataProvider.find(task); |
| } |
| } |
| |
| public void addFindListener(final IFindListener listener) { |
| this.findListeners.add(listener); |
| } |
| |
| public void removeFindListener(final IFindListener listener) { |
| this.findListeners.remove(listener); |
| } |
| |
| |
| public void setInput(final IRDataTableInput input) { |
| if (this.tableLayers != null) { |
| showDummy(""); //$NON-NLS-1$ |
| this.tableLayers.table.dispose(); |
| this.tableLayers.table= null; |
| this.dataProvider= null; |
| this.setAnchorByData= null; |
| } |
| if (input != null) { |
| showDummy("Preparing (" + input.getName() + ")..."); |
| try { |
| final ToolRunnable runnable= new SystemRunnable() { |
| |
| @Override |
| public String getTypeId() { |
| return "r/dataeditor/init"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public String getLabel() { |
| return "Prepare Data Viewer (" + input.getName() + ")"; |
| } |
| |
| @Override |
| public boolean canRunIn(final Tool tool) { |
| return true; // TODO |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| if (event == MOVING_FROM) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, |
| final ProgressMonitor m) throws StatusException { |
| final RToolService r= (RToolService) service; |
| |
| final AtomicReference<AbstractRDataProvider<?>> dataProvider= new AtomicReference<>(); |
| Exception error= null; |
| try { |
| final RObject struct= r.evalData(input.getFullName(), |
| null, RObjectFactory.F_ONLY_STRUCT, 1, m ); |
| RCharacterStore classNames= null; |
| { final FunctionCall call= r.createFunctionCall("class"); //$NON-NLS-1$ |
| call.add(input.getFullName()); |
| classNames= RDataUtils.checkRCharVector(call.evalData(m)).getData(); |
| } |
| |
| switch (struct.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| dataProvider.set(new RVectorDataProvider(input, (RVector<?>) struct)); |
| break; |
| case RObject.TYPE_ARRAY: { |
| final RArray<?> array= (RArray<?>) struct; |
| if (array.getDim().getLength() == 2) { |
| if (classNames.contains("ftable")) { //$NON-NLS-1$ |
| dataProvider.set(new FTableDataProvider(input, array)); |
| break; |
| } |
| dataProvider.set(new RMatrixDataProvider(input, array)); |
| break; |
| } |
| break; } |
| case RObject.TYPE_DATAFRAME: |
| dataProvider.set(new RDataFrameDataProvider(input, (RDataFrame) struct)); |
| break; |
| default: |
| break; |
| } |
| } |
| catch (final CoreException e) { |
| error= e; |
| } |
| catch (final UnexpectedRDataException e) { |
| error= e; |
| } |
| final IStatus status; |
| if (error != null) { |
| status= new org.eclipse.core.runtime.Status(IStatus.ERROR, RUI.BUNDLE_ID, |
| "An error occurred when preparing the R data viewer.", error ); |
| StatusManager.getManager().handle(status); |
| } |
| else if (dataProvider.get() == null) { |
| status= new org.eclipse.core.runtime.Status(IStatus.ERROR, RUI.BUNDLE_ID, |
| "This R element type is not supported.", null ); |
| } |
| else { |
| status= null; |
| } |
| RDataTableComposite.this.display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| RDataTableComposite.this.dataProvider= null; |
| if (!UIAccess.isOkToUse(RDataTableComposite.this)) { |
| return; |
| } |
| if (status == null) { |
| initTable(input, dataProvider.get()); |
| } |
| else { |
| showDummy(status.getMessage()); |
| } |
| } |
| }); |
| } |
| }; |
| final Status status= input.getTool().getQueue().add(runnable); |
| if (status.getSeverity() >= Status.ERROR) { |
| throw new StatusException(status); |
| } |
| } |
| catch (final StatusException e) { |
| showDummy(e.getLocalizedMessage()); |
| } |
| } |
| } |
| |
| protected void showReload() { |
| if (this.reloadControl == null) { |
| this.reloadControl= new Composite(this, SWT.NONE); |
| this.reloadControl.setLayout(LayoutUtils.newCompositeGrid(4)); |
| |
| final Label label= new Label(this.reloadControl, SWT.WRAP); |
| label.setText("The structure of the R element is changed (columns / data type)."); |
| label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 4, 1)); |
| |
| { final Button button= new Button(this.reloadControl, SWT.PUSH); |
| button.setLayoutData(LayoutUtils.hintWidth(new GridData( |
| SWT.FILL, SWT.CENTER, false, false), button)); |
| button.setText("Refresh"); |
| button.setToolTipText("Refresh table with old structure"); |
| button.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| refresh(); |
| } |
| }); |
| } |
| { final Button button= new Button(this.reloadControl, SWT.PUSH); |
| button.setLayoutData(LayoutUtils.hintWidth(new GridData( |
| SWT.FILL, SWT.CENTER, false, false), button)); |
| button.setText("Reopen"); |
| button.setToolTipText("Reopen table with new structure"); |
| button.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| setInput(RDataTableComposite.this.input); |
| } |
| }); |
| } |
| if (this.callbacks.isCloseSupported()) { |
| final Button button= new Button(this.reloadControl, SWT.PUSH); |
| button.setLayoutData(LayoutUtils.hintWidth(new GridData( |
| SWT.FILL, SWT.CENTER, false, false), button)); |
| button.setText("Close"); |
| button.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| RDataTableComposite.this.callbacks.close(); |
| } |
| }); |
| } |
| // |
| LayoutUtils.addSmallFiller(this.reloadControl, true); |
| } |
| this.layout.topControl= this.reloadControl; |
| layout(true); |
| } |
| |
| |
| public void setFilter(final String filter) { |
| this.dataProvider.setFilter(filter); |
| } |
| |
| |
| public RDataTableContentDescription getDescription() { |
| return this.dataProvider.getDescription(); |
| } |
| |
| public void addTableListener(final IRDataTableListener listener) { |
| this.tableListeners.add(listener); |
| if (this.tableLayers != null) { |
| listener.inputChanged(this.input, this.dataProvider.getDescription()); |
| } |
| } |
| |
| public void removeTableListener(final IRDataTableListener listener) { |
| this.tableListeners.remove(listener); |
| } |
| |
| |
| @Override |
| public boolean setFocus() { |
| if (this.layout == null || this.layout.topControl == null) { |
| return false; |
| } |
| return this.layout.topControl.forceFocus(); |
| } |
| |
| public void revealColumn(final long index) { |
| if (this.tableLayers != null) { |
| this.tableLayers.viewportLayer.getDim(HORIZONTAL).movePositionIntoViewport(index); |
| } |
| } |
| |
| public void selectColumns(final Collection<LRange> indexes) { |
| if (this.tableLayers != null) { |
| final LRangeList columns= LRangeList.toRangeList(indexes); |
| // final long rowIndex= fTableBodyLayerStack.getViewportLayer().getRowIndexByPosition(0); |
| final long rowIndex= 0; |
| this.tableLayers.table.doCommand(new SelectDimPositionsCommand( |
| this.tableLayers.selectionLayer.getDim(HORIZONTAL), |
| 0, columns, rowIndex, |
| 0, |
| !(columns.isEmpty()) ? columns.values().first() : SelectionLayer.NO_SELECTION )); |
| } |
| } |
| |
| public void setAnchorViewIdxs(final long columnIndex, final long rowIndex) { |
| if (this.tableLayers != null) { |
| this.tableLayers.selectionLayer.setSelectionAnchor(columnIndex, rowIndex, true); |
| } |
| } |
| |
| public long[] getAnchorDataIdxs() { |
| if (this.tableLayers != null) { |
| final PositionCoordinate coordinate= this.tableLayers.selectionLayer.getSelectionAnchor(); |
| if (coordinate.columnPosition < 0 || coordinate.rowPosition < 0) { |
| return null; |
| } |
| return this.dataProvider.toDataIdxs(coordinate.columnPosition, coordinate.rowPosition); |
| } |
| return null; |
| } |
| |
| public void setAnchorDataIdxs(final long columnIdx, final long rowIdx) { |
| if (this.tableLayers != null) { |
| if (this.setAnchorByData == null) { |
| this.setAnchorByData= new SetAnchorByDataIndexes(this.dataProvider); |
| } |
| this.setAnchorByData.resolve(columnIdx, rowIdx); |
| } |
| } |
| |
| public void selectAll() { |
| if (this.tableLayers != null) { |
| this.tableLayers.table.doCommand(new SelectAllCommand()); |
| } |
| } |
| |
| public void sortByColumn(final long index, final boolean increasing) { |
| if (this.tableLayers != null) { |
| this.tableLayers.dataLayer.doCommand(new SortDimPositionCommand( |
| this.tableLayers.dataLayer.getDim(HORIZONTAL), index, |
| increasing ? SortDirection.ASC : SortDirection.DESC, false )); |
| // final ISortModel sortModel= this.fDataProvider.getSortModel(); |
| // if (sortModel != null) { |
| // sortModel.sort( |
| // PositionId.BODY_CAT | index, increasing ? SortDirection.ASC : SortDirection.DESC, false ); |
| // this.fTableLayers.topColumnHeaderLayer.fireLayerEvent( |
| // new SortColumnEvent(this.fTableLayers.topColumnHeaderLayer.getDim(HORIZONTAL), 0) ); |
| // } |
| } |
| } |
| |
| public void clearSorting() { |
| if (this.tableLayers != null) { |
| this.tableLayers.table.doCommand(new ClearSortCommand()); |
| } |
| } |
| |
| public void refresh() { |
| if (this.tableLayers != null) { |
| this.dataProvider.reset(); |
| this.tableLayers.table.redraw(); |
| } |
| } |
| |
| } |