1 /*******************************************************************************
2 * Copyright (C) 2016 Thomas Wolf <thomas.wolf@paranor.ch>
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11 package org
.eclipse
.egit
.ui
.internal
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Arrays
;
15 import java
.util
.Collection
;
16 import java
.util
.function
.BooleanSupplier
;
18 import org
.eclipse
.jface
.action
.Action
;
19 import org
.eclipse
.jface
.action
.IAction
;
20 import org
.eclipse
.jface
.action
.MenuManager
;
21 import org
.eclipse
.jface
.action
.Separator
;
22 import org
.eclipse
.jface
.commands
.ActionHandler
;
23 import org
.eclipse
.jface
.text
.ITextOperationTarget
;
24 import org
.eclipse
.swt
.SWT
;
25 import org
.eclipse
.swt
.widgets
.Control
;
26 import org
.eclipse
.swt
.widgets
.Event
;
27 import org
.eclipse
.swt
.widgets
.Listener
;
28 import org
.eclipse
.ui
.ActiveShellExpression
;
29 import org
.eclipse
.ui
.PlatformUI
;
30 import org
.eclipse
.ui
.actions
.ActionFactory
;
31 import org
.eclipse
.ui
.actions
.ActionFactory
.IWorkbenchAction
;
32 import org
.eclipse
.ui
.handlers
.IHandlerActivation
;
33 import org
.eclipse
.ui
.handlers
.IHandlerService
;
34 import org
.eclipse
.ui
.texteditor
.IUpdate
;
37 * Action-related utilities.
39 public final class ActionUtils
{
41 private ActionUtils() {
42 // Utility class shall not be instantiated.
46 * Create an {@link IAction} taking the text, id, and action definition id
47 * from the given {@link ActionFactory}.
50 * from which the new {@link IAction} shall be derived
53 * @return the new {@link IAction}
55 public static IAction
createGlobalAction(ActionFactory factory
,
56 final Runnable action
) {
57 IWorkbenchAction template
= factory
58 .create(PlatformUI
.getWorkbench().getActiveWorkbenchWindow());
59 IAction result
= new Action(template
.getText()) {
66 result
.setActionDefinitionId(template
.getActionDefinitionId());
67 result
.setId(template
.getId());
68 result
.setImageDescriptor(template
.getImageDescriptor());
69 result
.setDisabledImageDescriptor(
70 template
.getDisabledImageDescriptor());
76 * Create an {@link UpdateableAction} taking the text, id, and action
77 * definition id from the given {@link ActionFactory}. The using code of
78 * such an action is responsible for calling {@link IUpdate#update()
79 * update()} on the action when its enablement should be updated.
82 * from which the new {@link IAction} shall be derived
86 * to obtain the action's enablement
87 * @return the new {@link UpdateableAction}
89 public static UpdateableAction
createGlobalAction(ActionFactory factory
,
90 final Runnable action
, final BooleanSupplier enabled
) {
91 IWorkbenchAction template
= factory
92 .create(PlatformUI
.getWorkbench().getActiveWorkbenchWindow());
93 UpdateableAction result
= new UpdateableAction(template
.getText()) {
101 public void update() {
102 setEnabled(enabled
.getAsBoolean());
105 result
.setActionDefinitionId(template
.getActionDefinitionId());
106 result
.setId(template
.getId());
107 result
.setImageDescriptor(template
.getImageDescriptor());
108 result
.setDisabledImageDescriptor(
109 template
.getDisabledImageDescriptor());
116 * Creates a new text action using a given {@link ActionFactory} to use as a
117 * template to set the label, image, and action definition id.
122 * to configure the action
123 * @param operationCode
125 * @return the configured {@link UpdateableAction}
127 public static UpdateableAction
createTextAction(
128 ITextOperationTarget target
, ActionFactory factory
,
130 if (operationCode
== ITextOperationTarget
.REDO
) {
131 // XXX: workaround for
132 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=206111
133 return createGlobalAction(factory
,
134 () -> target
.doOperation(operationCode
), () -> true);
136 return createGlobalAction(factory
,
137 () -> target
.doOperation(operationCode
),
138 () -> target
.canDoOperation(operationCode
));
141 private static UpdateableAction
[] createStandardTextActions(
142 ITextOperationTarget target
, boolean editable
) {
143 UpdateableAction
[] actions
= new UpdateableAction
[ITextOperationTarget
.SELECT_ALL
146 actions
[ITextOperationTarget
.UNDO
] = createTextAction(target
,
147 ActionFactory
.UNDO
, ITextOperationTarget
.UNDO
);
148 actions
[ITextOperationTarget
.REDO
] = createTextAction(target
,
149 ActionFactory
.REDO
, ITextOperationTarget
.REDO
);
150 actions
[ITextOperationTarget
.CUT
] = createTextAction(target
,
151 ActionFactory
.CUT
, ITextOperationTarget
.CUT
);
152 actions
[ITextOperationTarget
.PASTE
] = createTextAction(
153 target
, ActionFactory
.PASTE
, ITextOperationTarget
.PASTE
);
154 actions
[ITextOperationTarget
.DELETE
] = createTextAction(
155 target
, ActionFactory
.DELETE
, ITextOperationTarget
.DELETE
);
157 actions
[ITextOperationTarget
.COPY
] = createTextAction(target
,
158 ActionFactory
.COPY
, ITextOperationTarget
.COPY
);
159 actions
[ITextOperationTarget
.SELECT_ALL
] = createTextAction(
160 target
, ActionFactory
.SELECT_ALL
,
161 ITextOperationTarget
.SELECT_ALL
);
166 * Create the standard text actions, fill them into a {@MenuManager} and
167 * return them as an array indexed by the {@link ITextOperationTarget}
168 * operation codes. For an editable target, creates undo, redo | cut, copy,
169 * paste | delete, select all; otherwise just copy, select all.
172 * for the actions to operate on
174 * whether the target is editable
176 * to fill in; may be {@code null} if the actions shall not be
177 * added to a {@link MenuManager}
178 * @return the actions; may contain {@code null} values (index 0 will always
181 public static UpdateableAction
[] fillStandardTextActions(
182 ITextOperationTarget target
, boolean editable
,
183 MenuManager manager
) {
184 UpdateableAction
[] actions
= createStandardTextActions(target
,
186 if (manager
!= null) {
188 manager
.add(actions
[ITextOperationTarget
.UNDO
]);
189 manager
.add(actions
[ITextOperationTarget
.REDO
]);
190 manager
.add(new Separator());
191 manager
.add(actions
[ITextOperationTarget
.CUT
]);
193 manager
.add(actions
[ITextOperationTarget
.COPY
]);
195 manager
.add(actions
[ITextOperationTarget
.PASTE
]);
196 manager
.add(new Separator());
197 manager
.add(actions
[ITextOperationTarget
.DELETE
]);
199 manager
.add(actions
[ITextOperationTarget
.SELECT_ALL
]);
205 * Hooks up the {@link Control} such that the given {@link IAction}s are
206 * registered with the given {@link IHandlerService} while the control has
207 * the focus. Ensures that actions are properly de-registered when the
208 * control is disposed.
213 * to be registered while the control has the focus; {@code null}
216 * to register the actions with
218 public static void setGlobalActions(Control control
,
219 Collection
<?
extends IAction
> actions
, IHandlerService service
) {
220 ActiveShellExpression expression
= new ActiveShellExpression(
222 class ActivationListener
implements Listener
{
224 private Collection
<IHandlerActivation
> handlerActivations
= new ArrayList
<>();
227 public void handleEvent(Event event
) {
228 switch (event
.type
) {
232 if (!handlerActivations
.isEmpty()) {
233 service
.deactivateHandlers(handlerActivations
);
234 handlerActivations
.clear();
238 if (!handlerActivations
.isEmpty()) {
239 // Looks like sometimes we get two focusGained events.
242 for (IAction action
: actions
) {
243 if (action
!= null) {
244 handlerActivations
.add(service
.activateHandler(
245 action
.getActionDefinitionId(),
246 new ActionHandler(action
), expression
,
248 if (action
instanceof IUpdate
) {
249 ((IUpdate
) action
).update();
259 ActivationListener activationListener
= new ActivationListener();
260 control
.addListener(SWT
.Deactivate
, activationListener
);
261 control
.addListener(SWT
.FocusOut
, activationListener
);
262 control
.addListener(SWT
.FocusIn
, activationListener
);
263 control
.addListener(SWT
.Dispose
, activationListener
);
267 * Hooks up the {@link Control} such that the given {@link IAction}s are
268 * registered with the given {@link IHandlerService} while the control has
269 * the focus. Ensures that actions are properly de-registered when the
270 * control is disposed.
275 * to register the actions with
277 * to be registered while the control has the focus
279 public static void setGlobalActions(Control control
,
280 IHandlerService service
, IAction
... actions
) {
281 setGlobalActions(control
, Arrays
.asList(actions
), service
);
285 * Hooks up the {@link Control} such that the given {@link IAction}s are
286 * registered with the workbench-global {@link IHandlerService} while the
287 * control has the focus. Ensures that actions are properly de-registered
288 * when the control is disposed.
293 * to be registered while the control has the focus
295 public static void setGlobalActions(Control control
,
296 Collection
<?
extends IAction
> actions
) {
297 setGlobalActions(control
, actions
,
298 PlatformUI
.getWorkbench().getService(IHandlerService
.class));
302 * Hooks up the {@link Control} such that the given {@link IAction}s are
303 * registered with the workbench-global {@link IHandlerService} while the
304 * control has the focus. Ensures that actions are properly de-registered
305 * when the control is disposed.
310 * to be registered while the control has the focus
312 public static void setGlobalActions(Control control
, IAction
... actions
) {
313 setGlobalActions(control
, Arrays
.asList(actions
));
317 * An {@link Action} that is updateable via {@link IUpdate}.
319 public static abstract class UpdateableAction
extends Action
323 * Creates a new {@link UpdateableAction} with the given text.
327 public UpdateableAction(String text
) {