From f36ad7b5c7c2d7643b740433f66b3d9ad4989f5b Mon Sep 17 00:00:00 2001 From: Dirk Fauth Date: Mon, 19 May 2025 11:03:28 +0200 Subject: [PATCH] Impl #158 - [Export] Extend ExportCommand to configure success action Signed-off-by: Dirk Fauth --- .../widgets/nattable/export/NatExporter.java | 102 ++++++++++++++++-- .../export/command/ExportCommand.java | 74 ++++++++++++- .../export/command/ExportCommandHandler.java | 21 ++-- .../groupby/GroupByHeaderLayerTest.java | 7 ++ 4 files changed, 182 insertions(+), 22 deletions(-) diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/NatExporter.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/NatExporter.java index 7d2df152..388af2c4 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/NatExporter.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/NatExporter.java @@ -40,6 +40,7 @@ import org.eclipse.nebula.widgets.nattable.util.PlatformHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Shell; import org.slf4j.Logger; @@ -107,6 +108,16 @@ public class NatExporter { */ private boolean useProgressDialog = false; + /** + * The {@link Runnable} that should be executed after the export finished + * successfully. Useful in case {@link #openResult} is set to + * false so an alternative for reporting the export success can + * be configured. + * + * @since 2.6 + */ + private Runnable successRunnable; + /** * Create a new {@link NatExporter}. * @@ -160,13 +171,56 @@ public NatExporter(Shell shell, boolean executeSynchronously) { * is made based on whether a {@link Shell} is set or not. If a * {@link Shell} is set and this flag is set to true * the execution is performed synchronously. + * @param useProgressDialog + * Configure whether the progress should be reported via + * {@link ProgressMonitorDialog}. If set to false a + * custom shell with a {@link ProgressBar} will be shown if the + * shell parameter is not null. * * @since 2.3 */ public NatExporter(Shell shell, boolean executeSynchronously, boolean useProgressDialog) { + this(shell, executeSynchronously, useProgressDialog, true, null); + } + + /** + * Create a new {@link NatExporter}. + * + * @param shell + * The {@link Shell} that should be used to open sub-dialogs and + * perform export operations in a background thread. Can be + * null but could lead to + * {@link NullPointerException}s if {@link IExporter} are + * configured, that use a {@link FileOutputStreamProvider}. + * @param executeSynchronously + * Configure whether the export should be performed + * asynchronously or synchronously. By default the decision + * whether the execution should be performed synchronously or not + * is made based on whether a {@link Shell} is set or not. If a + * {@link Shell} is set and this flag is set to true + * the execution is performed synchronously. + * @param useProgressDialog + * Configure whether the progress should be reported via + * {@link ProgressMonitorDialog}. If set to false a + * custom shell with a {@link ProgressBar} will be shown if the + * shell parameter is not null. + * @param openResult + * Configure if the created export result should be opened after + * the export is finished. + * @param successRunnable + * The {@link Runnable} that should be executed after the export + * finished successfully. Useful in case {@link #openResult} is + * set to false so an alternative for reporting the + * export success can be configured. + * + * @since 2.6 + */ + public NatExporter(Shell shell, boolean executeSynchronously, boolean useProgressDialog, boolean openResult, Runnable successRunnable) { this.shell = shell; this.runAsynchronously = !executeSynchronously; this.useProgressDialog = useProgressDialog; + this.openResult = openResult; + this.successRunnable = successRunnable; } /** @@ -946,17 +1000,29 @@ protected void setClientAreaToMaximum(ILayer layer) { * @since 1.5 */ protected void openExport(IExporter exporter) { - if (this.exportSucceeded - && this.openResult - && exporter.getResult() != null - && exporter.getResult() instanceof File) { + if (this.exportSucceeded) { - try { - Class program = Class.forName("org.eclipse.swt.program.Program"); //$NON-NLS-1$ - Method launch = program.getMethod("launch", String.class); //$NON-NLS-1$ - launch.invoke(null, ((File) exporter.getResult()).getAbsolutePath()); - } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - LOG.info("Could not open the export because org.eclipse.swt.program.Program, you are probably running a RAP application."); //$NON-NLS-1$ + if (this.successRunnable != null) { + if (this.shell != null) { + this.shell.getDisplay().syncExec(() -> { + this.successRunnable.run(); + }); + } else { + this.successRunnable.run(); + } + } + + if (this.openResult + && exporter.getResult() != null + && exporter.getResult() instanceof File) { + + try { + Class program = Class.forName("org.eclipse.swt.program.Program"); //$NON-NLS-1$ + Method launch = program.getMethod("launch", String.class); //$NON-NLS-1$ + launch.invoke(null, ((File) exporter.getResult()).getAbsolutePath()); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + LOG.info("Could not open the export because org.eclipse.swt.program.Program, you are probably running a RAP application."); //$NON-NLS-1$ + } } } } @@ -1061,4 +1127,20 @@ public boolean isUseProgressDialog() { public void setUseProgressDialog(boolean useProgressDialog) { this.useProgressDialog = useProgressDialog; } + + /** + * Configure a {@link Runnable} that should be executed after a successful + * export operation. If a {@link #shell} is set, the {@link Runnable} is + * executed using {@link Display#syncExec(Runnable)}. + * + * @param successRunnable + * The {@link Runnable} that should be executed after the export + * finished successfully. Useful in case {@link #openResult} is + * set to false so an alternative for reporting the + * export success can be configured. + * @since 2.6 + */ + public void setSuccessRunnable(Runnable successRunnable) { + this.successRunnable = successRunnable; + } } diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommand.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommand.java index 6cebaf55..ef245ed9 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommand.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommand.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2023 Original authors and others. + * Copyright (c) 2012, 2025 Original authors and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -31,6 +31,8 @@ public class ExportCommand extends AbstractContextFreeCommand { private final boolean executeSynchronously; private final boolean useProgressDialog; private final ILayerExporter exporter; + private final boolean openResult; + private final Runnable successRunnable; /** * Creates a new {@link ExportCommand}. @@ -124,11 +126,59 @@ public ExportCommand(IConfigRegistry configRegistry, Shell shell, boolean execut * @since 2.3 */ public ExportCommand(IConfigRegistry configRegistry, Shell shell, boolean executeSynchronously, boolean useProgressDialog, ILayerExporter exporter) { + this(configRegistry, shell, executeSynchronously, useProgressDialog, exporter, true, null); + } + + /** + * Creates a new {@link ExportCommand}. + * + * @param configRegistry + * The {@link IConfigRegistry} that contains the necessary export + * configurations. + * @param shell + * The {@link Shell} that should be used to open sub-dialogs and + * perform export operations in a background thread. Can be + * null which definitely leads to synchronous + * execution but could cause errors in case sub-dialogs should be + * opened before exporting. + * @param executeSynchronously + * Configure if the export should be performed synchronously even + * if a {@link Shell} is set. + * @param useProgressDialog + * Configure whether the progress should be reported via + * {@link ProgressMonitorDialog}. If set to false a + * custom shell with a {@link ProgressBar} will be shown if the + * shell parameter is not null. + * @param exporter + * The {@link ILayerExporter} that should be used. Can be + * null, which causes the usage of the exporter + * registered in the {@link IConfigRegistry}. + * @param openResult + * Configure if the created export result should be opened after + * the export is finished. + * @param successRunnable + * The {@link Runnable} that should be executed after the export + * finished successfully. Useful in case {@link #openResult} is + * set to false so an alternative for reporting the + * export success can be configured. + * @since 2.6 + */ + public ExportCommand( + IConfigRegistry configRegistry, + Shell shell, + boolean executeSynchronously, + boolean useProgressDialog, + ILayerExporter exporter, + boolean openResult, + Runnable successRunnable) { + this.configRegistry = configRegistry; this.shell = shell; this.executeSynchronously = executeSynchronously; this.useProgressDialog = useProgressDialog; this.exporter = exporter; + this.openResult = openResult; + this.successRunnable = successRunnable; } /** @@ -185,4 +235,26 @@ public boolean isUseProgressDialog() { public ILayerExporter getExporter() { return this.exporter; } + + /** + * + * @return true if the created export file should be opened + * after the export finished successfully, false if not + * @since 2.6 + */ + public boolean isOpenResult() { + return this.openResult; + } + + /** + * + * @return The {@link Runnable} that should be executed after the export + * finished successfully. Useful in case {@link #openResult} is set + * to false so an alternative for reporting the export + * success can be configured. + * @since 2.6 + */ + public Runnable getSuccessRunnable() { + return this.successRunnable; + } } diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommandHandler.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommandHandler.java index c0508b87..cc0e2256 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/export/command/ExportCommandHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2023 Original authors and others. + * Copyright (c) 2012, 2025 Original authors and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -37,18 +37,17 @@ public ExportCommandHandler(ILayer layer) { @Override public boolean doCommand(final ExportCommand command) { + NatExporter natExporter = new NatExporter( + command.getShell(), + command.isExecuteSynchronously(), + command.isUseProgressDialog(), + command.isOpenResult(), + command.getSuccessRunnable()); + if (command.getExporter() == null) { - new NatExporter( - command.getShell(), - command.isExecuteSynchronously(), - command.isUseProgressDialog()) - .exportSingleLayer(this.layer, command.getConfigRegistry()); + natExporter.exportSingleLayer(this.layer, command.getConfigRegistry()); } else { - new NatExporter( - command.getShell(), - command.isExecuteSynchronously(), - command.isUseProgressDialog()) - .exportSingleLayer(command.getExporter(), this.layer, command.getConfigRegistry()); + natExporter.exportSingleLayer(command.getExporter(), this.layer, command.getConfigRegistry()); } return true; diff --git a/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupby/GroupByHeaderLayerTest.java b/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupby/GroupByHeaderLayerTest.java index 40d43611..748c0ba7 100644 --- a/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupby/GroupByHeaderLayerTest.java +++ b/org.eclipse.nebula.widgets.nattable.extension.glazedlists.test/src/org/eclipse/nebula/widgets/nattable/extension/glazedlists/groupby/GroupByHeaderLayerTest.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import org.eclipse.nebula.widgets.nattable.command.DisposeResourcesCommand; import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry; import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; @@ -60,6 +61,7 @@ import org.eclipse.nebula.widgets.nattable.sort.config.DefaultSortConfiguration; import org.eclipse.nebula.widgets.nattable.tree.TreeLayer; import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -176,6 +178,11 @@ public void setup() { this.natTable.configure(); } + @AfterEach + public void tearDown() { + this.natTable.doCommand(new DisposeResourcesCommand()); + } + @Test public void shouldUpdateGroupByModelViaGroupByCommand() { this.natTable.doCommand(new GroupByCommand(GroupByAction.ADD, 1));