diff --git a/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/IHandlerWithExpression.java b/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/IHandlerWithExpression.java new file mode 100644 index 00000000000..f0967376bdc --- /dev/null +++ b/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/IHandlerWithExpression.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform - initial API and implementation + ******************************************************************************/ + +package org.eclipse.e4.core.commands; + +import org.eclipse.e4.core.contexts.IEclipseContext; + +/** + * A marker interface for handlers that have enabledWhen expressions that need to be + * evaluated before @CanExecute. + * + * @since 1.1 + * @noimplement This interface is not intended to be implemented by clients outside of the + * workbench. + */ +public interface IHandlerWithExpression { + /** + * Evaluates the enabledWhen expression if present. + * + * @param context the eclipse context for evaluation + * @return true if no expression is defined or if the expression evaluates to true + */ + boolean evaluateEnabledWhen(IEclipseContext context); + + /** + * Returns the actual handler object that should be used for execution. + * + * @return the handler object + */ + Object getHandler(); +} diff --git a/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/internal/HandlerServiceHandler.java b/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/internal/HandlerServiceHandler.java index e248e3c9558..c653d34a0fa 100644 --- a/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/internal/HandlerServiceHandler.java +++ b/bundles/org.eclipse.e4.core.commands/src/org/eclipse/e4/core/commands/internal/HandlerServiceHandler.java @@ -25,6 +25,7 @@ import org.eclipse.core.commands.State; import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.e4.core.commands.ExpressionContext; +import org.eclipse.e4.core.commands.IHandlerWithExpression; import org.eclipse.e4.core.commands.internal.HandlerServiceImpl.ExecutionContexts; import org.eclipse.e4.core.contexts.ContextInjectionFactory; import org.eclipse.e4.core.contexts.EclipseContextFactory; @@ -64,8 +65,20 @@ public boolean isEnabled() { setBaseEnabled(false); return super.isEnabled(); } + + // Check for enabledWhen expression first (takes precedence over @CanExecute) + Object actualHandler = handler; + if (handler instanceof IHandlerWithExpression) { + IHandlerWithExpression handlerWithExpr = (IHandlerWithExpression) handler; + if (!handlerWithExpr.evaluateEnabledWhen(executionContext)) { + setBaseEnabled(false); + return super.isEnabled(); + } + actualHandler = handlerWithExpr.getHandler(); + } + IEclipseContext staticContext = contexts.staticContext; // getStaticContext(contexts); - Boolean result = (Boolean) ContextInjectionFactory.invoke(handler, CanExecute.class, + Boolean result = (Boolean) ContextInjectionFactory.invoke(actualHandler, CanExecute.class, executionContext, staticContext, Boolean.TRUE); setBaseEnabled(result.booleanValue()); return super.isEnabled(); @@ -83,12 +96,19 @@ public void setEnabled(Object evaluationContext) { if (handler == null) { return; } + + // Unwrap handler if needed + Object actualHandler = handler; + if (handler instanceof IHandlerWithExpression) { + actualHandler = ((IHandlerWithExpression) handler).getHandler(); + } + IEclipseContext staticContext = getStaticContext(executionContext); if (staticContext == null) { staticContext = EclipseContextFactory.create(); createContext = true; } - ContextInjectionFactory.invoke(handler, SetEnabled.class, executionContext, staticContext, + ContextInjectionFactory.invoke(actualHandler, SetEnabled.class, executionContext, staticContext, Boolean.TRUE); if (createContext) { staticContext.dispose(); @@ -132,10 +152,17 @@ public boolean isHandled() { if (contexts != null) { Object handler = HandlerServiceImpl.lookUpHandler(contexts.context, commandId); switchHandler(handler); - if (handler instanceof IHandler) { - return ((IHandler) handler).isHandled(); + + // Unwrap handler if needed + Object actualHandler = handler; + if (handler instanceof IHandlerWithExpression) { + actualHandler = ((IHandlerWithExpression) handler).getHandler(); } - return handler != null; + + if (actualHandler instanceof IHandler) { + return ((IHandler) actualHandler).isHandled(); + } + return actualHandler != null; } return false; @@ -154,6 +181,13 @@ public Object execute(ExecutionEvent event) throws ExecutionException { if (handler == null) { return null; } + + // Unwrap handler if needed + Object actualHandler = handler; + if (handler instanceof IHandlerWithExpression) { + actualHandler = ((IHandlerWithExpression) handler).getHandler(); + } + IEclipseContext staticContext = getStaticContext(executionContext); IEclipseContext localStaticContext = null; try { @@ -162,11 +196,11 @@ public Object execute(ExecutionEvent event) throws ExecutionException { .create(HandlerServiceImpl.TMP_STATIC_CONTEXT); staticContext.set(HandlerServiceImpl.PARM_MAP, event.getParameters()); } - Object result = ContextInjectionFactory.invoke(handler, Execute.class, + Object result = ContextInjectionFactory.invoke(actualHandler, Execute.class, executionContext, staticContext, missingExecute); if (result == missingExecute) { - throw new ExecutionException(handler.getClass().getName() + HANDLER_MISSING_EXECUTE_ANNOTATION, + throw new ExecutionException(actualHandler.getClass().getName() + HANDLER_MISSING_EXECUTE_ANNOTATION, new NotHandledException(getClass().getName())); } return result; diff --git a/bundles/org.eclipse.e4.ui.model.workbench/model/UIElements.ecore b/bundles/org.eclipse.e4.ui.model.workbench/model/UIElements.ecore index f6b0f277fcb..8947a3ea1de 100644 --- a/bundles/org.eclipse.e4.ui.model.workbench/model/UIElements.ecore +++ b/bundles/org.eclipse.e4.ui.model.workbench/model/UIElements.ecore @@ -260,6 +260,12 @@
+ + +
+ + diff --git a/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/MHandler.java b/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/MHandler.java index 432317858cc..eda58393f63 100644 --- a/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/MHandler.java +++ b/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/MHandler.java @@ -14,6 +14,7 @@ package org.eclipse.e4.ui.model.application.commands; import org.eclipse.e4.ui.model.application.MContribution; +import org.eclipse.e4.ui.model.application.ui.MExpression; /** * @@ -34,6 +35,7 @@ *

*
    *
  • {@link org.eclipse.e4.ui.model.application.commands.MHandler#getCommand Command}
  • + *
  • {@link org.eclipse.e4.ui.model.application.commands.MHandler#getEnabledWhen Enabled When}
  • *
* * @model @@ -66,4 +68,32 @@ public interface MHandler extends MContribution { */ void setCommand(MCommand value); + /** + * Returns the value of the 'Enabled When' containment reference. + * + * + * + *

+ * An optional core expression to control handler enablement. When specified, this takes precedence over the @CanExecute annotation. + *

+ * @since 1.4 + * + * @return the value of the 'Enabled When' containment reference. + * @see #setEnabledWhen(MExpression) + * @model containment="true" + * @generated + */ + MExpression getEnabledWhen(); + + /** + * Sets the value of the '{@link org.eclipse.e4.ui.model.application.commands.MHandler#getEnabledWhen Enabled When}' containment reference. + * + * + * @param value the new value of the 'Enabled When' containment reference. + * @see #getEnabledWhen() + * @since 1.4 + * @generated + */ + void setEnabledWhen(MExpression value); + } // MHandler diff --git a/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/CommandsPackageImpl.java b/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/CommandsPackageImpl.java index 96836f8c3f8..be565891bfa 100644 --- a/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/CommandsPackageImpl.java +++ b/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/CommandsPackageImpl.java @@ -767,6 +767,16 @@ public class CommandsPackageImpl extends EPackageImpl { */ public static final int HANDLER__COMMAND = ApplicationPackageImpl.CONTRIBUTION_FEATURE_COUNT + 0; + /** + * The feature id for the 'Enabled When' containment reference. + * + * + * @since 1.4 + * @generated + * @ordered + */ + public static final int HANDLER__ENABLED_WHEN = ApplicationPackageImpl.CONTRIBUTION_FEATURE_COUNT + 1; + /** * The number of structural features of the 'Handler' class. * @@ -775,7 +785,7 @@ public class CommandsPackageImpl extends EPackageImpl { * @generated * @ordered */ - public static final int HANDLER_FEATURE_COUNT = ApplicationPackageImpl.CONTRIBUTION_FEATURE_COUNT + 1; + public static final int HANDLER_FEATURE_COUNT = ApplicationPackageImpl.CONTRIBUTION_FEATURE_COUNT + 2; /** * The number of operations of the 'Handler' class. @@ -1801,6 +1811,20 @@ public EReference getHandler_Command() { return (EReference) handlerEClass.getEStructuralFeatures().get(0); } + /** + * Returns the meta object for the containment reference '{@link org.eclipse.e4.ui.model.application.commands.MHandler#getEnabledWhen Enabled When}'. + * + * + * @return the meta object for the containment reference 'Enabled When'. + * @see org.eclipse.e4.ui.model.application.commands.MHandler#getEnabledWhen() + * @see #getHandler() + * @since 1.4 + * @generated + */ + public EReference getHandler_EnabledWhen() { + return (EReference) handlerEClass.getEStructuralFeatures().get(1); + } + /** * Returns the meta object for class '{@link org.eclipse.e4.ui.model.application.commands.MHandlerContainer Handler Container}'. * @@ -2097,6 +2121,7 @@ public void createPackageContents() { handlerEClass = createEClass(HANDLER); createEReference(handlerEClass, HANDLER__COMMAND); + createEReference(handlerEClass, HANDLER__ENABLED_WHEN); handlerContainerEClass = createEClass(HANDLER_CONTAINER); createEReference(handlerContainerEClass, HANDLER_CONTAINER__HANDLERS); @@ -2244,6 +2269,9 @@ public void initializePackageContents() { initEReference(getHandler_Command(), this.getCommand(), null, "command", null, 1, 1, MHandler.class, //$NON-NLS-1$ !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_COMPOSITE, IS_RESOLVE_PROXIES, !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); + initEReference(getHandler_EnabledWhen(), theUiPackageImpl.getExpression(), null, "enabledWhen", null, 0, 1, //$NON-NLS-1$ + MHandler.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, IS_COMPOSITE, !IS_RESOLVE_PROXIES, + !IS_UNSETTABLE, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); initEClass(handlerContainerEClass, MHandlerContainer.class, "HandlerContainer", IS_ABSTRACT, IS_INTERFACE, //$NON-NLS-1$ IS_GENERATED_INSTANCE_CLASS); @@ -2566,6 +2594,15 @@ public interface Literals { */ public static final EReference HANDLER__COMMAND = eINSTANCE.getHandler_Command(); + /** + * The meta object literal for the 'Enabled When' containment reference feature. + * + * + * @since 1.4 + * @generated + */ + public static final EReference HANDLER__ENABLED_WHEN = eINSTANCE.getHandler_EnabledWhen(); + /** * The meta object literal for the '{@link org.eclipse.e4.ui.model.application.commands.MHandlerContainer Handler Container}' class. * diff --git a/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/HandlerImpl.java b/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/HandlerImpl.java index 8a1f8303f8b..595207b8a83 100644 --- a/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/HandlerImpl.java +++ b/bundles/org.eclipse.e4.ui.model.workbench/src/org/eclipse/e4/ui/model/application/commands/impl/HandlerImpl.java @@ -16,7 +16,9 @@ import org.eclipse.e4.ui.model.application.commands.MCommand; import org.eclipse.e4.ui.model.application.commands.MHandler; import org.eclipse.e4.ui.model.application.impl.ContributionImpl; +import org.eclipse.e4.ui.model.application.ui.MExpression; import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.NotificationChain; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.InternalEObject; @@ -31,6 +33,7 @@ *

*
    *
  • {@link org.eclipse.e4.ui.model.application.commands.impl.HandlerImpl#getCommand Command}
  • + *
  • {@link org.eclipse.e4.ui.model.application.commands.impl.HandlerImpl#getEnabledWhen Enabled When}
  • *
* * @since 1.0 @@ -47,6 +50,16 @@ public class HandlerImpl extends ContributionImpl implements MHandler { */ protected MCommand command; + /** + * The cached value of the '{@link #getEnabledWhen() Enabled When}' containment reference. + * + * + * @see #getEnabledWhen() + * @generated + * @ordered + */ + protected MExpression enabledWhen; + /** * * @@ -110,6 +123,78 @@ public void setCommand(MCommand newCommand) { } } + /** + * + * + * @generated + */ + @Override + public MExpression getEnabledWhen() { + return enabledWhen; + } + + /** + * + * + * @generated + */ + public NotificationChain basicSetEnabledWhen(MExpression newEnabledWhen, NotificationChain msgs) { + MExpression oldEnabledWhen = enabledWhen; + enabledWhen = newEnabledWhen; + if (eNotificationRequired()) { + ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, + CommandsPackageImpl.HANDLER__ENABLED_WHEN, oldEnabledWhen, newEnabledWhen); + if (msgs == null) { + msgs = notification; + } else { + msgs.add(notification); + } + } + return msgs; + } + + /** + * + * + * @generated + */ + @Override + public void setEnabledWhen(MExpression newEnabledWhen) { + if (newEnabledWhen != enabledWhen) { + NotificationChain msgs = null; + if (enabledWhen != null) { + msgs = ((InternalEObject) enabledWhen).eInverseRemove(this, + EOPPOSITE_FEATURE_BASE - CommandsPackageImpl.HANDLER__ENABLED_WHEN, null, msgs); + } + if (newEnabledWhen != null) { + msgs = ((InternalEObject) newEnabledWhen).eInverseAdd(this, + EOPPOSITE_FEATURE_BASE - CommandsPackageImpl.HANDLER__ENABLED_WHEN, null, msgs); + } + msgs = basicSetEnabledWhen(newEnabledWhen, msgs); + if (msgs != null) { + msgs.dispatch(); + } + } else if (eNotificationRequired()) { + eNotify(new ENotificationImpl(this, Notification.SET, CommandsPackageImpl.HANDLER__ENABLED_WHEN, + newEnabledWhen, newEnabledWhen)); + } + } + + /** + * + * + * @generated + */ + @Override + public NotificationChain eInverseRemove(InternalEObject otherEnd, int featureID, NotificationChain msgs) { + switch (featureID) { + case CommandsPackageImpl.HANDLER__ENABLED_WHEN: + return basicSetEnabledWhen(null, msgs); + default: + return super.eInverseRemove(otherEnd, featureID, msgs); + } + } + /** * * @@ -123,6 +208,8 @@ public Object eGet(int featureID, boolean resolve, boolean coreType) { return getCommand(); } return basicGetCommand(); + case CommandsPackageImpl.HANDLER__ENABLED_WHEN: + return getEnabledWhen(); default: return super.eGet(featureID, resolve, coreType); } @@ -139,6 +226,9 @@ public void eSet(int featureID, Object newValue) { case CommandsPackageImpl.HANDLER__COMMAND: setCommand((MCommand) newValue); return; + case CommandsPackageImpl.HANDLER__ENABLED_WHEN: + setEnabledWhen((MExpression) newValue); + return; default: super.eSet(featureID, newValue); return; @@ -156,6 +246,9 @@ public void eUnset(int featureID) { case CommandsPackageImpl.HANDLER__COMMAND: setCommand((MCommand) null); return; + case CommandsPackageImpl.HANDLER__ENABLED_WHEN: + setEnabledWhen((MExpression) null); + return; default: super.eUnset(featureID); return; @@ -172,6 +265,8 @@ public boolean eIsSet(int featureID) { switch (featureID) { case CommandsPackageImpl.HANDLER__COMMAND: return command != null; + case CommandsPackageImpl.HANDLER__ENABLED_WHEN: + return enabledWhen != null; default: return super.eIsSet(featureID); } diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerEnabledWhenWrapper.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerEnabledWhenWrapper.java new file mode 100644 index 00000000000..3e46e0e8c63 --- /dev/null +++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerEnabledWhenWrapper.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform - initial API and implementation + ******************************************************************************/ + +package org.eclipse.e4.ui.internal.workbench.addons; + +import org.eclipse.core.expressions.EvaluationResult; +import org.eclipse.core.expressions.Expression; +import org.eclipse.core.expressions.ReferenceExpression; +import org.eclipse.e4.core.commands.ExpressionContext; +import org.eclipse.e4.core.commands.IHandlerWithExpression; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.ui.model.application.commands.MHandler; +import org.eclipse.e4.ui.model.application.ui.MCoreExpression; +import org.eclipse.e4.ui.model.application.ui.MExpression; + +/** + * A wrapper that associates a handler with its enabledWhen expression from the model. + * This allows expression-based enablement to be checked in addition to @CanExecute. + * + * @since 1.4 + */ +public class HandlerEnabledWhenWrapper implements IHandlerWithExpression { + private final Object handler; + private final MHandler handlerModel; + + public HandlerEnabledWhenWrapper(Object handler, MHandler handlerModel) { + this.handler = handler; + this.handlerModel = handlerModel; + } + + @Override + public Object getHandler() { + return handler; + } + + public MHandler getHandlerModel() { + return handlerModel; + } + + @Override + public boolean evaluateEnabledWhen(IEclipseContext context) { + MExpression enabledWhen = handlerModel.getEnabledWhen(); + if (enabledWhen == null) { + return true; + } + + if (enabledWhen instanceof MCoreExpression coreExpression) { + return evaluateCoreExpression(coreExpression, context); + } + + // For other expression types, default to enabled + return true; + } + + private boolean evaluateCoreExpression(MCoreExpression coreExpression, IEclipseContext context) { + Expression expr = null; + if (coreExpression.getCoreExpression() instanceof Expression) { + expr = (Expression) coreExpression.getCoreExpression(); + } else if (coreExpression.getCoreExpressionId() != null && !coreExpression.getCoreExpressionId().isEmpty()) { + expr = new ReferenceExpression(coreExpression.getCoreExpressionId()); + coreExpression.setCoreExpression(expr); + } + + if (expr == null) { + return true; + } + + try { + ExpressionContext exprContext = new ExpressionContext(context); + EvaluationResult result = expr.evaluate(exprContext); + return result != EvaluationResult.FALSE; + } catch (Exception e) { + // Log error and default to disabled + return false; + } + } +} diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerProcessingAddon.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerProcessingAddon.java index bf0b880b35f..82d9536d0bb 100644 --- a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerProcessingAddon.java +++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/addons/HandlerProcessingAddon.java @@ -131,7 +131,14 @@ private void processActiveHandler(MHandler handler, IEclipseContext context) { handler.setObject(contributionFactory.create(handler.getContributionURI(), context)); } EHandlerService handlerService = context.get(EHandlerService.class); - handlerService.activateHandler(commandId, handler.getObject()); + + // Wrap handler with enabledWhen support if expression is defined + Object handlerObj = handler.getObject(); + if (handler.getEnabledWhen() != null) { + handlerObj = new HandlerEnabledWhenWrapper(handlerObj, handler); + } + + handlerService.activateHandler(commandId, handlerObj); } } \ No newline at end of file diff --git a/docs/Eclipse4_Migration.md b/docs/Eclipse4_Migration.md index 1b879c2c307..66c9f9205b1 100644 --- a/docs/Eclipse4_Migration.md +++ b/docs/Eclipse4_Migration.md @@ -268,13 +268,38 @@ public class MyHandler { ``` 4. **Convert enablement logic**: - - Replace `enabledWhen` expressions with `@CanExecute` method: - ```java - @CanExecute - public boolean canExecute(@Named(IServiceConstants.ACTIVE_SELECTION) ISelection selection) { - return selection != null && !selection.isEmpty(); - } + + **Option A: Use Core Expressions (Recommended for complex conditions)** + + If your E3 handler had an `` expression, you can migrate it to a core expression in the E4 model: + + - Define a core expression in plugin.xml (see [Command_Core_Expressions.md](Command_Core_Expressions.md#definitions)): + ```xml + + + + + + + ``` + + - In the E4 Model Editor, select your handler and add an "Enabled When" expression: + - In the handler details, click "Add" in the "Enabled When" section + - Select "Core Expression" + - Set Core Expression Id: `com.example.handler.enabled` + + When a core expression is defined, it takes precedence over `@CanExecute` annotations. + + **Option B: Use @CanExecute annotation (Recommended for simple conditions)** + + For simple enablement conditions, use `@CanExecute` method in your handler class: + ```java + @CanExecute + public boolean canExecute(@Named(IServiceConstants.ACTIVE_SELECTION) ISelection selection) { + return selection != null && !selection.isEmpty(); + } + ``` 5. **Handle active context** (replacement for `activeWhen`): - In E4, handlers are scoped to their containing model element @@ -290,6 +315,17 @@ public class MyHandler { jakarta.annotation;version="1.1.0" ``` +### Handler Enablement Precedence + +When both an `enabledWhen` core expression and a `@CanExecute` annotation are present: +1. The `enabledWhen` expression is evaluated first +2. If the expression returns false, the handler is disabled (regardless of `@CanExecute`) +3. If the expression returns true (or is not defined), the `@CanExecute` method is called + +This allows you to use core expressions for complex, declarative conditions while still having programmatic control through `@CanExecute` when needed. + +For more information on core expression syntax, see [Command_Core_Expressions.md](Command_Core_Expressions.md). + ### Common Injection Patterns for Handlers **Important**: Always inject dependencies in the `@Execute` and `@CanExecute` methods rather than using field injection. This ensures you always receive the most recent values from the context, which is critical for handlers that may be executed multiple times with different contexts. diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/HandlerEnabledWhenTest.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/HandlerEnabledWhenTest.java new file mode 100644 index 00000000000..bdea1eeac09 --- /dev/null +++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/HandlerEnabledWhenTest.java @@ -0,0 +1,227 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse Platform and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eclipse Platform - initial API and implementation + ******************************************************************************/ + +package org.eclipse.e4.ui.tests.workbench; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.eclipse.core.commands.Category; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ParameterizedCommand; +import org.eclipse.core.expressions.EvaluationResult; +import org.eclipse.core.expressions.Expression; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.e4.core.commands.CommandServiceAddon; +import org.eclipse.e4.core.commands.ECommandService; +import org.eclipse.e4.core.commands.EHandlerService; +import org.eclipse.e4.core.contexts.ContextInjectionFactory; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.internal.workbench.swt.E4Application; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.commands.MCommand; +import org.eclipse.e4.ui.model.application.commands.MHandler; +import org.eclipse.e4.ui.model.application.commands.impl.CommandsFactoryImpl; +import org.eclipse.e4.ui.model.application.ui.MCoreExpression; +import org.eclipse.e4.ui.model.application.ui.impl.UiFactoryImpl; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for handler enabledWhen core expression support. + * + * @since 1.4 + */ +public class HandlerEnabledWhenTest { + private static final String TEST_COMMAND_ID = "test.command.enabledwhen"; + private IEclipseContext appContext; + + public static class TestHandler { + boolean ran = false; + boolean canRun = true; + + @CanExecute + public boolean canExecute() { + return canRun; + } + + @Execute + public Object execute() { + ran = true; + return "executed"; + } + } + + /** + * A simple expression that always returns true. + */ + public static class TrueExpression extends Expression { + @Override + public EvaluationResult evaluate(IEvaluationContext context) { + return EvaluationResult.TRUE; + } + } + + /** + * A simple expression that always returns false. + */ + public static class FalseExpression extends Expression { + @Override + public EvaluationResult evaluate(IEvaluationContext context) { + return EvaluationResult.FALSE; + } + } + + @Before + public void setUp() throws Exception { + appContext = E4Application.createDefaultContext(); + ContextInjectionFactory.make(CommandServiceAddon.class, appContext); + defineCommand(appContext); + } + + @After + public void tearDown() throws Exception { + appContext.dispose(); + } + + /** + * Tests that when no enabledWhen expression is set, the handler's @CanExecute + * is used as normal. + */ + @Test + public void testNoEnabledWhen() throws Exception { + ParameterizedCommand cmd = getCommand(appContext, TEST_COMMAND_ID); + EHandlerService service = appContext.get(EHandlerService.class); + TestHandler handler = new TestHandler(); + service.activateHandler(TEST_COMMAND_ID, handler); + + // Should be enabled based on @CanExecute + assertTrue(service.canExecute(cmd)); + assertEquals("executed", service.executeHandler(cmd)); + assertTrue(handler.ran); + + // Now disable via @CanExecute + handler.canRun = false; + assertFalse(service.canExecute(cmd)); + } + + /** + * Tests that when an enabledWhen expression is set to true, and @CanExecute + * returns true, the handler is enabled. + */ + @Test + public void testEnabledWhenTrue() throws Exception { + ParameterizedCommand cmd = getCommand(appContext, TEST_COMMAND_ID); + EHandlerService service = appContext.get(EHandlerService.class); + TestHandler handler = new TestHandler(); + + // Create handler model with enabledWhen expression + MHandler handlerModel = createHandlerWithExpression(handler, new TrueExpression()); + + // Use the wrapper created by HandlerProcessingAddon simulation + // The wrapper implements IHandlerWithExpression interface + org.eclipse.e4.ui.internal.workbench.addons.HandlerEnabledWhenWrapper wrapper = + new org.eclipse.e4.ui.internal.workbench.addons.HandlerEnabledWhenWrapper(handler, handlerModel); + service.activateHandler(TEST_COMMAND_ID, wrapper); + + // Expression is true, @CanExecute is true => enabled + assertTrue(service.canExecute(cmd)); + assertEquals("executed", service.executeHandler(cmd)); + assertTrue(handler.ran); + } + + /** + * Tests that when an enabledWhen expression is set to false, the handler is + * disabled regardless of @CanExecute. + */ + @Test + public void testEnabledWhenFalse() throws Exception { + ParameterizedCommand cmd = getCommand(appContext, TEST_COMMAND_ID); + EHandlerService service = appContext.get(EHandlerService.class); + TestHandler handler = new TestHandler(); + + // Create handler model with enabledWhen expression that returns false + MHandler handlerModel = createHandlerWithExpression(handler, new FalseExpression()); + + // Use the wrapper + org.eclipse.e4.ui.internal.workbench.addons.HandlerEnabledWhenWrapper wrapper = + new org.eclipse.e4.ui.internal.workbench.addons.HandlerEnabledWhenWrapper(handler, handlerModel); + service.activateHandler(TEST_COMMAND_ID, wrapper); + + // Expression is false => disabled (even though @CanExecute would return true) + assertFalse(service.canExecute(cmd)); + + // Handler should not execute when disabled + assertNull(service.executeHandler(cmd)); + assertFalse(handler.ran); + } + + /** + * Tests that enabledWhen expression takes precedence over @CanExecute. + */ + @Test + public void testEnabledWhenPrecedence() throws Exception { + ParameterizedCommand cmd = getCommand(appContext, TEST_COMMAND_ID); + EHandlerService service = appContext.get(EHandlerService.class); + TestHandler handler = new TestHandler(); + handler.canRun = false; // @CanExecute would return false + + // Create handler model with enabledWhen expression that returns true + MHandler handlerModel = createHandlerWithExpression(handler, new TrueExpression()); + + // Use the wrapper + org.eclipse.e4.ui.internal.workbench.addons.HandlerEnabledWhenWrapper wrapper = + new org.eclipse.e4.ui.internal.workbench.addons.HandlerEnabledWhenWrapper(handler, handlerModel); + service.activateHandler(TEST_COMMAND_ID, wrapper); + + // Expression is true, but @CanExecute is false + // The @CanExecute should still be consulted after expression passes + assertFalse(service.canExecute(cmd)); + + // Now enable via @CanExecute + handler.canRun = true; + assertTrue(service.canExecute(cmd)); + } + + private ParameterizedCommand getCommand(IEclipseContext context, String commandId) { + ECommandService cs = context.get(ECommandService.class); + Command cmd = cs.getCommand(commandId); + return new ParameterizedCommand(cmd, null); + } + + private void defineCommand(IEclipseContext context) { + ECommandService cmdService = context.get(ECommandService.class); + Category category = cmdService.defineCategory("test.category", "Test Category", null); + cmdService.defineCommand(TEST_COMMAND_ID, "Test Command", null, category, null); + } + + private MHandler createHandlerWithExpression(Object handler, Expression expression) { + MHandler handlerModel = CommandsFactoryImpl.eINSTANCE.createHandler(); + MCommand command = CommandsFactoryImpl.eINSTANCE.createCommand(); + command.setElementId(TEST_COMMAND_ID); + handlerModel.setCommand(command); + handlerModel.setObject(handler); + + MCoreExpression coreExpression = UiFactoryImpl.eINSTANCE.createCoreExpression(); + coreExpression.setCoreExpression(expression); + handlerModel.setEnabledWhen(coreExpression); + + return handlerModel; + } +}