diff options
Diffstat (limited to 'plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra')
13 files changed, 1550 insertions, 0 deletions
diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/ServiceConfigAttributes.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/ServiceConfigAttributes.java new file mode 100644 index 00000000000..f9255e4d342 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/ServiceConfigAttributes.java @@ -0,0 +1,132 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.layer.LabelStack; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.style.ConfigAttribute; + +/** + * A factory for unique NatTable configuration attributes for Papyrus services. + */ +public class ServiceConfigAttributes { + private static Map<Class<?>, ConfigAttribute<?>> configAttributes = new HashMap<>(); + + private ServiceConfigAttributes() { + super(); + } + + /** + * Obtains the (unique) configuration attribute for the given service type. + * + * @param serviceType + * a service type + * + * @return the corresponding configuration attribute + */ + @SuppressWarnings("unchecked") + public static <T> ConfigAttribute<T> get(Class<T> serviceType) { + return (ConfigAttribute<T>) configAttributes.computeIfAbsent(serviceType, key -> new ConfigAttribute<T>()); + } + + /** + * Obtains a builder-like configurator object that uses the given service accessor to obtain + * services to register. + * + * @param registry + * the NatTable configuration registry + * @param serviceAccessor + * a service accessor function + * + * @return the configurator + */ + public static Configurator with(IConfigRegistry registry, Function<? super Class<?>, ?> serviceAccessor) { + return new Configurator(registry, serviceAccessor); + } + + public static <T> void registerService(Class<T> serviceType, IConfigRegistry configRegistry, T service) { + configRegistry.registerConfigAttribute(get(serviceType), service); + } + + public static <T> void registerService(Class<T> serviceType, IConfigRegistry configRegistry, T service, String targetDisplayMode) { + configRegistry.registerConfigAttribute(get(serviceType), service, targetDisplayMode); + } + + public static <T> void registerService(Class<T> serviceType, IConfigRegistry configRegistry, T service, String targetDisplayMode, String label) { + configRegistry.registerConfigAttribute(get(serviceType), service, targetDisplayMode, label); + } + + public static <T> T getService(Class<T> serviceType, IConfigRegistry config, String targetDisplayMode, String... labels) { + return config.getConfigAttribute(get(serviceType), targetDisplayMode, labels); + } + + public static <T> T getService(Class<T> serviceType, IConfigRegistry config, String targetDisplayMode, List<String> labels) { + return config.getConfigAttribute(get(serviceType), targetDisplayMode, labels); + } + + public static <T> T getService(Class<T> serviceType, IConfigRegistry config, String targetDisplayMode, LabelStack labels) { + return getService(serviceType, config, targetDisplayMode, labels.getLabels()); + } + + public static <T> T getService(Class<T> serviceType, IConfigRegistry config, ILayerCell cell) { + return getService(serviceType, config, cell.getDisplayMode(), cell.getConfigLabels().getLabels()); + } + + // + // Nested types + // + + /** + * A builder-like configurator object that automatically registers services in a particular + * NatTable configuration registry. + */ + public static class Configurator { + private final IConfigRegistry configRegistry; + private final Function<? super Class<?>, ?> serviceAccessor; + + Configurator(IConfigRegistry configRegistry, Function<? super Class<?>, ?> serviceAccessor) { + super(); + + this.configRegistry = configRegistry; + this.serviceAccessor = serviceAccessor; + } + + <T> T getService(Class<T> serviceType) { + @SuppressWarnings("unchecked") + Function<? super Class<T>, T> accessor = (Function<? super Class<T>, T>) serviceAccessor; + return accessor.apply(serviceType); + } + + public <T> Configurator register(Class<T> serviceType) { + registerService(serviceType, configRegistry, getService(serviceType)); + return this; + } + + public <T> Configurator register(Class<T> serviceType, String targetDisplayMode) { + registerService(serviceType, configRegistry, getService(serviceType), targetDisplayMode); + return this; + } + + public <T> Configurator register(Class<T> serviceType, String targetDisplayMode, String label) { + registerService(serviceType, configRegistry, getService(serviceType), targetDisplayMode, label); + return this; + } + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/hyperlink/HyperlinkNavigationAction.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/hyperlink/HyperlinkNavigationAction.java new file mode 100644 index 00000000000..b5236901c8d --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/hyperlink/HyperlinkNavigationAction.java @@ -0,0 +1,62 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.hyperlink; + +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.selection.action.AbstractMouseSelectionAction; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.IPageManager; +import org.eclipse.papyrus.infra.editor.welcome.nattable.ServiceConfigAttributes; +import org.eclipse.papyrus.infra.services.navigation.service.NavigationService; +import org.eclipse.swt.events.MouseEvent; + +/** + * A NatTable action for navigation of a hyperlink cell. + */ +public class HyperlinkNavigationAction extends AbstractMouseSelectionAction { + + public HyperlinkNavigationAction() { + super(); + } + + <T> T getService(NatTable natTable, Class<T> serviceType) { + return ServiceConfigAttributes.getService(serviceType, natTable.getConfigRegistry(), DisplayMode.NORMAL); + } + + @Override + public void run(NatTable natTable, MouseEvent event) { + super.run(natTable, event); + + Object data = natTable.getDataValueByPosition(getGridColumnPosition(), getGridRowPosition()); + if (data instanceof IObservableValue<?>) { + data = ((IObservableValue<?>) data).getValue(); + } + + IPageManager pages = getService(natTable, IPageManager.class); + if (pages.allPages().contains(data)) { + if (pages.isOpen(data)) { + pages.selectPage(data); + } else { + pages.openPage(data); + } + } else if (data != null) { + getService(natTable, NavigationService.class).navigate(data); + getService(natTable, ISelectionProvider.class).setSelection(new StructuredSelection(data)); + } + } + +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/hyperlink/HyperlinkNavigationConfiguration.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/hyperlink/HyperlinkNavigationConfiguration.java new file mode 100644 index 00000000000..3c795edc3aa --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/hyperlink/HyperlinkNavigationConfiguration.java @@ -0,0 +1,160 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.hyperlink; + +import java.util.function.Predicate; + +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration; +import org.eclipse.nebula.widgets.nattable.layer.LabelStack; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; +import org.eclipse.nebula.widgets.nattable.ui.matcher.IMouseEventMatcher; +import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher; +import org.eclipse.papyrus.infra.editor.welcome.nattable.painter.LabelPainter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; + +/** + * A NatTable configuration that installs hyperlink navigation cursor feed-back and + * actions on the cells of a table that have hyperlinks. + */ +public class HyperlinkNavigationConfiguration<E> extends AbstractUiBindingConfiguration { + private final Class<? extends E> elementType; + private final Predicate<? super E> isActiveHyperlink; + + private Cursor defaultCursor; + private Cursor handCursor; + + private int currentColumn = -1; + private int currentRow = -1; + private Rectangle currentCellTextBounds; + + /** + * Initializes me. + * + * @param owner + * the control that owns the {@link NatTable} that I will configure + * @param elementType + * the type of object that is a potential hyperlink + * @param isActiveHyperlink + * a predicate matching valid, navigable hyperlink elements + */ + public HyperlinkNavigationConfiguration(Control owner, Class<? extends E> elementType, Predicate<? super E> isActiveHyperlink) { + super(); + + this.elementType = elementType; + this.isActiveHyperlink = isActiveHyperlink; + + this.defaultCursor = owner.getCursor(); + this.handCursor = new Cursor(owner.getDisplay(), SWT.CURSOR_HAND); + + owner.addDisposeListener(event -> handCursor.dispose()); + } + + @Override + public void configureUiBindings(UiBindingRegistry uiBindingRegistry) { + uiBindingRegistry.registerSingleClickBinding( + MouseEventMatcher.bodyLeftClick(SWT.NONE), + new HyperlinkNavigationAction()); + + // On moving the mouse into the body area, show a navigation cursor + uiBindingRegistry.registerFirstMouseMoveBinding( + new IMouseEventMatcher() { + @Override + public boolean matches(NatTable natTable, MouseEvent event, LabelStack regionLabels) { + int col = natTable.getColumnPositionByX(event.x); + int row = natTable.getRowPositionByY(event.y); + + return (col >= 0) && (row >= 0); + } + + }, this::updateCursor); + + // On moving the mouse out of the body area, restore the usual cursor + uiBindingRegistry.registerMouseMoveBinding(new IMouseEventMatcher() { + @Override + public boolean matches(NatTable natTable, MouseEvent event, LabelStack regionLabels) { + return (natTable != null && regionLabels == null); + } + + }, this::reset); + + // And same if it is moved out of the table altogether + uiBindingRegistry.registerMouseExitBinding(new IMouseEventMatcher() { + @Override + public boolean matches(NatTable natTable, MouseEvent event, LabelStack regionLabels) { + return true; + } + + }, this::reset); + } + + void updateCursor(NatTable table, MouseEvent event) { + int col = table.getColumnPositionByX(event.x); + int row = table.getRowPositionByY(event.y); + Object data = (col >= 0) && (row >= 0) ? table.getDataValueByPosition(col, row) : null; + + if (!elementType.isInstance(data) || !isActiveHyperlink.test(elementType.cast(data))) { + // not a havigable hyperlink + table.setCursor(defaultCursor); + } else { + // are we over text? + if ((currentCellTextBounds == null) || (col != currentColumn) || (row != currentRow)) { + updateCellTextBounds(table, col, row); + } + + if (currentCellTextBounds != null && currentCellTextBounds.contains(event.x, event.y)) { + table.setCursor(handCursor); + } else { + table.setCursor(defaultCursor); + } + } + } + + void reset(NatTable table, MouseEvent event) { + currentColumn = -1; + currentRow = -1; + currentCellTextBounds = null; + + table.setCursor(defaultCursor); + } + + void updateCellTextBounds(NatTable table, int column, int row) { + currentColumn = column; + currentRow = row; + currentCellTextBounds = null; + + ILayerCell cell = table.getCellByPosition(column, row); + if (cell != null) { + ICellPainter painter = table.getCellPainter(column, row, cell, table.getConfigRegistry()); + if (painter instanceof LabelPainter) { + GC gc = new GC(table.getDisplay()); + try { + currentCellTextBounds = ((LabelPainter) painter).getTextBounds(cell, gc, table.getConfigRegistry()); + } finally { + gc.dispose(); + } + } else { + // Assume the whole cell is text, then + currentCellTextBounds = cell.getBounds(); + } + } + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/internal/Activator.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/internal/Activator.java new file mode 100644 index 00000000000..be2ea27bebf --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/internal/Activator.java @@ -0,0 +1,60 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.editor.welcome.nattable.internal; + +import org.eclipse.papyrus.infra.core.log.LogHelper; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.papyrus.infra.editor.welcome.nattable"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + public static LogHelper log; + + /** + * The constructor + */ + public Activator() { + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + log = new LogHelper(this); + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/internal/widgets/LanguagesTable.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/internal/widgets/LanguagesTable.java new file mode 100644 index 00000000000..9b677ba672b --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/internal/widgets/LanguagesTable.java @@ -0,0 +1,91 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.internal.widgets; + +import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; +import org.eclipse.papyrus.infra.editor.welcome.internal.modelelements.LanguageObservable; +import org.eclipse.papyrus.infra.editor.welcome.nattable.widgets.FormTable; +import org.eclipse.swt.widgets.Composite; + +/** + * A table widget presenting languages in two columns for name and version. + */ +public class LanguagesTable extends FormTable<LanguageObservable> { + + public LanguagesTable(Composite parent, int style) { + // No hyperlinks + super(parent, style, "Languages:", new LanguagesColumnAccessor(), "Name", "Version"); + } + + // + // Nested types + // + + static class LanguagesColumnAccessor implements IColumnPropertyAccessor<LanguageObservable> { + static final String NAME = "name"; //$NON-NLS-1$ + static final String VERSION = "version"; //$NON-NLS-1$ + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getDataValue(LanguageObservable rowObject, int columnIndex) { + Object result; + + switch (columnIndex) { + case 0: + result = rowObject.getName(); + break; + case 1: + result = rowObject.getVersion(); + break; + default: + throw new IndexOutOfBoundsException(Integer.toString(columnIndex)); + } + + return result; + } + + @Override + public void setDataValue(LanguageObservable rowObject, int columnIndex, Object newValue) { + throw new IllegalStateException("not editable"); //$NON-NLS-1$ + } + + @Override + public String getColumnProperty(int columnIndex) { + switch (columnIndex) { + case 0: + return NAME; + case 1: + return VERSION; + default: + throw new IndexOutOfBoundsException(Integer.toString(columnIndex)); + } + } + + @Override + public int getColumnIndex(String propertyName) { + switch (propertyName) { + case NAME: + return 0; + case VERSION: + return 1; + default: + throw new IllegalArgumentException(propertyName); + } + } + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelPainter.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelPainter.java new file mode 100644 index 00000000000..cdc86d17ed0 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelPainter.java @@ -0,0 +1,119 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.painter; + +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.CellPainterDecorator; +import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; + +/** + * @author damus + * + */ +public class LabelPainter extends CellPainterDecorator { + + public LabelPainter(CellEdgeEnum cellEdge) { + this(new LabelProviderTextPainter(), cellEdge, new LabelProviderImagePainter()); + } + + public LabelPainter(ICellPainter textPainter, CellEdgeEnum cellEdge) { + this(textPainter, cellEdge, new LabelProviderImagePainter()); + } + + public LabelPainter(CellEdgeEnum cellEdge, ICellPainter imagePainter) { + this(new LabelProviderTextPainter(), cellEdge, imagePainter); + } + + public LabelPainter(ICellPainter textPainter, CellEdgeEnum cellEdge, ICellPainter imagePainter) { + super(textPainter, cellEdge, imagePainter); + } + + public LabelPainter(ICellPainter textPainter, CellEdgeEnum cellEdge, ICellPainter imagePainter, boolean paintDecorationDependent) { + super(textPainter, cellEdge, imagePainter, paintDecorationDependent); + } + + public LabelPainter(ICellPainter textPainter, CellEdgeEnum cellEdge, int spacing, ICellPainter imagePainter) { + super(textPainter, cellEdge, spacing, imagePainter); + } + + public LabelPainter(ICellPainter textPainter, CellEdgeEnum cellEdge, int spacing, ICellPainter imagePainter, boolean paintDecorationDependent) { + super(textPainter, cellEdge, spacing, imagePainter, paintDecorationDependent); + } + + public LabelPainter(ICellPainter textPainter, CellEdgeEnum cellEdge, int spacing, ICellPainter imagePainter, boolean paintDecorationDependent, boolean paintBg) { + super(textPainter, cellEdge, spacing, imagePainter, paintDecorationDependent, paintBg); + } + + public Rectangle getTextBounds(ILayerCell cell, GC gc, IConfigRegistry configRegistry) { + Rectangle cellBounds = cell.getBounds(); + + int imageWidth = getDecoratorCellPainter().getPreferredWidth(cell, gc, configRegistry); + int imageHeight = getDecoratorCellPainter().getPreferredHeight(cell, gc, configRegistry); + + // grab any extra space: + int preferredWidth = getBaseCellPainter().getPreferredWidth(cell, gc, configRegistry); + int preferredHeight = getBaseCellPainter().getPreferredHeight(cell, gc, configRegistry); + + Rectangle result = null; + + switch (getCellEdge()) { + case LEFT: + result = new Rectangle(cellBounds.x + imageWidth + getSpacing(), + cellBounds.y, preferredWidth, cellBounds.height); + break; + case RIGHT: + result = new Rectangle(cellBounds.x, cellBounds.y, preferredWidth, cellBounds.height); + break; + case TOP: + result = new Rectangle(cellBounds.x, + cellBounds.y + imageHeight + getSpacing(), + cellBounds.width, preferredHeight); + break; + case BOTTOM: + result = new Rectangle(cellBounds.x, cellBounds.y, cellBounds.width, preferredHeight); + break; + case TOP_LEFT: + result = new Rectangle(cellBounds.x + imageWidth + getSpacing(), + cellBounds.y + imageHeight + getSpacing(), + preferredWidth, preferredHeight); + break; + case TOP_RIGHT: + result = new Rectangle(cellBounds.x, + cellBounds.y + imageHeight + getSpacing(), + preferredWidth, preferredHeight); + break; + case BOTTOM_LEFT: + result = new Rectangle(cellBounds.x + imageWidth + getSpacing(), + cellBounds.y, preferredWidth, preferredHeight); + break; + case BOTTOM_RIGHT: + result = new Rectangle(cellBounds.x, cellBounds.y, preferredWidth, preferredHeight); + break; + case NONE: + break; + } + + if (result != null) { + // Crop to the actual cell bounds + result = result.intersection(cellBounds); + } + + return result; + } + +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelProviderImagePainter.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelProviderImagePainter.java new file mode 100644 index 00000000000..048820b9b29 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelProviderImagePainter.java @@ -0,0 +1,52 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.painter; + +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter; +import org.eclipse.papyrus.infra.editor.welcome.nattable.ServiceConfigAttributes; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.swt.graphics.Image; + +/** + * An image painter that obtains images from the {@link LabelProviderService} registered + * in the NatTable configuration registry. + */ +public class LabelProviderImagePainter extends ImagePainter { + + public LabelProviderImagePainter() { + super(); + } + + public LabelProviderImagePainter(boolean paintBg) { + super(null, paintBg); + } + + @Override + protected Image getImage(ILayerCell cell, IConfigRegistry configRegistry) { + Object dataValue = cell.getDataValue(); + + if (dataValue instanceof IObservableValue<?>) { + dataValue = ((IObservableValue<?>) dataValue).getValue(); + } + + LabelProviderService labelService = ServiceConfigAttributes.getService(LabelProviderService.class, configRegistry, cell); + ILabelProvider labelProvider = (dataValue == null) ? labelService.getLabelProvider() : labelService.getLabelProvider(dataValue); + + return labelProvider.getImage(dataValue); + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelProviderTextPainter.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelProviderTextPainter.java new file mode 100644 index 00000000000..671337988b4 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/painter/LabelProviderTextPainter.java @@ -0,0 +1,77 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.painter; + +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter; +import org.eclipse.papyrus.infra.editor.welcome.nattable.ServiceConfigAttributes; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; + +/** + * A text painter that obtains text labels from the {@link LabelProviderService} registered + * in the NatTable configuration registry. + */ +public class LabelProviderTextPainter extends TextPainter { + + public LabelProviderTextPainter() { + super(); + } + + public LabelProviderTextPainter(boolean wrapText, boolean paintBg, boolean calculateByTextLength, boolean calculateByTextHeight) { + super(wrapText, paintBg, calculateByTextLength, calculateByTextHeight); + } + + public LabelProviderTextPainter(boolean wrapText, boolean paintBg, boolean calculate) { + super(wrapText, paintBg, calculate); + } + + public LabelProviderTextPainter(boolean wrapText, boolean paintBg, int spacing, boolean calculateByTextLength, boolean calculateByTextHeight) { + super(wrapText, paintBg, spacing, calculateByTextLength, calculateByTextHeight); + } + + public LabelProviderTextPainter(boolean wrapText, boolean paintBg, int spacing, boolean calculate) { + super(wrapText, paintBg, spacing, calculate); + } + + public LabelProviderTextPainter(boolean wrapText, boolean paintBg, int spacing) { + super(wrapText, paintBg, spacing); + } + + public LabelProviderTextPainter(boolean wrapText, boolean paintBg) { + super(wrapText, paintBg); + } + + @Override + protected String convertDataType(ILayerCell cell, IConfigRegistry configRegistry) { + String result; + Object dataValue = cell.getDataValue(); + if (dataValue instanceof String) { + result = (String) dataValue; + } else { + if (dataValue instanceof IObservableValue<?>) { + dataValue = ((IObservableValue<?>) dataValue).getValue(); + } + + LabelProviderService labelService = ServiceConfigAttributes.getService(LabelProviderService.class, configRegistry, cell); + ILabelProvider labelProvider = (dataValue == null) ? labelService.getLabelProvider() : labelService.getLabelProvider(dataValue); + + result = labelProvider.getText(dataValue); + } + + return result; + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/ColumnHeaderHelper.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/ColumnHeaderHelper.java new file mode 100644 index 00000000000..6f6072be724 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/ColumnHeaderHelper.java @@ -0,0 +1,157 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.sorting; + +import java.text.Collator; +import java.util.Comparator; + +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel; +import org.eclipse.nebula.widgets.nattable.grid.GridRegion; +import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.eclipse.nebula.widgets.nattable.layer.ILayer; +import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.sort.ISortModel; +import org.eclipse.nebula.widgets.nattable.sort.SortConfigAttributes; +import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer; +import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration; +import org.eclipse.nebula.widgets.nattable.sort.painter.SortableHeaderTextPainter; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.nebula.widgets.nattable.style.IStyle; +import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum; +import org.eclipse.papyrus.infra.editor.welcome.nattable.ServiceConfigAttributes; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; + +import ca.odell.glazedlists.SortedList; + +/** + * A helper utility for the configuration of sorting a NatTable that renders a + * SWT Forms-compatible flat presentation. + */ +public class ColumnHeaderHelper<T> { + private final IConfigRegistry configRegistry; + + private final ILayer viewportLayer; + private final SelectionLayer selectionLayer; + + private IColumnPropertyAccessor<T> columnAccessor; + private DataLayer headerData; + private SortHeaderLayer<T> sortHeaders; + + /** + * Initializes me. + * + * @param configRegistry + * the NatTable configuration registry + * @param viewportLayer + * the viewport layer to track for positioning of header cells + * @param selectionLayer + * the selection layer in the table body + */ + public ColumnHeaderHelper(IConfigRegistry configRegistry, ILayer viewportLayer, SelectionLayer selectionLayer) { + super(); + + this.configRegistry = configRegistry; + this.viewportLayer = viewportLayer; + this.selectionLayer = selectionLayer; + } + + /** + * Creates the header layer. <b>Note</b> that this must be done exactly once, as the returned + * layer and other parts of it are retained for follow-up work. + * + * @param data + * the sorted Glazed-list backing store of the table + * @param columnAccessor + * the column accessor describing the columns to be presented + * @param columnHeadings + * the localized heading titles corresponding to each column + * + * @return the column header layer + */ + public ILayer createHeaderLayer(SortedList<T> data, IColumnPropertyAccessor<T> columnAccessor, String... columnHeadings) { + this.columnAccessor = columnAccessor; + IDataProvider headerProvider = new DefaultColumnHeaderDataProvider(columnHeadings); + headerData = new DataLayer(headerProvider); + ColumnHeaderLayer headers = new ColumnHeaderLayer(headerData, viewportLayer, selectionLayer); + ISortModel sortModel = new GlazedListsSortModel<>(data, columnAccessor, configRegistry, headerData); + sortHeaders = new SortHeaderLayer<>(headers, sortModel, false); + return sortHeaders; + } + + /** + * Configures the header layer {@linkplain #createHeaderLayer previously created} for sorting + * control behavior. + * + * @see #createHeaderLayer(SortedList, IColumnPropertyAccessor, String...) + */ + public void configureSorting() { + ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(headerData); + headerData.setConfigLabelAccumulator(labelAccumulator); + + for (int i = 0; i < columnAccessor.getColumnCount(); i++) { + String label = sortLabel(i); + labelAccumulator.registerColumnOverrides(i, label); + configRegistry.registerConfigAttribute(SortConfigAttributes.SORT_COMPARATOR, getByLabelOrdering(), DisplayMode.NORMAL, label); + } + } + + /** + * Configures the visual styling of the header layer {@linkplain #createHeaderLayer previously created} + * for presentation in a SWT Forms UI with a flat appearance. + * + * @see #createHeaderLayer(SortedList, IColumnPropertyAccessor, String...) + */ + public void configureHeaders(ICellPainter cellPainter, IStyle style) { + sortHeaders.addConfiguration(new SingleClickSortConfiguration(new SortableHeaderTextPainter(cellPainter, CellEdgeEnum.LEFT, true, 3, true))); + configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, cellPainter, DisplayMode.NORMAL, GridRegion.COLUMN_HEADER); + configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, style, DisplayMode.NORMAL, GridRegion.COLUMN_HEADER); + for (int i = 0; i < columnAccessor.getColumnCount(); i++) { + String label = sortLabel(i); + configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, cellPainter, DisplayMode.NORMAL, label); + configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, style, DisplayMode.NORMAL, label); + } + } + + String sortLabel(int columnIndex) { + return sortLabel(columnAccessor.getColumnProperty(columnIndex)); + } + + static String sortLabel(String propertyName) { + return "sortBy:" + propertyName; + } + + private Comparator<IObservableValue<?>> getByLabelOrdering() { + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + return (a, b) -> collator.compare(getLabel(a), getLabel(b)); + } + + private String getLabel(IObservableValue<?> observable) { + LabelProviderService labels = ServiceConfigAttributes.getService(LabelProviderService.class, configRegistry, DisplayMode.NORMAL); + Object value = (observable == null) ? null : observable.getValue(); + ILabelProvider labelProvider = (value == null) ? null : labels.getLabelProvider(observable); + return (labelProvider == null) ? null : labelProvider.getText(value); + } + +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/EventListObservableAdapter.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/EventListObservableAdapter.java new file mode 100644 index 00000000000..5145cb4aeba --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/EventListObservableAdapter.java @@ -0,0 +1,198 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.sorting; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; + +import ca.odell.glazedlists.AbstractEventList; +import ca.odell.glazedlists.event.ListEventPublisher; +import ca.odell.glazedlists.util.concurrent.LockFactory; + +/** + * @author damus + * + */ +public class EventListObservableAdapter<E> extends AbstractEventList<E> { + private final IChangeListener elementListener; + private final IListChangeListener<E> listener; + private IObservableList<E> delegate; + + public EventListObservableAdapter() { + this(null); + } + + public EventListObservableAdapter(ListEventPublisher publisher) { + super(publisher); + + this.readWriteLock = LockFactory.DEFAULT.createReadWriteLock(); + + this.listener = new IListChangeListener<E>() { + @Override + public void handleListChange(ListChangeEvent<? extends E> event) { + readWriteLock.readLock().lock(); + try { + updates.beginEvent(); + try { + Stream.of(event.diff.getDifferences()).forEach(entry -> { + if (entry.isAddition()) { + updates.elementInserted(entry.getPosition(), entry.getElement()); + connect(entry.getElement()); + } else { + disconnect(entry.getElement()); + updates.elementDeleted(entry.getPosition(), entry.getElement()); + } + }); + } finally { + updates.commitEvent(); + } + } finally { + readWriteLock.readLock().unlock(); + } + } + }; + + this.elementListener = new IChangeListener() { + + @Override + public void handleChange(ChangeEvent event) { + readWriteLock.readLock().lock(); + try { + updates.beginEvent(); + try { + @SuppressWarnings("unchecked") + E element = (E) event.getObservable(); + updates.elementUpdated(indexOf(element), element, element); + } finally { + updates.commitEvent(); + } + } finally { + readWriteLock.readLock().unlock(); + } + } + }; + } + + public synchronized void setDelegate(IObservableList<E> newDelegate) { + IObservableList<E> oldDelegate = this.delegate; + this.delegate = newDelegate; + + if (oldDelegate != null) { + oldDelegate.removeListChangeListener(listener); + } + if (newDelegate != null) { + newDelegate.addListChangeListener(listener); + } + + readWriteLock.readLock().lock(); + try { + updates.beginEvent(); + try { + AtomicInteger i = new AtomicInteger(); + if (oldDelegate != null) { + // Report updates for elements that have correspondents in the new list + oldDelegate.subList(0, (newDelegate == null) ? 0 : newDelegate.size()).forEach(e -> { + E newE = newDelegate.get(i.get()); + disconnect(e); + updates.elementUpdated(i.getAndIncrement(), e, newE); + connect(newE); + }); + + // Report deletions for any further elements + if (oldDelegate.size() < i.get()) { + oldDelegate.forEach(e -> { + disconnect(e); + updates.elementDeleted(i.getAndIncrement(), e); + }); + } + } + + if (newDelegate != null) { + if ((newDelegate.size() > i.get())) { + // Report insertions for any further new elements + newDelegate.listIterator(i.get()).forEachRemaining(e -> { + updates.elementInserted(i.getAndIncrement(), e); + connect(e); + }); + } + } + } finally { + updates.commitEvent(); + } + } finally { + readWriteLock.readLock().unlock(); + } + } + + @Override + public void dispose() { + if (delegate != null) { + delegate.removeListChangeListener(listener); + } + } + + @Override + public int size() { + return (delegate == null) ? 0 : delegate.size(); + } + + @Override + public E get(int index) { + checkDelegate(); + return delegate.get(index); + } + + private void checkDelegate() { + if (delegate == null) { + throw new IndexOutOfBoundsException(); + } + } + + @Override + public void add(int index, E value) { + checkDelegate(); + delegate.add(index, value); + } + + @Override + public E set(int index, E value) { + checkDelegate(); + return delegate.set(index, value); + } + + @Override + public E remove(int index) { + checkDelegate(); + return delegate.remove(index); + } + + private void connect(E newElement) { + if (newElement instanceof IObservable) { + ((IObservable) newElement).addChangeListener(elementListener); + } + } + + private void disconnect(E newElement) { + if (newElement instanceof IObservable) { + ((IObservable) newElement).removeChangeListener(elementListener); + } + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/PapyrusGlazedListEventsLayer.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/PapyrusGlazedListEventsLayer.java new file mode 100644 index 00000000000..a8bb13c1439 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/sorting/PapyrusGlazedListEventsLayer.java @@ -0,0 +1,159 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.sorting; + +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer; +import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralRefreshEvent; +import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent; +import org.eclipse.swt.widgets.Display; + +import ca.odell.glazedlists.EventList; +import ca.odell.glazedlists.event.ListEvent; +import ca.odell.glazedlists.event.ListEventListener; + +/** + * An alternative to the {@link GlazedListsEventLayer} that doesn't run a thread + * continuously checking for updates every 100 milliseconds, but instead posts + * 100-millisecond deferred updates that coalesce events in the interim. + */ +public class PapyrusGlazedListEventsLayer<E> extends AbstractLayerTransform implements IUniqueIndexLayer, ListEventListener<E> { + + private final IUniqueIndexLayer underlyingLayer; + private EventList<? extends E> eventList; + + private volatile boolean active; + private volatile Update pendingUpdate; + + /** + * Initializes me. + * + * @param underlyingLayer + * the layer to which I post refresh events + * @param eventList + * the event list from which the {@code underlyingLayer} obtains its data + */ + public PapyrusGlazedListEventsLayer(IUniqueIndexLayer underlyingLayer, EventList<? extends E> eventList) { + super(underlyingLayer); + this.underlyingLayer = underlyingLayer; + this.eventList = eventList; + + this.eventList.addListEventListener(this); + } + + @Override + public void dispose() { + super.dispose(); + + if (eventList != null) { + eventList.removeListEventListener(this); + eventList = null; + } + } + + /** + * Queries whether I am currently reactiving to changes in the list that I am observing. + * + * @return whether I am active + */ + public boolean isActive() { + return active; + } + + /** + * Sets I am currently reactiving to changes in the list that I am observing. + * It is useful to deactivate me when initiating bulk operations on the list + * to avoid useless work when a full table refresh will follow, anyways. + * + * @param active + * whether I am active + */ + public void setActive(boolean active) { + this.active = active; + } + + // + // Event handling + // + + @Override + public void listChanged(ListEvent<E> event) { + // Check whether we even want to process anything + if (!isActive()) { + return; + } + + boolean structure = false; + + out: while (event.next()) { + int eventType = event.getType(); + switch (eventType) { + case ListEvent.DELETE: + case ListEvent.INSERT: + structure = true; + Update update = pendingUpdate; + if (update != null) { + update.structuralUpdate = structure; + } + break out; + } + } + + if (pendingUpdate == null) { + // May have missed the boat + pendingUpdate = new Update(); + pendingUpdate.structuralUpdate = structure; + Display.getDefault().timerExec(100, pendingUpdate); + } + } + + // + // IUniqueIndexLayer protocol + // + + @Override + public int getColumnPositionByIndex(int columnIndex) { + return this.underlyingLayer.getColumnPositionByIndex(columnIndex); + } + + @Override + public int getRowPositionByIndex(int rowIndex) { + return this.underlyingLayer.getRowPositionByIndex(rowIndex); + } + + // + // Nested types + // + + private class Update implements Runnable { + volatile boolean structuralUpdate; + + @Override + public void run() { + if (pendingUpdate == this) { + pendingUpdate = null; + + // Check that we haven't been disposed since posting the update + if (eventList != null) { + if (structuralUpdate) { + fireLayerEvent(new RowStructuralRefreshEvent(getUnderlyingLayer())); + } else { + fireLayerEvent(new VisualRefreshEvent(getUnderlyingLayer())); + } + } + } + } + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/widgets/FormTable.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/widgets/FormTable.java new file mode 100644 index 00000000000..615f360c423 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/widgets/FormTable.java @@ -0,0 +1,230 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.widgets; + +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; +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.data.IColumnPropertyAccessor; +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.GridRegion; +import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.eclipse.nebula.widgets.nattable.layer.ILayer; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum; +import org.eclipse.nebula.widgets.nattable.style.Style; +import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum; +import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; +import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor; +import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.IPageManager; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.editor.welcome.nattable.ServiceConfigAttributes; +import org.eclipse.papyrus.infra.editor.welcome.nattable.painter.LabelPainter; +import org.eclipse.papyrus.infra.editor.welcome.nattable.painter.LabelProviderImagePainter; +import org.eclipse.papyrus.infra.editor.welcome.nattable.painter.LabelProviderTextPainter; +import org.eclipse.papyrus.infra.editor.welcome.nattable.sorting.ColumnHeaderHelper; +import org.eclipse.papyrus.infra.editor.welcome.nattable.sorting.EventListObservableAdapter; +import org.eclipse.papyrus.infra.editor.welcome.nattable.sorting.PapyrusGlazedListEventsLayer; +import org.eclipse.papyrus.infra.emf.utils.EMFHelper; +import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForEObject; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.papyrus.infra.services.navigation.service.NavigationService; +import org.eclipse.papyrus.views.properties.modelelement.DataSource; +import org.eclipse.papyrus.views.properties.modelelement.ModelElement; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import ca.odell.glazedlists.SortedList; + +/** + * A {@link NatTable} composite with a "flat" presentation suitable for use in an SWT Forms context. + */ +public abstract class FormTable<E> extends Composite { + private final IConfigRegistry configRegistry; + private final NatTable table; + private final EventListObservableAdapter<E> backingList; + private final PapyrusGlazedListEventsLayer<E> eventLayer; + + private String propertyPath; + private DataSource input; + + public FormTable(Composite parent, int style, IColumnPropertyAccessor<E> columnAccessor, String... columnTitle) { + this(parent, style, null, columnAccessor, columnTitle); + } + + public FormTable(Composite parent, int style, String label, IColumnPropertyAccessor<E> columnAccessor, String... columnTitle) { + super(parent, style); + + setBackground(parent.getBackground()); + setBackgroundMode(SWT.INHERIT_DEFAULT); + + setLayout(new GridLayout()); + + configRegistry = new ConfigRegistry(); + + // Optional label for the table + if ((label != null) && !label.isEmpty()) { + Label _label = new Label(parent, SWT.LEFT); + _label.setText(label); + } + + // Body of the table + backingList = new EventListObservableAdapter<>(); + SortedList<E> sortedDiagrams = new SortedList<>(backingList, null); + IDataProvider dataProvider = new GlazedListsDataProvider<>(sortedDiagrams, columnAccessor); + DataLayer bodyDataLayer = new DataLayer(dataProvider); + eventLayer = new PapyrusGlazedListEventsLayer<>(bodyDataLayer, backingList); + SelectionLayer selectionLayer = new SelectionLayer(eventLayer, false); + addConfigurations(selectionLayer); + ViewportLayer viewportLayer = new ViewportLayer(selectionLayer); + viewportLayer.setRegionName(GridRegion.BODY); + + // Column headers + ColumnHeaderHelper<E> headerHelper = new ColumnHeaderHelper<>(configRegistry, viewportLayer, selectionLayer); + ILayer headers = headerHelper.createHeaderLayer(sortedDiagrams, columnAccessor, columnTitle); + + // Arrange the headers and body together + CompositeLayer composition = new CompositeLayer(1, 2); + composition.setChildLayer(GridRegion.COLUMN_HEADER, headers, 0, 0); + composition.setChildLayer(GridRegion.BODY, viewportLayer, 0, 1); + + // Sorting configuration + headerHelper.configureSorting(); + + // + // Presentation configuration + // + ICellPainter commonPainter = new LabelProviderTextPainter(false, true, 3, true); + ICellPainter iconAndTextPainter = new LabelPainter(commonPainter, CellEdgeEnum.LEFT, new LabelProviderImagePainter()); + + // Headers + Style headerStyle = new Style(); + headerStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, parent.getBackground()); + headerStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT); + headerStyle.setAttributeValue(CellStyleAttributes.FONT, JFaceResources.getFont(JFaceResources.BANNER_FONT)); + headerHelper.configureHeaders(commonPainter, headerStyle); + + // Body + configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, iconAndTextPainter, DisplayMode.NORMAL, GridRegion.BODY); + Style bodyStyle = new Style(); + bodyStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, parent.getBackground()); + bodyStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT); + configureBodyStyle(bodyStyle); + configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, bodyStyle, DisplayMode.NORMAL, GridRegion.BODY); + configRegistry.registerConfigAttribute(CellConfigAttributes.RENDER_GRID_LINES, false); + + table = new NatTable(parent, composition, false); + GridDataFactory.fillDefaults().grab(true, true).applyTo(table); + table.setConfigRegistry(configRegistry); + table.setBackground(parent.getBackground()); + table.addConfiguration(new DefaultNatTableStyleConfiguration()); + table.configure(); + } + + protected void addConfigurations(SelectionLayer selectionLayer) { + // Pass + } + + protected void configureBodyStyle(Style bodyStyle) { + // Pass + } + + /** + * Sets the qualified property name (including model element path) of the + * {@linkplain DataSource data-source} property to access. The referenced + * property must be an {@linkplain IObservableList observable list}. + * + * @param propertyPath + * the property path + */ + public void setProperty(String propertyPath) { + this.propertyPath = propertyPath; + checkInput(); + } + + public String getProperty() { + return propertyPath; + } + + /** + * Sets the input DataSource for this Property editor. + * + * @param input + */ + public void setInput(DataSource input) { + this.input = input; + checkInput(); + } + + protected void checkInput() { + String propertyPath = getProperty(); + + if ((input != null) && (propertyPath != null)) { + String elementPath = propertyPath.substring(0, propertyPath.lastIndexOf(':')); + String propertyName = propertyPath.substring(elementPath.length() + 1); + + ModelElement element = input.getModelElement(elementPath); + + ServiceConfigAttributes.with(configRegistry, this::getService) + .register(LabelProviderService.class) + .register(NavigationService.class) + .register(IPageManager.class); + ServiceConfigAttributes.registerService(ISelectionProvider.class, configRegistry, + getService(IMultiDiagramEditor.class).getEditorSite().getSelectionProvider()); + + eventLayer.setActive(false); + try { + @SuppressWarnings("unchecked") + IObservableList<E> diagrams = (IObservableList<E>) element.getObservable(propertyName); + this.backingList.setDelegate(diagrams); + } finally { + eventLayer.setActive(true); + table.refresh(); + } + } + } + + /** + * @return the input DataSource for this Property editor + */ + public DataSource getInput() { + return input; + } + + protected EObject getModelElement() { + return EMFHelper.getEObject(input.getSelection().getFirstElement()); + } + + protected <S> S getService(Class<? extends S> serviceType) { + try { + return ServiceUtilsForEObject.getInstance().getService(serviceType, getModelElement()); + } catch (ServiceException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/widgets/HyperlinkTable.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/widgets/HyperlinkTable.java new file mode 100644 index 00000000000..f268f4d4495 --- /dev/null +++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome.nattable/src/org/eclipse/papyrus/infra/editor/welcome/nattable/widgets/HyperlinkTable.java @@ -0,0 +1,53 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus 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: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.editor.welcome.nattable.widgets; + +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; +import org.eclipse.nebula.widgets.nattable.style.Style; +import org.eclipse.nebula.widgets.nattable.style.TextDecorationEnum; +import org.eclipse.nebula.widgets.nattable.util.GUIHelper; +import org.eclipse.papyrus.infra.editor.welcome.nattable.hyperlink.HyperlinkNavigationConfiguration; +import org.eclipse.swt.widgets.Composite; + +/** + * A table of hyperlinks for objects. One or more columns may render as hyperlinks to + * diagrams, views, etc. Label-based sorting of columns is provided. + */ +public abstract class HyperlinkTable<E> extends FormTable<E> { + public HyperlinkTable(Composite parent, int style, IColumnPropertyAccessor<E> columnAccessor, String... columnTitle) { + this(parent, style, null, columnAccessor, columnTitle); + } + + public HyperlinkTable(Composite parent, int style, String label, IColumnPropertyAccessor<E> columnAccessor, String... columnTitle) { + super(parent, style, label, columnAccessor, columnTitle); + } + + @Override + protected void addConfigurations(SelectionLayer selectionLayer) { + selectionLayer.addConfiguration(new HyperlinkNavigationConfiguration<>(getParent(), IObservableValue.class, this::isActiveHyperlink)); + } + + @Override + protected void configureBodyStyle(Style bodyStyle) { + bodyStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_BLUE); + bodyStyle.setAttributeValue(CellStyleAttributes.TEXT_DECORATION, TextDecorationEnum.UNDERLINE); + } + + protected boolean isActiveHyperlink(IObservableValue<?> value) { + return (value != null) && (value.getValue() != null); + } +} |