/*
* Copyright 2015 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.robotframework.ide.eclipse.main.plugin.tableeditor.variables;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.tools.services.IDirtyProviderService;
import org.eclipse.e4.ui.di.Persist;
import org.eclipse.e4.ui.di.UIEventTopic;
import org.eclipse.jface.layout.GridDataFactory;
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.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.hover.HoverLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.selection.EditTraversalStrategy;
import org.eclipse.nebula.widgets.nattable.selection.ITraversalStrategy;
import org.eclipse.nebula.widgets.nattable.selection.MoveCellSelectionCommandHandler;
import org.eclipse.nebula.widgets.nattable.selection.RowSelectionProvider;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.sort.ISortModel;
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorSite;
import org.robotframework.ide.eclipse.main.plugin.RedPlugin;
import org.robotframework.ide.eclipse.main.plugin.RedPreferences;
import org.robotframework.ide.eclipse.main.plugin.RedPreferences.CellWrappingStrategy;
import org.robotframework.ide.eclipse.main.plugin.hyperlink.TableHyperlinksSupport;
import org.robotframework.ide.eclipse.main.plugin.hyperlink.detectors.ITableHyperlinksDetector;
import org.robotframework.ide.eclipse.main.plugin.hyperlink.detectors.TableHyperlinksToVariablesDetector;
import org.robotframework.ide.eclipse.main.plugin.model.RobotElement;
import org.robotframework.ide.eclipse.main.plugin.model.RobotElementChange;
import org.robotframework.ide.eclipse.main.plugin.model.RobotElementChange.Kind;
import org.robotframework.ide.eclipse.main.plugin.model.RobotModelEvents;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSuiteFile;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSuiteFileSection;
import org.robotframework.ide.eclipse.main.plugin.model.RobotVariable;
import org.robotframework.ide.eclipse.main.plugin.model.RobotVariablesSection;
import org.robotframework.ide.eclipse.main.plugin.model.cmd.variables.CreateFreshVariableCommand;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.AddingToken;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.FilterSwitchRequest;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.HeaderFilterMatchesCollection;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.HeaderFilterMatchesCollector;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.ISectionFormFragment;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.MarkersLabelAccumulator;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.MarkersSelectionLayerPainter;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.RedNatTableContentTooltip;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotEditorCommandsStack;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotEditorSources;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotSuiteEditorEvents;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.SelectionLayerAccessor;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.SuiteFileMarkersContainer;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.TableThemes;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.TableThemes.TableTheme;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.variables.VariablesMatchesCollection.VariableFilter;
import org.robotframework.red.nattable.AddingElementLabelAccumulator;
import org.robotframework.red.nattable.AssistanceLabelAccumulator;
import org.robotframework.red.nattable.NewElementsCreator;
import org.robotframework.red.nattable.RedNattableDataProvidersFactory;
import org.robotframework.red.nattable.RedNattableLayersFactory;
import org.robotframework.red.nattable.TableCellsStrings;
import org.robotframework.red.nattable.configs.AddingElementStyleConfiguration;
import org.robotframework.red.nattable.configs.AlternatingRowsStyleConfiguration;
import org.robotframework.red.nattable.configs.ColumnHeaderStyleConfiguration;
import org.robotframework.red.nattable.configs.GeneralTableStyleConfiguration;
import org.robotframework.red.nattable.configs.HeaderSortConfiguration;
import org.robotframework.red.nattable.configs.HoveredCellStyleConfiguration;
import org.robotframework.red.nattable.configs.RedTableEditConfiguration;
import org.robotframework.red.nattable.configs.RedTableResizableRowsBindingsConfiguration;
import org.robotframework.red.nattable.configs.RowHeaderStyleConfiguration;
import org.robotframework.red.nattable.configs.SelectionStyleConfiguration;
import org.robotframework.red.nattable.configs.TableMatchesSupplierRegistryConfiguration;
import org.robotframework.red.nattable.configs.TableStringsPositionsRegistryConfiguration;
import org.robotframework.red.nattable.edit.CellEditorCloser;
import org.robotframework.red.nattable.painter.RedNatGridLayerPainter;
import org.robotframework.red.nattable.painter.RedTableTextPainter;
import com.google.common.base.Function;
public class VariablesEditorFormFragment implements ISectionFormFragment {
@Inject
private IEventBroker eventBroker;
@Inject
private IEditorSite site;
@Inject
@Named(RobotEditorSources.SUITE_FILE_MODEL)
private RobotSuiteFile fileModel;
@Inject
private SuiteFileMarkersContainer markersContainer;
@Inject
private RobotEditorCommandsStack commandsStack;
@Inject
private IDirtyProviderService dirtyProviderService;
private HeaderFilterMatchesCollection matches;
private VariablesDataProvider dataProvider;
private NatTable table;
private RowSelectionProvider<Object> selectionProvider;
private SelectionLayerAccessor selectionLayerAccessor;
private ISortModel sortModel;
private TableHyperlinksSupport detector;
ISelectionProvider getSelectionProvider() {
return selectionProvider;
}
SelectionLayerAccessor getSelectionLayerAccessor() {
return selectionLayerAccessor;
}
@Override
public void initialize(final Composite parent) {
final TableTheme theme = TableThemes.getTheme(parent.getBackground().getRGB());
final ConfigRegistry configRegistry = new ConfigRegistry();
final RedNattableDataProvidersFactory dataProvidersFactory = new RedNattableDataProvidersFactory();
final RedNattableLayersFactory factory = new RedNattableLayersFactory();
// data providers
dataProvider = new VariablesDataProvider(commandsStack, getSection());
final IDataProvider columnHeaderDataProvider = dataProvidersFactory.createColumnHeaderDataProvider("Variable",
"Value", "Comment");
final IDataProvider rowHeaderDataProvider = dataProvidersFactory.createRowHeaderDataProvider(dataProvider);
// body layers
final DataLayer bodyDataLayer = factory.createDataLayer(dataProvider,
new AssistanceLabelAccumulator(dataProvider,
position -> position.getColumnPosition() == 1,
rowObject -> rowObject instanceof RobotElement),
new AlternatingRowConfigLabelAccumulator(),
new AddingElementLabelAccumulator(dataProvider, true),
new VariableTypesAndColumnsLabelAccumulator(dataProvider));
final GlazedListsEventLayer<RobotVariable> bodyEventLayer = factory.createGlazedListEventsLayer(bodyDataLayer,
dataProvider.getSortedList());
final HoverLayer bodyHoverLayer = factory.createHoverLayer(bodyEventLayer);
final SelectionLayer bodySelectionLayer = factory.createSelectionLayer(theme, bodyHoverLayer);
final ViewportLayer bodyViewportLayer = factory.createViewportLayer(bodySelectionLayer);
// column header layers
final DataLayer columnHeaderDataLayer = factory.createColumnHeaderDataLayer(columnHeaderDataProvider);
final ColumnHeaderLayer columnHeaderLayer = factory.createColumnHeaderLayer(columnHeaderDataLayer,
bodySelectionLayer, bodyViewportLayer);
final SortHeaderLayer<RobotVariable> columnHeaderSortingLayer = factory.createSortingColumnHeaderLayer(
columnHeaderDataLayer, columnHeaderLayer, dataProvider.getPropertyAccessor(), configRegistry,
dataProvider.getSortedList());
// row header layers
final RowHeaderLayer rowHeaderLayer = factory.createRowsHeaderLayer(bodySelectionLayer, bodyViewportLayer,
rowHeaderDataProvider, new MarkersSelectionLayerPainter(),
new MarkersLabelAccumulator(markersContainer, dataProvider));
// corner layer
final ILayer cornerLayer = factory.createCornerLayer(columnHeaderDataProvider, columnHeaderSortingLayer,
rowHeaderDataProvider, rowHeaderLayer);
// combined grid layer
final GridLayer gridLayer = factory.createGridLayer(bodyViewportLayer, columnHeaderSortingLayer, rowHeaderLayer,
cornerLayer);
final boolean wrapCellContent = hasWrappedCells();
if (wrapCellContent) {
gridLayer.addConfiguration(new RedTableResizableRowsBindingsConfiguration());
}
gridLayer.addConfiguration(new VariablesTableAdderStatesConfiguration(dataProvider));
gridLayer.addConfiguration(new RedTableEditConfiguration<>(fileModel, newElementsCreator(), wrapCellContent));
gridLayer.addConfiguration(
new VariableValuesEditConfiguration(theme, fileModel, dataProvider, commandsStack, wrapCellContent));
table = createTable(parent, theme, factory, gridLayer, bodyDataLayer, configRegistry);
bodyViewportLayer.registerCommandHandler(new MoveCellSelectionCommandHandler(bodySelectionLayer,
new EditTraversalStrategy(ITraversalStrategy.TABLE_CYCLE_TRAVERSAL_STRATEGY, table),
new EditTraversalStrategy(ITraversalStrategy.AXIS_CYCLE_TRAVERSAL_STRATEGY, table)));
sortModel = columnHeaderSortingLayer.getSortModel();
selectionProvider = new RowSelectionProvider<>(bodySelectionLayer, dataProvider, false);
selectionLayerAccessor = new SelectionLayerAccessor(dataProvider, bodySelectionLayer, selectionProvider);
// tooltips support
new RedNatTableContentTooltip(table, markersContainer, dataProvider);
}
private NatTable createTable(final Composite parent, final TableTheme theme, final RedNattableLayersFactory factory,
final GridLayer gridLayer, final DataLayer dataLayer, final ConfigRegistry configRegistry) {
final int style = SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL;
final NatTable table = new NatTable(parent, style, gridLayer, false);
table.setConfigRegistry(configRegistry);
table.setLayerPainter(
new RedNatGridLayerPainter(table, theme.getGridBorderColor(), theme.getHeadersBackground(),
theme.getHeadersUnderlineColor(), 2, RedNattableLayersFactory.ROW_HEIGHT));
table.setBackground(theme.getBodyBackgroundOddRowBackground());
table.setForeground(parent.getForeground());
// calculate columns width
table.addListener(SWT.Paint, factory.getColumnsWidthCalculatingPaintListener(table, dataProvider, dataLayer));
addCustomStyling(table, theme);
// matches support
table.addConfiguration(new TableMatchesSupplierRegistryConfiguration(() -> matches));
// hyperlinks support
final TableCellsStrings tableStrings = new TableCellsStrings();
table.addConfiguration(new TableStringsPositionsRegistryConfiguration(tableStrings));
detector = TableHyperlinksSupport.enableHyperlinksInTable(table, tableStrings);
detector.addDetectors(new TableHyperlinksToVariablesDetector(dataProvider));
// sorting
table.addConfiguration(new HeaderSortConfiguration());
table.addConfiguration(new VariablesTableSortingConfiguration());
// popup menus
table.addConfiguration(new VariablesTableMenuConfiguration(site, table, selectionProvider));
table.configure();
GridDataFactory.fillDefaults().grab(true, true).applyTo(table);
return table;
}
private void addCustomStyling(final NatTable table, final TableTheme theme) {
table.addConfiguration(new GeneralTableStyleConfiguration(theme, new RedTableTextPainter(hasWrappedCells())));
table.addConfiguration(new HoveredCellStyleConfiguration(theme));
table.addConfiguration(new ColumnHeaderStyleConfiguration(theme));
table.addConfiguration(new RowHeaderStyleConfiguration(theme));
table.addConfiguration(new AlternatingRowsStyleConfiguration(theme));
table.addConfiguration(new SelectionStyleConfiguration(theme, table.getFont()));
table.addConfiguration(new AddingElementStyleConfiguration(theme, fileModel.isEditable()));
}
private boolean hasWrappedCells() {
final RedPreferences preferences = RedPlugin.getDefault().getPreferences();
return preferences.getCellWrappingStrategy() == CellWrappingStrategy.WRAP;
}
private NewElementsCreator<RobotVariable> newElementsCreator() {
return addingTokenRowIndex -> {
final RobotVariablesSection section = dataProvider.getInput();
commandsStack.execute(
new CreateFreshVariableCommand(section, dataProvider.getAdderState().getVariableType()));
return section.getChildren().get(section.getChildren().size() - 1);
};
}
private RobotVariablesSection getSection() {
return fileModel.findSection(RobotVariablesSection.class).orElse(null);
}
void revealVariable(final RobotVariable robotVariable, final boolean focus) {
if (dataProvider.isFilterSet() && !dataProvider.isProvided(robotVariable)) {
final String topic = RobotSuiteEditorEvents.FORM_FILTER_SWITCH_REQUEST_TOPIC + "/"
+ RobotVariablesSection.SECTION_NAME;
eventBroker.send(topic, new FilterSwitchRequest(RobotVariablesSection.SECTION_NAME, ""));
}
CellEditorCloser.closeForcibly(table);
if (focus) {
table.setFocus();
}
selectionProvider.setSelection(new StructuredSelection(new Object[] { robotVariable }));
}
@Override
public void setFocus() {
table.setFocus();
}
public void aboutToChangeToOtherPage() {
CellEditorCloser.closeForcibly(table);
}
@Persist
public void onSave() {
CellEditorCloser.closeForcibly(table);
}
@SuppressWarnings("restriction")
private void setDirty() {
dirtyProviderService.setDirtyState(true);
}
@Override
public HeaderFilterMatchesCollection collectMatches(final String filter) {
final VariablesMatchesCollection variablesMatches = new VariablesMatchesCollection();
variablesMatches.collect(dataProvider.getInput(), filter);
return variablesMatches;
}
public List<ITableHyperlinksDetector> getDetectors() {
return detector.getDetectors();
}
@Inject
@Optional
private void whenUserRequestedFilteringEnabled(@UIEventTopic(RobotSuiteEditorEvents.SECTION_FILTERING_ENABLED_TOPIC
+ "/" + RobotVariablesSection.SECTION_NAME) final HeaderFilterMatchesCollection matches) {
if (matches.getCollectors().contains(this)) {
this.matches = matches;
dataProvider.setFilter(new VariableFilter(matches));
table.refresh();
}
}
@Inject
@Optional
private void whenUserRequestedFilteringDisabled(
@UIEventTopic(RobotSuiteEditorEvents.SECTION_FILTERING_DISABLED_TOPIC + "/"
+ RobotVariablesSection.SECTION_NAME) final Collection<HeaderFilterMatchesCollector> collectors) {
if (collectors.contains(this)) {
this.matches = null;
dataProvider.setFilter(null);
table.refresh();
}
}
@Inject
@Optional
private void whenSectionIsCreated(
@UIEventTopic(RobotModelEvents.ROBOT_SUITE_SECTION_ADDED) final RobotSuiteFile file) {
if (file == fileModel && dataProvider.getInput() == null) {
dataProvider.setInput(getSection());
table.refresh();
setDirty();
}
}
@Inject
@Optional
private void whenSectionIsRemoved(
@UIEventTopic(RobotModelEvents.ROBOT_SUITE_SECTION_REMOVED) final RobotSuiteFile file) {
if (file == fileModel && dataProvider.getInput() != null) {
final ICellEditor activeCellEditor = table.getActiveCellEditor();
if (activeCellEditor != null && !activeCellEditor.isClosed()) {
activeCellEditor.close();
}
dataProvider.setInput(getSection());
table.refresh();
selectionLayerAccessor.clear();
setDirty();
}
}
@Inject
@Optional
private void whenVariableDetailChanges(
@UIEventTopic(RobotModelEvents.ROBOT_VARIABLE_DETAIL_CHANGE_ALL) final RobotVariable variable) {
if (variable.getSuiteFile() == fileModel) {
table.refresh();
setDirty();
}
}
@Inject
@Optional
private void whenVariableIsAdded(
@UIEventTopic(RobotModelEvents.ROBOT_VARIABLE_ADDED) final RobotSuiteFileSection section) {
if (section.getSuiteFile() == fileModel) {
sortModel.clear();
selectionLayerAccessor.preserveSelectionWhen(tableInputIsReplaced());
}
}
@Inject
@Optional
private void whenVariableIsRemoved(
@UIEventTopic(RobotModelEvents.ROBOT_VARIABLE_REMOVED) final RobotSuiteFileSection section) {
if (section.getSuiteFile() == fileModel) {
selectionLayerAccessor.preserveSelectionWhen(tableInputIsReplaced(),
new Function<PositionCoordinate, PositionCoordinate>() {
@Override
public PositionCoordinate apply(final PositionCoordinate coordinate) {
if (section.getChildren().isEmpty()) {
return null;
} else if (dataProvider.getRowObject(coordinate.getRowPosition()) instanceof AddingToken) {
return new PositionCoordinate(coordinate.getLayer(), coordinate.getColumnPosition(),
coordinate.getRowPosition() - 1);
}
return coordinate;
}
});
}
}
@Inject
@Optional
private void whenVariableIsMoved(
@UIEventTopic(RobotModelEvents.ROBOT_VARIABLE_MOVED) final RobotSuiteFileSection section) {
if (section.getSuiteFile() == fileModel) {
sortModel.clear();
selectionLayerAccessor.preserveElementSelectionWhen(tableInputIsReplaced());
}
}
private Runnable tableInputIsReplaced() {
return new Runnable() {
@Override
public void run() {
dataProvider.setInput(getSection());
table.refresh();
setDirty();
}
};
}
@Inject
@Optional
private void whenFileChangedExternally(
@UIEventTopic(RobotModelEvents.EXTERNAL_MODEL_CHANGE) final RobotElementChange change) {
if (change.getKind() == Kind.CHANGED) {
final RobotSuiteFile suite = change.getElement() instanceof RobotSuiteFile
? (RobotSuiteFile) change.getElement() : null;
if (suite == fileModel) {
refreshEverything();
}
}
}
@Inject
@Optional
private void whenReconcilationWasDone(
@UIEventTopic(RobotModelEvents.REPARSING_DONE) final RobotSuiteFile fileModel) {
if (fileModel == this.fileModel) {
commandsStack.clear();
refreshEverything();
}
}
@Inject
@Optional
private void whenMarkersContainerWasReloaded(
@UIEventTopic(RobotModelEvents.MARKERS_CACHE_RELOADED) final RobotSuiteFile fileModel) {
if (fileModel == this.fileModel) {
refreshEverything();
}
}
private void refreshEverything() {
dataProvider.setInput(getSection());
table.refresh();
}
}