| /******************************************************************************* |
| * Copyright (c) 2012, 2013 Original authors and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Original authors and others - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Properties; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.eclipse.nebula.widgets.nattable.command.DisposeResourcesCommand; |
| import org.eclipse.nebula.widgets.nattable.command.ILayerCommand; |
| import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.command.StructuralRefreshCommand; |
| import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommand; |
| import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry; |
| import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; |
| import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; |
| import org.eclipse.nebula.widgets.nattable.config.IConfiguration; |
| import org.eclipse.nebula.widgets.nattable.conflation.EventConflaterChain; |
| import org.eclipse.nebula.widgets.nattable.conflation.IEventConflater; |
| import org.eclipse.nebula.widgets.nattable.conflation.VisualChangeEventConflater; |
| import org.eclipse.nebula.widgets.nattable.coordinate.Range; |
| import org.eclipse.nebula.widgets.nattable.edit.ActiveCellEditorRegistry; |
| import org.eclipse.nebula.widgets.nattable.edit.CellEditorCreatedEvent; |
| import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor; |
| import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand; |
| import org.eclipse.nebula.widgets.nattable.grid.command.InitializeGridCommand; |
| import org.eclipse.nebula.widgets.nattable.layer.AbstractDpiConverter; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; |
| import org.eclipse.nebula.widgets.nattable.layer.LabelStack; |
| import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; |
| import org.eclipse.nebula.widgets.nattable.layer.command.ConfigureScalingCommand; |
| import org.eclipse.nebula.widgets.nattable.layer.event.CellVisualUpdateEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ColumnVisualUpdateEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.IVisualChangeEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.RowVisualUpdateEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.stack.DummyGridLayerStack; |
| import org.eclipse.nebula.widgets.nattable.painter.IOverlayPainter; |
| import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; |
| import org.eclipse.nebula.widgets.nattable.painter.layer.ILayerPainter; |
| import org.eclipse.nebula.widgets.nattable.painter.layer.NatLayerPainter; |
| import org.eclipse.nebula.widgets.nattable.persistence.IPersistable; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; |
| import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.event.ISelectionEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.event.RowSelectionEvent; |
| import org.eclipse.nebula.widgets.nattable.style.theme.ThemeConfiguration; |
| import org.eclipse.nebula.widgets.nattable.style.theme.ThemeManager; |
| import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; |
| import org.eclipse.nebula.widgets.nattable.ui.mode.ConfigurableModeEventHandler; |
| import org.eclipse.nebula.widgets.nattable.ui.mode.Mode; |
| import org.eclipse.nebula.widgets.nattable.ui.mode.ModeSupport; |
| import org.eclipse.nebula.widgets.nattable.util.GUIHelper; |
| import org.eclipse.nebula.widgets.nattable.util.IClientAreaProvider; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.RecalculateScrollBarsCommand; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.dnd.DragSource; |
| import org.eclipse.swt.dnd.DragSourceEvent; |
| import org.eclipse.swt.dnd.DragSourceListener; |
| import org.eclipse.swt.dnd.DropTarget; |
| import org.eclipse.swt.dnd.DropTargetListener; |
| import org.eclipse.swt.dnd.Transfer; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Canvas; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.ScrollBar; |
| |
| //this warning suppression is because of the ActiveCellEditorRegistry usage to ensure backwards compatibility |
| @SuppressWarnings("deprecation") |
| public class NatTable extends Canvas implements ILayer, PaintListener, IClientAreaProvider, ILayerListener, IPersistable { |
| |
| private static final Log log = LogFactory.getLog(NatTable.class); |
| |
| public static final int DEFAULT_STYLE_OPTIONS = SWT.NO_BACKGROUND |
| | SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL |
| | SWT.H_SCROLL; |
| |
| /** |
| * Key that is used for loading NatTable states. Is set to <code>true</code> |
| * in case the initial painting is not finished yet. In this case there is |
| * no need to call refresh commands on loading. |
| */ |
| public static final String INITIAL_PAINT_COMPLETE_FLAG = "NatTable.initialPaintComplete"; //$NON-NLS-1$ |
| |
| private UiBindingRegistry uiBindingRegistry; |
| |
| private ModeSupport modeSupport; |
| |
| private final EventConflaterChain conflaterChain = new EventConflaterChain(); |
| |
| private final List<IOverlayPainter> overlayPainters = new ArrayList<IOverlayPainter>(); |
| |
| private final List<IPersistable> persistables = new LinkedList<IPersistable>(); |
| |
| private ILayer underlyingLayer; |
| |
| private IConfigRegistry configRegistry; |
| |
| protected final Collection<IConfiguration> configurations = new LinkedList<IConfiguration>(); |
| |
| protected String id = GUIHelper.getSequenceNumber(); |
| |
| private ILayerPainter layerPainter = new NatLayerPainter(this); |
| |
| private final boolean autoconfigure; |
| |
| /** |
| * Listener that is added because of Bug 415459.<br/> |
| * It is added to the parent composite and will close an active cell editor |
| * in case the parent is resized. We need to listen to the parent composite |
| * resize, because resizing a shell or a part in e4 does not cause loosing |
| * the focus. Therefore the editor will stay open in such cases. As this |
| * causes rendering issues when using percentage sizing, this listener |
| * closes an editor on parent composite resize.<br/> |
| * It is not registered as listener to NatTable itself, because this would |
| * have impact when filtering or dynamic updates cause scrollbars to become |
| * visible/invisible, which result in resizing of the NatTable. |
| */ |
| private Listener closeEditorOnParentResize = new Listener() { |
| @Override |
| public void handleEvent(Event event) { |
| // as resizing doesn't cause the current active editor to loose |
| // focus |
| // we are closing the current active editor manually |
| if (!commitAndCloseActiveCellEditor()) { |
| // if committing didn't work out we need to perform a hard close |
| // otherwise the state of the table would be unstale |
| getActiveCellEditor().close(); |
| } |
| } |
| }; |
| |
| /** |
| * This flag is used to deal with runtime issues on loading states while the |
| * initial rendering is not finished yet. |
| */ |
| private boolean initialPaintComplete = false; |
| |
| /** |
| * The {@link ThemeManager} that is used to switch |
| * {@link ThemeConfiguration}s at runtime. |
| */ |
| private ThemeManager themeManager; |
| |
| /** |
| * The active cell editor or {@code null} if there is no one. |
| */ |
| private ICellEditor activeCellEditor; |
| |
| public NatTable(Composite parent) { |
| this(parent, DEFAULT_STYLE_OPTIONS); |
| } |
| |
| /** |
| * @param parent |
| * widget for the table. |
| * @param autoconfigure |
| * if set to False - No auto configuration is done - Default |
| * settings are <i>not</i> loaded. Configuration(s) have to be |
| * manually added by invoking addConfiguration(). At the minimum |
| * the {@link DefaultNatTableStyleConfiguration} must be added |
| * for the table to render. |
| */ |
| public NatTable(Composite parent, boolean autoconfigure) { |
| this(parent, DEFAULT_STYLE_OPTIONS, autoconfigure); |
| } |
| |
| public NatTable(Composite parent, ILayer layer) { |
| this(parent, DEFAULT_STYLE_OPTIONS, layer); |
| } |
| |
| public NatTable(Composite parent, ILayer layer, boolean autoconfigure) { |
| this(parent, DEFAULT_STYLE_OPTIONS, layer, autoconfigure); |
| } |
| |
| public NatTable(Composite parent, final int style) { |
| this(parent, style, new DummyGridLayerStack()); |
| } |
| |
| public NatTable(Composite parent, final int style, boolean autoconfigure) { |
| this(parent, style, new DummyGridLayerStack(), autoconfigure); |
| } |
| |
| public NatTable(final Composite parent, final int style, ILayer layer) { |
| this(parent, style, layer, true); |
| } |
| |
| public NatTable(final Composite parent, final int style, |
| final ILayer layer, boolean autoconfigure) { |
| super(parent, style); |
| |
| // Disable scroll bars by default; if a Viewport is available, it will |
| // enable the scroll bars |
| disableScrollBar(getHorizontalBar()); |
| disableScrollBar(getVerticalBar()); |
| |
| initInternalListener(); |
| |
| internalSetLayer(layer); |
| |
| this.autoconfigure = autoconfigure; |
| if (autoconfigure) { |
| this.configurations.add(new DefaultNatTableStyleConfiguration()); |
| configure(); |
| } |
| |
| this.conflaterChain.add(getVisualChangeEventConflater()); |
| this.conflaterChain.start(); |
| |
| parent.addListener(SWT.Resize, this.closeEditorOnParentResize); |
| |
| addDisposeListener(new DisposeListener() { |
| |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| doCommand(new DisposeResourcesCommand()); |
| NatTable.this.conflaterChain.stop(); |
| ActiveCellEditorRegistry.unregisterActiveCellEditor(); |
| layer.dispose(); |
| |
| parent.removeListener(SWT.Resize, NatTable.this.closeEditorOnParentResize); |
| } |
| |
| }); |
| } |
| |
| protected IEventConflater getVisualChangeEventConflater() { |
| return new VisualChangeEventConflater(this); |
| } |
| |
| private void disableScrollBar(ScrollBar scrollBar) { |
| if (scrollBar != null) { |
| scrollBar.setMinimum(0); |
| scrollBar.setMaximum(1); |
| scrollBar.setThumb(1); |
| scrollBar.setEnabled(false); |
| } |
| } |
| |
| public ILayer getLayer() { |
| return this.underlyingLayer; |
| } |
| |
| public void setLayer(ILayer layer) { |
| if (this.autoconfigure) { |
| throw new IllegalStateException("May only set layer post construction if autoconfigure is turned off"); //$NON-NLS-1$ |
| } |
| |
| internalSetLayer(layer); |
| } |
| |
| private void internalSetLayer(ILayer layer) { |
| if (layer != null) { |
| this.underlyingLayer = layer; |
| this.underlyingLayer.setClientAreaProvider(new IClientAreaProvider() { |
| |
| @Override |
| public Rectangle getClientArea() { |
| final Rectangle clientArea = new Rectangle(0, 0, 0, 0); |
| if (!isDisposed()) { |
| getDisplay().syncExec(new Runnable() { |
| @Override |
| public void run() { |
| Rectangle natClientArea = NatTable.this.getClientArea(); |
| clientArea.x = natClientArea.x; |
| clientArea.y = natClientArea.y; |
| clientArea.width = natClientArea.width; |
| clientArea.height = natClientArea.height; |
| } |
| }); |
| } |
| return clientArea; |
| } |
| |
| }); |
| this.underlyingLayer.addLayerListener(this); |
| |
| // register the DPI scaling |
| this.underlyingLayer.doCommand(new ConfigureScalingCommand( |
| new AbstractDpiConverter() { |
| |
| @Override |
| protected void readDpiFromDisplay() { |
| Display.getDefault().syncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| dpi = Display.getDefault().getDPI().x; |
| } |
| }); |
| } |
| }, |
| new AbstractDpiConverter() { |
| |
| @Override |
| protected void readDpiFromDisplay() { |
| Display.getDefault().syncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| dpi = Display.getDefault().getDPI().y; |
| } |
| }); |
| } |
| })); |
| } |
| } |
| |
| /** |
| * Adds a configuration to the table. |
| * <p> |
| * Configurations are processed when the {@link #configure()} method is |
| * invoked. Each configuration object then has a chance to configure the |
| * <ol> |
| * <li>ILayer</li> |
| * <li>ConfigRegistry</li> |
| * <li>UiBindingRegistry</li> |
| * </ol> |
| */ |
| public void addConfiguration(IConfiguration configuration) { |
| if (this.autoconfigure) { |
| throw new IllegalStateException("May only add configurations post construction if autoconfigure is turned off"); //$NON-NLS-1$ |
| } |
| |
| this.configurations.add(configuration); |
| } |
| |
| /** |
| * @return {@link IConfigRegistry} used to hold the configuration bindings |
| * by Layer, DisplayMode and Config labels. |
| */ |
| public IConfigRegistry getConfigRegistry() { |
| if (this.configRegistry == null) { |
| this.configRegistry = new ConfigRegistry(); |
| this.themeManager = new ThemeManager(this.configRegistry); |
| } |
| return this.configRegistry; |
| } |
| |
| public void setConfigRegistry(IConfigRegistry configRegistry) { |
| if (this.autoconfigure) { |
| throw new IllegalStateException("May only set config registry post construction if autoconfigure is turned off"); //$NON-NLS-1$ |
| } |
| |
| this.configRegistry = configRegistry; |
| this.themeManager = new ThemeManager(configRegistry); |
| } |
| |
| /** |
| * @return Registry holding all the UIBindings contributed by the underlying |
| * layers |
| */ |
| public UiBindingRegistry getUiBindingRegistry() { |
| if (this.uiBindingRegistry == null) { |
| this.uiBindingRegistry = new UiBindingRegistry(this); |
| } |
| return this.uiBindingRegistry; |
| } |
| |
| public void setUiBindingRegistry(UiBindingRegistry uiBindingRegistry) { |
| if (this.autoconfigure) { |
| throw new IllegalStateException("May only set UI binding registry post construction if autoconfigure is turned off"); //$NON-NLS-1$ |
| } |
| |
| this.uiBindingRegistry = uiBindingRegistry; |
| } |
| |
| public String getID() { |
| return this.id; |
| } |
| |
| @Override |
| protected void checkSubclass() {} |
| |
| protected void initInternalListener() { |
| this.modeSupport = new ModeSupport(this); |
| this.modeSupport.registerModeEventHandler(Mode.NORMAL_MODE, |
| new ConfigurableModeEventHandler(this.modeSupport, this)); |
| this.modeSupport.switchMode(Mode.NORMAL_MODE); |
| |
| addPaintListener(this); |
| |
| addFocusListener(new FocusListener() { |
| |
| @Override |
| public void focusLost(final FocusEvent arg0) { |
| redraw(); |
| } |
| |
| @Override |
| public void focusGained(final FocusEvent arg0) { |
| redraw(); |
| } |
| |
| }); |
| |
| addListener(SWT.Resize, new Listener() { |
| @Override |
| public void handleEvent(final Event e) { |
| doCommand(new ClientAreaResizeCommand(NatTable.this)); |
| redraw(); |
| } |
| }); |
| } |
| |
| @Override |
| public boolean forceFocus() { |
| return super.forceFocus(); |
| } |
| |
| // Painting /////////////////////////////////////////////////////////////// |
| |
| public List<IOverlayPainter> getOverlayPainters() { |
| return this.overlayPainters; |
| } |
| |
| public void addOverlayPainter(IOverlayPainter overlayPainter) { |
| this.overlayPainters.add(overlayPainter); |
| } |
| |
| public void removeOverlayPainter(IOverlayPainter overlayPainter) { |
| this.overlayPainters.remove(overlayPainter); |
| } |
| |
| @Override |
| public void paintControl(final PaintEvent event) { |
| paintNatTable(event); |
| this.initialPaintComplete = true; |
| } |
| |
| private void paintNatTable(final PaintEvent event) { |
| getLayerPainter().paintLayer(this, event.gc, 0, 0, |
| new Rectangle(event.x, event.y, event.width, event.height), |
| getConfigRegistry()); |
| } |
| |
| @Override |
| public ILayerPainter getLayerPainter() { |
| return this.layerPainter; |
| } |
| |
| public void setLayerPainter(ILayerPainter layerPainter) { |
| this.layerPainter = layerPainter; |
| } |
| |
| /** |
| * Repaint only a specific column in the grid. This method is optimized so |
| * that only the specific column is repainted and nothing else. |
| * |
| * @param columnPosition |
| * column of the grid to repaint |
| */ |
| public void repaintColumn(int columnPosition) { |
| int xOffset = getStartXOfColumnPosition(columnPosition); |
| if (xOffset < 0) { |
| return; |
| } |
| redraw(xOffset, 0, getColumnWidthByPosition(columnPosition), getHeight(), true); |
| } |
| |
| /** |
| * Repaint only a specific row in the grid. This method is optimized so that |
| * only the specific row is repainted and nothing else. |
| * |
| * @param rowPosition |
| * row of the grid to repaint |
| */ |
| public void repaintRow(int rowPosition) { |
| int yOffset = getStartYOfRowPosition(rowPosition); |
| if (yOffset < 0) { |
| return; |
| } |
| redraw(0, yOffset, getWidth(), getRowHeightByPosition(rowPosition), true); |
| } |
| |
| /** |
| * Repaint only a specific cell in the grid. This method is optimized so |
| * that only the specific cell is repainted and nothing else. |
| * |
| * @param columnPosition |
| * column position of the cell to repaint |
| * @param rowPosition |
| * row position of the cell to repaint |
| */ |
| public void repaintCell(int columnPosition, int rowPosition) { |
| int xOffset = getStartXOfColumnPosition(columnPosition); |
| int yOffset = getStartYOfRowPosition(rowPosition); |
| |
| redraw(xOffset, yOffset, getColumnWidthByPosition(columnPosition), |
| getRowHeightByPosition(rowPosition), true); |
| } |
| |
| /** |
| * Repaint the area to the right of the last column in case there is more |
| * space available than columns to paint. |
| */ |
| public void repaintHorizontalLeftOver() { |
| int leftOverSpace = getClientArea().width - getWidth(); |
| if (leftOverSpace > 0) |
| redraw(getWidth(), 0, leftOverSpace, getHeight(), true); |
| } |
| |
| /** |
| * Repaint the area to the bottom of the last row in case there is more |
| * space available than rows to paint. |
| */ |
| public void repaintVerticalLeftOver() { |
| int leftOverSpace = getClientArea().height - getHeight(); |
| if (leftOverSpace > 0) |
| redraw(0, getHeight(), getClientArea().width, leftOverSpace, true); |
| } |
| |
| public void updateResize() { |
| updateResize(true); |
| } |
| |
| /** |
| * Update the table screen by re-calculating everything again. It should not |
| * be called too frequently. |
| * |
| * @param redraw |
| * true to redraw the table |
| */ |
| private void updateResize(final boolean redraw) { |
| if (isDisposed()) { |
| return; |
| } |
| doCommand(new RecalculateScrollBarsCommand()); |
| if (redraw) { |
| redraw(); |
| } |
| } |
| |
| /** |
| * Refreshes the entire NatTable as every layer will be refreshed. |
| */ |
| public void refresh() { |
| doCommand(new StructuralRefreshCommand()); |
| } |
| |
| @Override |
| public void configure(ConfigRegistry configRegistry, UiBindingRegistry uiBindingRegistry) { |
| throw new UnsupportedOperationException("Cannot use this method to configure NatTable. Use no-argument configure() instead."); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Processes all the registered {@link IConfiguration} (s). All the |
| * underlying layers are walked and given a chance to configure. Note: all |
| * desired configuration tweaks must be done <i>before</i> this method is |
| * invoked. |
| */ |
| public void configure() { |
| if (this.underlyingLayer == null) { |
| throw new IllegalStateException("Layer must be set before configure is called"); //$NON-NLS-1$ |
| } |
| |
| if (this.underlyingLayer != null) { |
| this.underlyingLayer.configure((ConfigRegistry) getConfigRegistry(), |
| getUiBindingRegistry()); |
| } |
| |
| for (IConfiguration configuration : this.configurations) { |
| configuration.configureLayer(this); |
| configuration.configureRegistry(getConfigRegistry()); |
| configuration.configureUiBindings(getUiBindingRegistry()); |
| } |
| |
| // Once everything is initialized and properly configured we will |
| // now formally initialize the grid |
| doCommand(new InitializeGridCommand(this)); |
| } |
| |
| // Events ///////////////////////////////////////////////////////////////// |
| |
| @Override |
| public void handleLayerEvent(ILayerEvent event) { |
| for (ILayerListener layerListener : this.listeners) { |
| layerListener.handleLayerEvent(event); |
| } |
| |
| if (event instanceof CellVisualUpdateEvent) { |
| CellVisualUpdateEvent update = (CellVisualUpdateEvent) event; |
| repaintCell(update.getColumnPosition(), update.getRowPosition()); |
| return; |
| } |
| |
| if (event instanceof ColumnVisualUpdateEvent) { |
| ColumnVisualUpdateEvent update = (ColumnVisualUpdateEvent) event; |
| // if more than one column has changed repaint the whole table |
| Collection<Range> ranges = update.getColumnPositionRanges(); |
| if (ranges.size() == 1) { |
| Range range = ranges.iterator().next(); |
| if (range.end - range.start == 1) { |
| repaintColumn(range.start); |
| return; |
| } |
| } |
| } |
| |
| if (event instanceof RowVisualUpdateEvent) { |
| RowVisualUpdateEvent update = (RowVisualUpdateEvent) event; |
| // if more than one row has changed repaint the whole table |
| Collection<Range> ranges = update.getRowPositionRanges(); |
| if (ranges.size() == 1) { |
| Range range = ranges.iterator().next(); |
| if (range.end - range.start == 1) { |
| repaintRow(range.start); |
| return; |
| } |
| } |
| } |
| |
| if (event instanceof ISelectionEvent) { |
| if (event instanceof CellSelectionEvent) { |
| Event e = new Event(); |
| e.widget = this; |
| try { |
| notifyListeners(SWT.Selection, e); |
| } catch (RuntimeException re) { |
| log.error("Error on SWT selection processing", re); //$NON-NLS-1$ |
| } |
| } |
| |
| // in case of selections we redraw immediately |
| // this is because with Bug 440037 it was reported that |
| // NatTable is too lazy in handling selections which |
| // was caused by the EventConflaterChain that only performs |
| // updates every 100ms to avoid flickering when handling too |
| // many refresh operations in a short period |
| redraw(); |
| } else if (event instanceof IVisualChangeEvent) { |
| this.conflaterChain.addEvent(event); |
| } |
| |
| if (event instanceof CellEditorCreatedEvent) { |
| CellEditorCreatedEvent editorEvent = (CellEditorCreatedEvent) event; |
| this.activeCellEditor = editorEvent.getEditor(); |
| Control editorControl = this.activeCellEditor.getEditorControl(); |
| if (editorControl != null && !editorControl.isDisposed()) { |
| editorControl.addDisposeListener(new DisposeListener() { |
| |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| NatTable.this.activeCellEditor = null; |
| ActiveCellEditorRegistry.unregisterActiveCellEditor(); |
| } |
| }); |
| } else { |
| this.activeCellEditor = null; |
| ActiveCellEditorRegistry.unregisterActiveCellEditor(); |
| } |
| ActiveCellEditorRegistry.registerActiveCellEditor(this.activeCellEditor); |
| } |
| } |
| |
| // ILayer ///////////////////////////////////////////////////////////////// |
| |
| // Persistence |
| |
| /** |
| * Save the state of the table to the properties object. |
| * {@link ILayer#saveState(String, Properties)} is invoked on all the |
| * underlying layers. This properties object will be populated with the |
| * settings of all underlying layers and any {@link IPersistable} registered |
| * with those layers. |
| */ |
| @Override |
| public void saveState(final String prefix, final Properties properties) { |
| BusyIndicator.showWhile(null, new Runnable() { |
| |
| @Override |
| public void run() { |
| NatTable.this.underlyingLayer.saveState(prefix, properties); |
| } |
| }); |
| } |
| |
| /** |
| * Restore the state of the underlying layers from the values in the |
| * properties object. |
| * |
| * @see #saveState(String, Properties) |
| */ |
| @Override |
| public void loadState(final String prefix, final Properties properties) { |
| BusyIndicator.showWhile(null, new Runnable() { |
| |
| @Override |
| public void run() { |
| // if the initial painting is not finished yet, tell this the |
| // underlying |
| // mechanisms so there will be no refresh events fired |
| if (!NatTable.this.initialPaintComplete) |
| properties.setProperty(INITIAL_PAINT_COMPLETE_FLAG, "true"); //$NON-NLS-1$ |
| |
| NatTable.this.underlyingLayer.loadState(prefix, properties); |
| } |
| }); |
| } |
| |
| /** |
| * @see ILayer#registerPersistable(IPersistable) |
| */ |
| @Override |
| public void registerPersistable(IPersistable persistable) { |
| this.persistables.add(persistable); |
| } |
| |
| @Override |
| public void unregisterPersistable(IPersistable persistable) { |
| this.persistables.remove(persistable); |
| } |
| |
| // Command |
| |
| @Override |
| public boolean doCommand(ILayerCommand command) { |
| return this.underlyingLayer.doCommand(command); |
| } |
| |
| @Override |
| public void registerCommandHandler(ILayerCommandHandler<?> commandHandler) { |
| this.underlyingLayer.registerCommandHandler(commandHandler); |
| } |
| |
| @Override |
| public void unregisterCommandHandler( |
| Class<? extends ILayerCommand> commandClass) { |
| this.underlyingLayer.unregisterCommandHandler(commandClass); |
| } |
| |
| // Events |
| |
| private final List<ILayerListener> listeners = new ArrayList<ILayerListener>(); |
| |
| @Override |
| public void fireLayerEvent(ILayerEvent event) { |
| this.underlyingLayer.fireLayerEvent(event); |
| } |
| |
| @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; |
| } |
| |
| // Columns |
| |
| @Override |
| public int getColumnCount() { |
| return this.underlyingLayer.getColumnCount(); |
| } |
| |
| @Override |
| public int getPreferredColumnCount() { |
| return this.underlyingLayer.getPreferredColumnCount(); |
| } |
| |
| @Override |
| public int getColumnIndexByPosition(int columnPosition) { |
| return this.underlyingLayer.getColumnIndexByPosition(columnPosition); |
| } |
| |
| @Override |
| public int localToUnderlyingColumnPosition(int localColumnPosition) { |
| return localColumnPosition; |
| } |
| |
| @Override |
| public int underlyingToLocalColumnPosition( |
| ILayer sourceUnderlyingLayer, int underlyingColumnPosition) { |
| if (sourceUnderlyingLayer != this.underlyingLayer) { |
| return -1; |
| } |
| |
| return underlyingColumnPosition; |
| } |
| |
| @Override |
| public Collection<Range> underlyingToLocalColumnPositions( |
| ILayer sourceUnderlyingLayer, |
| Collection<Range> underlyingColumnPositionRanges) { |
| if (sourceUnderlyingLayer != this.underlyingLayer) { |
| return null; |
| } |
| |
| return underlyingColumnPositionRanges; |
| } |
| |
| // Width |
| |
| @Override |
| public int getWidth() { |
| return this.underlyingLayer.getWidth(); |
| } |
| |
| @Override |
| public int getPreferredWidth() { |
| return this.underlyingLayer.getPreferredWidth(); |
| } |
| |
| @Override |
| public int getColumnWidthByPosition(int columnPosition) { |
| return this.underlyingLayer.getColumnWidthByPosition(columnPosition); |
| } |
| |
| // Column resize |
| |
| @Override |
| public boolean isColumnPositionResizable(int columnPosition) { |
| return this.underlyingLayer.isColumnPositionResizable(columnPosition); |
| } |
| |
| // X |
| |
| @Override |
| public int getColumnPositionByX(int x) { |
| return this.underlyingLayer.getColumnPositionByX(x); |
| } |
| |
| @Override |
| public int getStartXOfColumnPosition(int columnPosition) { |
| return this.underlyingLayer.getStartXOfColumnPosition(columnPosition); |
| } |
| |
| // Underlying |
| |
| @Override |
| public Collection<ILayer> getUnderlyingLayersByColumnPosition(int columnPosition) { |
| Collection<ILayer> underlyingLayers = new HashSet<ILayer>(); |
| underlyingLayers.add(this.underlyingLayer); |
| return underlyingLayers; |
| } |
| |
| // Rows |
| |
| @Override |
| public int getRowCount() { |
| return this.underlyingLayer.getRowCount(); |
| } |
| |
| @Override |
| public int getPreferredRowCount() { |
| return this.underlyingLayer.getPreferredRowCount(); |
| } |
| |
| @Override |
| public int getRowIndexByPosition(int rowPosition) { |
| return this.underlyingLayer.getRowIndexByPosition(rowPosition); |
| } |
| |
| @Override |
| public int localToUnderlyingRowPosition(int localRowPosition) { |
| return localRowPosition; |
| } |
| |
| @Override |
| public int underlyingToLocalRowPosition( |
| ILayer sourceUnderlyingLayer, int underlyingRowPosition) { |
| if (sourceUnderlyingLayer != this.underlyingLayer) { |
| return -1; |
| } |
| |
| return underlyingRowPosition; |
| } |
| |
| @Override |
| public Collection<Range> underlyingToLocalRowPositions( |
| ILayer sourceUnderlyingLayer, |
| Collection<Range> underlyingRowPositionRanges) { |
| if (sourceUnderlyingLayer != this.underlyingLayer) { |
| return null; |
| } |
| |
| return underlyingRowPositionRanges; |
| } |
| |
| // Height |
| |
| @Override |
| public int getHeight() { |
| return this.underlyingLayer.getHeight(); |
| } |
| |
| @Override |
| public int getPreferredHeight() { |
| return this.underlyingLayer.getPreferredHeight(); |
| } |
| |
| @Override |
| public int getRowHeightByPosition(int rowPosition) { |
| return this.underlyingLayer.getRowHeightByPosition(rowPosition); |
| } |
| |
| // Row resize |
| |
| @Override |
| public boolean isRowPositionResizable(int rowPosition) { |
| return this.underlyingLayer.isRowPositionResizable(rowPosition); |
| } |
| |
| // Y |
| |
| @Override |
| public int getRowPositionByY(int y) { |
| return this.underlyingLayer.getRowPositionByY(y); |
| } |
| |
| @Override |
| public int getStartYOfRowPosition(int rowPosition) { |
| return this.underlyingLayer.getStartYOfRowPosition(rowPosition); |
| } |
| |
| // Underlying |
| |
| @Override |
| public Collection<ILayer> getUnderlyingLayersByRowPosition(int rowPosition) { |
| Collection<ILayer> underlyingLayers = new HashSet<ILayer>(); |
| underlyingLayers.add(this.underlyingLayer); |
| return underlyingLayers; |
| } |
| |
| // Cell features |
| |
| @Override |
| public ILayerCell getCellByPosition(int columnPosition, int rowPosition) { |
| return this.underlyingLayer.getCellByPosition(columnPosition, rowPosition); |
| } |
| |
| @Override |
| public Rectangle getBoundsByPosition(int columnPosition, int rowPosition) { |
| return this.underlyingLayer.getBoundsByPosition(columnPosition, rowPosition); |
| } |
| |
| @Override |
| public String getDisplayModeByPosition(int columnPosition, int rowPosition) { |
| return this.underlyingLayer.getDisplayModeByPosition(columnPosition, |
| rowPosition); |
| } |
| |
| @Override |
| public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) { |
| return this.underlyingLayer.getConfigLabelsByPosition(columnPosition, |
| rowPosition); |
| } |
| |
| @Override |
| public Object getDataValueByPosition(int columnPosition, int rowPosition) { |
| return this.underlyingLayer.getDataValueByPosition(columnPosition, |
| rowPosition); |
| } |
| |
| @Override |
| public ICellPainter getCellPainter(int columnPosition, int rowPosition, |
| ILayerCell cell, IConfigRegistry configRegistry) { |
| return this.underlyingLayer.getCellPainter(columnPosition, rowPosition, |
| cell, configRegistry); |
| } |
| |
| // IRegionResolver |
| |
| @Override |
| public LabelStack getRegionLabelsByXY(int x, int y) { |
| return this.underlyingLayer.getRegionLabelsByXY(x, y); |
| } |
| |
| @Override |
| public ILayer getUnderlyingLayerByPosition(int columnPosition, int rowPosition) { |
| return this.underlyingLayer; |
| } |
| |
| @Override |
| public IClientAreaProvider getClientAreaProvider() { |
| return this; |
| } |
| |
| @Override |
| public void setClientAreaProvider(IClientAreaProvider clientAreaProvider) { |
| this.underlyingLayer.setClientAreaProvider(clientAreaProvider); |
| } |
| |
| // DND ///////////////////////////////////////////////////////////////// |
| |
| /** |
| * Adds support for dragging items out of this control via a user |
| * drag-and-drop operation. |
| * |
| * @param operations |
| * a bitwise OR of the supported drag and drop operation types ( |
| * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and |
| * <code>DROP_MOVE</code>) |
| * @param transferTypes |
| * the transfer types that are supported by the drag operation |
| * @param listener |
| * the callback that will be invoked to set the drag data and to |
| * cleanup after the drag and drop operation finishes |
| * @see org.eclipse.swt.dnd.DND |
| */ |
| public void addDragSupport(final int operations, |
| final Transfer[] transferTypes, final DragSourceListener listener) { |
| final DragSource dragSource = new DragSource(this, operations); |
| dragSource.setTransfer(transferTypes); |
| |
| DragSourceListener wrapper = new DragSourceListener() { |
| |
| @Override |
| public void dragStart(DragSourceEvent event) { |
| listener.dragStart(event); |
| } |
| |
| @Override |
| public void dragSetData(DragSourceEvent event) { |
| listener.dragSetData(event); |
| } |
| |
| @Override |
| public void dragFinished(DragSourceEvent event) { |
| listener.dragFinished(event); |
| // ensure to stop any current active internal drag mode |
| NatTable.this.modeSupport.switchMode(Mode.NORMAL_MODE); |
| } |
| }; |
| |
| dragSource.addDragListener(wrapper); |
| } |
| |
| /** |
| * Adds support for dropping items into this control via a user |
| * drag-and-drop operation. |
| * |
| * @param operations |
| * a bitwise OR of the supported drag and drop operation types ( |
| * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and |
| * <code>DROP_MOVE</code>) |
| * @param transferTypes |
| * the transfer types that are supported by the drop operation |
| * @param listener |
| * the callback that will be invoked after the drag and drop |
| * operation finishes |
| * @see org.eclipse.swt.dnd.DND |
| */ |
| public void addDropSupport(final int operations, |
| final Transfer[] transferTypes, final DropTargetListener listener) { |
| final DropTarget dropTarget = new DropTarget(this, operations); |
| dropTarget.setTransfer(transferTypes); |
| dropTarget.addDropListener(listener); |
| } |
| |
| // Theme styling |
| |
| /** |
| * Will unregister the style configurations that were applied before by |
| * another {@link ThemeConfiguration} and register the style configurations |
| * of the given {@link ThemeConfiguration}. |
| * |
| * @param themeConfiguration |
| * The ThemeConfiguration that contains the style configurations |
| * to apply. |
| */ |
| public void setTheme(ThemeConfiguration themeConfiguration) { |
| this.themeManager.applyTheme(themeConfiguration); |
| doCommand(new VisualRefreshCommand()); |
| } |
| |
| // Editor |
| /** |
| * Returns the active cell editor that is currently open or {@code null} if |
| * there is no editor active. |
| * |
| * @return the active editor or {@code null} |
| */ |
| public ICellEditor getActiveCellEditor() { |
| return this.activeCellEditor; |
| } |
| |
| /** |
| * Checks if there is an active cell editor registered. If there is one, it |
| * is tried to commit the value that is currently entered there. |
| * |
| * @return <code>false</code> if there is an open editor that can not be |
| * committed because of conversion/validation errors, |
| * <code>true</code> if there is no active open editor or it could |
| * be closed after committing the value. |
| */ |
| public boolean commitAndCloseActiveCellEditor() { |
| if (this.activeCellEditor != null) { |
| return this.activeCellEditor.commit(MoveDirectionEnum.NONE, true); |
| } |
| return true; |
| } |
| |
| } |