1 /*******************************************************************************
2  * Copyright (c) 2000, 2015 IBM Corporation and others.
3  *
4  * 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/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jface.action;
15 
16 import org.eclipse.jface.resource.ImageDescriptor;
17 import org.eclipse.swt.events.HelpListener;
18 import org.eclipse.swt.widgets.Control;
19 import org.eclipse.swt.widgets.Event;
20 import org.eclipse.swt.widgets.Menu;
21 
22 /**
23  * The standard abstract implementation of an action.
24  * <p>
25  * Subclasses must implement the <code>IAction.run</code> method to carry out
26  * the action's semantics.
27  * </p>
28  */
29 public abstract class Action extends AbstractAction {
30 
31 	private static final IMenuCreator VAL_DROP_DOWN_MENU = new IMenuCreator() {
32 		@Override
33 		public void dispose() {
34 			// do nothing
35 		}
36 
37 		@Override
38 		public Menu getMenu(Control parent) {
39 			// do nothing
40 			return null;
41 		}
42 
43 		@Override
44 		public Menu getMenu(Menu parent) {
45 			// do nothing
46 			return null;
47 		}
48 	};
49 
50 	/*
51 	 * The list of default values the action can have. These values will
52 	 * determine the style of the action.
53 	 */
54 	private static final String VAL_PUSH_BTN = "PUSH_BTN"; //$NON-NLS-1$
55 
56 	private static final Integer VAL_RADIO_BTN_OFF = Integer.valueOf(0);
57 
58 	private static final Integer VAL_RADIO_BTN_ON = Integer.valueOf(1);
59 
60 	private static final Boolean VAL_TOGGLE_BTN_OFF = Boolean.FALSE;
61 
62 	private static final Boolean VAL_TOGGLE_BTN_ON = Boolean.TRUE;
63 
64 	/**
65 	 * Converts an accelerator key code to a string representation.
66 	 *
67 	 * @param keyCode
68 	 *            the key code to be translated
69 	 * @return a string representation of the key code
70 	 */
convertAccelerator(int keyCode)71 	public static String convertAccelerator(int keyCode) {
72 		return LegacyActionTools.convertAccelerator(keyCode);
73 	}
74 
75 	/**
76 	 * Parses the given accelerator text, and converts it to an accelerator key
77 	 * code.
78 	 *
79 	 * @param acceleratorText
80 	 *            the accelerator text
81 	 * @return the SWT key code, or 0 if there is no accelerator
82 	 */
convertAccelerator(String acceleratorText)83 	public static int convertAccelerator(String acceleratorText) {
84 		return LegacyActionTools.convertAccelerator(acceleratorText);
85 	}
86 
87 	/**
88 	 * Maps a standard keyboard key name to an SWT key code. Key names are converted
89 	 * to upper case before comparison. If the key name is a single letter, for
90 	 * example "S", its character code is returned.
91 	 * <p>
92 	 * The following key names are known (case is ignored):
93 	 * </p>
94 	 * <ul>
95 	 * <li><code>"BACKSPACE"</code></li>
96 	 * <li><code>"TAB"</code></li>
97 	 * <li><code>"RETURN"</code></li>
98 	 * <li><code>"ENTER"</code></li>
99 	 * <li><code>"ESC"</code></li>
100 	 * <li><code>"ESCAPE"</code></li>
101 	 * <li><code>"DELETE"</code></li>
102 	 * <li><code>"SPACE"</code></li>
103 	 * <li><code>"ARROW_UP"</code>, <code>"ARROW_DOWN"</code>,
104 	 * <code>"ARROW_LEFT"</code>, and <code>"ARROW_RIGHT"</code></li>
105 	 * <li><code>"PAGE_UP"</code> and <code>"PAGE_DOWN"</code></li>
106 	 * <li><code>"HOME"</code></li>
107 	 * <li><code>"END"</code></li>
108 	 * <li><code>"INSERT"</code></li>
109 	 * <li><code>"F1"</code>, <code>"F2"</code> through <code>"F12"</code></li>
110 	 * </ul>
111 	 *
112 	 * @param token the key name
113 	 * @return the SWT key code, <code>-1</code> if no match was found
114 	 * @see org.eclipse.swt.SWT
115 	 */
findKeyCode(String token)116 	public static int findKeyCode(String token) {
117 		return LegacyActionTools.findKeyCode(token);
118 	}
119 
120 	/**
121 	 * Maps an SWT key code to a standard keyboard key name. The key code is
122 	 * stripped of modifiers (SWT.CTRL, SWT.ALT, SWT.SHIFT, and SWT.COMMAND). If
123 	 * the key code is not an SWT code (for example if it a key code for the key
124 	 * 'S'), a string containing a character representation of the key code is
125 	 * returned.
126 	 *
127 	 * @param keyCode
128 	 *            the key code to be translated
129 	 * @return the string representation of the key code
130 	 * @see org.eclipse.swt.SWT
131 	 * @since 2.0
132 	 */
findKeyString(int keyCode)133 	public static String findKeyString(int keyCode) {
134 		return LegacyActionTools.findKeyString(keyCode);
135 	}
136 
137 	/**
138 	 * Maps standard keyboard modifier key names to the corresponding SWT
139 	 * modifier bit. The following modifier key names are recognized (case is
140 	 * ignored): <code>"CTRL"</code>, <code>"SHIFT"</code>,
141 	 * <code>"ALT"</code>, and <code>"COMMAND"</code>. The given modifier
142 	 * key name is converted to upper case before comparison.
143 	 *
144 	 * @param token
145 	 *            the modifier key name
146 	 * @return the SWT modifier bit, or <code>0</code> if no match was found
147 	 * @see org.eclipse.swt.SWT
148 	 */
findModifier(String token)149 	public static int findModifier(String token) {
150 		return LegacyActionTools.findModifier(token);
151 	}
152 
153 	/**
154 	 * Returns a string representation of an SWT modifier bit (SWT.CTRL,
155 	 * SWT.ALT, SWT.SHIFT, and SWT.COMMAND). Returns <code>null</code> if the
156 	 * key code is not an SWT modifier bit.
157 	 *
158 	 * @param keyCode
159 	 *            the SWT modifier bit to be translated
160 	 * @return the string representation of the SWT modifier bit, or
161 	 *         <code>null</code> if the key code was not an SWT modifier bit
162 	 * @see org.eclipse.swt.SWT
163 	 * @since 2.0
164 	 */
findModifierString(int keyCode)165 	public static String findModifierString(int keyCode) {
166 		return LegacyActionTools.findModifierString(keyCode);
167 	}
168 
169 	/**
170 	 * Convenience method for removing any optional accelerator text from the
171 	 * given string. The accelerator text appears at the end of the text, and is
172 	 * separated from the main part by the last tab character <code>'\t'</code>
173 	 * (or the last <code>'@'</code> if there is no tab).
174 	 *
175 	 * @param text
176 	 *            the text
177 	 * @return the text sans accelerator
178 	 */
removeAcceleratorText(String text)179 	public static String removeAcceleratorText(String text) {
180 		return LegacyActionTools.removeAcceleratorText(text);
181 	}
182 
183 	/**
184 	 * Convenience method for removing any mnemonics from the given string. For
185 	 * example, <code>removeMnemonics("&amp;Open")</code> will return
186 	 * <code>"Open"</code>.
187 	 *
188 	 * @param text the text
189 	 * @return the text sans mnemonics
190 	 *
191 	 * @since 3.0
192 	 */
removeMnemonics(String text)193 	public static String removeMnemonics(String text) {
194 		return LegacyActionTools.removeMnemonics(text);
195 	}
196 
197 	/**
198 	 * This action's accelerator; <code>0</code> means none.
199 	 */
200 	private int accelerator = 0;
201 
202 	/**
203 	 * This action's action definition id, or <code>null</code> if none.
204 	 */
205 	private String actionDefinitionId;
206 
207 	/**
208 	 * This action's description, or <code>null</code> if none.
209 	 */
210 	private String description;
211 
212 	/**
213 	 * This action's disabled image, or <code>null</code> if none.
214 	 */
215 	private ImageDescriptor disabledImage;
216 
217 	/**
218 	 * Indicates this action is enabled.
219 	 */
220 	private boolean enabled = true;
221 
222 	/**
223 	 * An action's help listener, or <code>null</code> if none.
224 	 */
225 	private HelpListener helpListener;
226 
227 	/**
228 	 * This action's hover image, or <code>null</code> if none.
229 	 */
230 	private ImageDescriptor hoverImage;
231 
232 	/**
233 	 * This action's id, or <code>null</code> if none.
234 	 */
235 	private String id;
236 
237 	/**
238 	 * This action's image, or <code>null</code> if none.
239 	 */
240 	private ImageDescriptor image;
241 
242 	/**
243 	 * This action's text, or <code>null</code> if none.
244 	 */
245 	private String text;
246 
247 	/**
248 	 * This action's tool tip text, or <code>null</code> if none.
249 	 */
250 	private String toolTipText;
251 
252 	/**
253 	 * Holds the action's menu creator (an IMenuCreator) or checked state (a
254 	 * Boolean for toggle button, or an Integer for radio button), or
255 	 * <code>null</code> if neither have been set.
256 	 * <p>
257 	 * The value of this field affects the value of <code>getStyle()</code>.
258 	 * </p>
259 	 */
260 	private Object value = null;
261 
262 	/**
263 	 * Creates a new action with no text and no image.
264 	 * <p>
265 	 * Configure the action later using the set methods.
266 	 * </p>
267 	 */
Action()268 	protected Action() {
269 		// do nothing
270 	}
271 
272 	/**
273 	 * Creates a new action with the given text and no image. Calls the zero-arg
274 	 * constructor, then <code>setText</code>.
275 	 *
276 	 * @param text
277 	 *            the string used as the text for the action, or
278 	 *            <code>null</code> if there is no text
279 	 * @see #setText
280 	 */
Action(String text)281 	protected Action(String text) {
282 		this();
283 		setText(text);
284 	}
285 
286 	/**
287 	 * Creates a new action with the given text and image. Calls the zero-arg
288 	 * constructor, then <code>setText</code> and
289 	 * <code>setImageDescriptor</code>.
290 	 *
291 	 * @param text
292 	 *            the action's text, or <code>null</code> if there is no text
293 	 * @param image
294 	 *            the action's image, or <code>null</code> if there is no
295 	 *            image
296 	 * @see #setText
297 	 * @see #setImageDescriptor
298 	 */
Action(String text, ImageDescriptor image)299 	protected Action(String text, ImageDescriptor image) {
300 		this(text);
301 		setImageDescriptor(image);
302 	}
303 
304 	/**
305 	 * Creates a new action with the given text and style.
306 	 *
307 	 * @param text
308 	 *            the action's text, or <code>null</code> if there is no text
309 	 * @param style
310 	 *            one of <code>AS_PUSH_BUTTON</code>,
311 	 *            <code>AS_CHECK_BOX</code>, <code>AS_DROP_DOWN_MENU</code>,
312 	 *            <code>AS_RADIO_BUTTON</code>, and
313 	 *            <code>AS_UNSPECIFIED</code>.
314 	 */
Action(String text, int style)315 	protected Action(String text, int style) {
316 		this(text);
317 		switch (style) {
318 		case AS_PUSH_BUTTON:
319 			value = VAL_PUSH_BTN;
320 			break;
321 		case AS_CHECK_BOX:
322 			value = VAL_TOGGLE_BTN_OFF;
323 			break;
324 		case AS_DROP_DOWN_MENU:
325 			value = VAL_DROP_DOWN_MENU;
326 			break;
327 		case AS_RADIO_BUTTON:
328 			value = VAL_RADIO_BTN_OFF;
329 			break;
330 		}
331 	}
332 
333 	@Override
getAccelerator()334 	public int getAccelerator() {
335 		return accelerator;
336 	}
337 
338 	@Override
getActionDefinitionId()339 	public String getActionDefinitionId() {
340 		return actionDefinitionId;
341 	}
342 
343 	@Override
getDescription()344 	public String getDescription() {
345 		if (description != null) {
346 			return description;
347 		}
348 		return getToolTipText();
349 	}
350 
351 	@Override
getDisabledImageDescriptor()352 	public ImageDescriptor getDisabledImageDescriptor() {
353 		return disabledImage;
354 	}
355 
356 	@Override
getHelpListener()357 	public HelpListener getHelpListener() {
358 		return helpListener;
359 	}
360 
361 	@Override
getHoverImageDescriptor()362 	public ImageDescriptor getHoverImageDescriptor() {
363 		return hoverImage;
364 	}
365 
366 	@Override
getId()367 	public String getId() {
368 		return id;
369 	}
370 
371 	@Override
getImageDescriptor()372 	public ImageDescriptor getImageDescriptor() {
373 		return image;
374 	}
375 
376 	@Override
getMenuCreator()377 	public IMenuCreator getMenuCreator() {
378 		// The default drop down menu value is only used
379 		// to mark this action requested style. So do not
380 		// return it. For backward compatibility reasons.
381 		if (value == VAL_DROP_DOWN_MENU) {
382 			return null;
383 		}
384 		if (value instanceof IMenuCreator) {
385 			return (IMenuCreator) value;
386 		}
387 		return null;
388 	}
389 
390 	@Override
getStyle()391 	public int getStyle() {
392 		// Infer the style from the value field.
393 		if (value == VAL_PUSH_BTN || value == null) {
394 			return AS_PUSH_BUTTON;
395 		}
396 		if (value == VAL_TOGGLE_BTN_ON || value == VAL_TOGGLE_BTN_OFF) {
397 			return AS_CHECK_BOX;
398 		}
399 		if (value == VAL_RADIO_BTN_ON || value == VAL_RADIO_BTN_OFF) {
400 			return AS_RADIO_BUTTON;
401 		}
402 		if (value instanceof IMenuCreator) {
403 			return AS_DROP_DOWN_MENU;
404 		}
405 
406 		// We should never get to this line...
407 		return AS_PUSH_BUTTON;
408 	}
409 
410 	@Override
getText()411 	public String getText() {
412 		return text;
413 	}
414 
415 	@Override
getToolTipText()416 	public String getToolTipText() {
417 		return toolTipText;
418 	}
419 
420 	@Override
isChecked()421 	public boolean isChecked() {
422 		return value == VAL_TOGGLE_BTN_ON || value == VAL_RADIO_BTN_ON;
423 	}
424 
425 	@Override
isEnabled()426 	public boolean isEnabled() {
427 		return enabled;
428 	}
429 
430 	@Override
isHandled()431 	public boolean isHandled() {
432 		return true;
433 	}
434 
435 	/**
436 	 * Reports the outcome of the running of this action via the
437 	 * {@link IAction#RESULT} property.
438 	 *
439 	 * @param success
440 	 *            <code>true</code> if the action succeeded and
441 	 *            <code>false</code> if the action failed or was not completed
442 	 * @see IAction#RESULT
443 	 * @since 3.0
444 	 */
notifyResult(boolean success)445 	public final void notifyResult(boolean success) {
446 		// avoid Boolean.valueOf(boolean) to allow compilation against JCL
447 		// Foundation (bug 80059)
448 		firePropertyChange(RESULT, null, success ? Boolean.TRUE : Boolean.FALSE);
449 	}
450 
451 	/**
452 	 * The default implementation of this <code>IAction</code> method does
453 	 * nothing. Subclasses should override this method if they do not need
454 	 * information from the triggering event, or override
455 	 * <code>runWithEvent(Event)</code> if they do.
456 	 */
457 	@Override
run()458 	public void run() {
459 		// do nothing
460 	}
461 
462 	/**
463 	 * The default implementation of this <code>IAction</code> method ignores
464 	 * the event argument, and simply calls <code>run()</code>. Subclasses
465 	 * should override this method if they need information from the triggering
466 	 * event, or override <code>run()</code> if not.
467 	 *
468 	 * @param event
469 	 *            the SWT event which triggered this action being run
470 	 * @since 2.0
471 	 */
472 	@Override
runWithEvent(Event event)473 	public void runWithEvent(Event event) {
474 		run();
475 	}
476 
477 	@Override
setAccelerator(int keycode)478 	public void setAccelerator(int keycode) {
479 		this.accelerator = keycode;
480 	}
481 
482 	@Override
setActionDefinitionId(String id)483 	public void setActionDefinitionId(String id) {
484 		actionDefinitionId = id;
485 	}
486 
487 	@Override
setChecked(boolean checked)488 	public void setChecked(boolean checked) {
489 		Object newValue = null;
490 
491 		// For backward compatibility, if the style is not
492 		// set yet, then convert it to a toggle button.
493 		if (value == null || value == VAL_TOGGLE_BTN_ON
494 				|| value == VAL_TOGGLE_BTN_OFF) {
495 			newValue = checked ? VAL_TOGGLE_BTN_ON : VAL_TOGGLE_BTN_OFF;
496 		} else if (value == VAL_RADIO_BTN_ON || value == VAL_RADIO_BTN_OFF) {
497 			newValue = checked ? VAL_RADIO_BTN_ON : VAL_RADIO_BTN_OFF;
498 		} else {
499 			// Some other style already, so do nothing.
500 			return;
501 		}
502 
503 		if (newValue != value) {
504 			value = newValue;
505 			if (checked) {
506 				firePropertyChange(CHECKED, Boolean.FALSE, Boolean.TRUE);
507 			} else {
508 				firePropertyChange(CHECKED, Boolean.TRUE, Boolean.FALSE);
509 			}
510 		}
511 	}
512 
513 	@Override
setDescription(String text)514 	public void setDescription(String text) {
515 
516 		if ((description == null && text != null)
517 				|| (description != null && text == null)
518 				|| (description != null && text != null && !text
519 						.equals(description))) {
520 			String oldDescription = description;
521 			description = text;
522 			firePropertyChange(DESCRIPTION, oldDescription, description);
523 		}
524 	}
525 
526 	@Override
setDisabledImageDescriptor(ImageDescriptor newImage)527 	public void setDisabledImageDescriptor(ImageDescriptor newImage) {
528 		if (disabledImage != newImage) {
529 			ImageDescriptor oldImage = disabledImage;
530 			disabledImage = newImage;
531 			firePropertyChange(IMAGE, oldImage, newImage);
532 		}
533 	}
534 
535 	@Override
setEnabled(boolean enabled)536 	public void setEnabled(boolean enabled) {
537 		if (enabled != this.enabled) {
538 			Boolean oldVal = this.enabled ? Boolean.TRUE : Boolean.FALSE;
539 			Boolean newVal = enabled ? Boolean.TRUE : Boolean.FALSE;
540 			this.enabled = enabled;
541 			firePropertyChange(ENABLED, oldVal, newVal);
542 		}
543 	}
544 
545 	@Override
setHelpListener(HelpListener listener)546 	public void setHelpListener(HelpListener listener) {
547 		helpListener = listener;
548 	}
549 
550 	@Override
setHoverImageDescriptor(ImageDescriptor newImage)551 	public void setHoverImageDescriptor(ImageDescriptor newImage) {
552 		if (hoverImage != newImage) {
553 			ImageDescriptor oldImage = hoverImage;
554 			hoverImage = newImage;
555 			firePropertyChange(IMAGE, oldImage, newImage);
556 		}
557 	}
558 
559 	@Override
setId(String id)560 	public void setId(String id) {
561 		this.id = id;
562 	}
563 
564 	@Override
setImageDescriptor(ImageDescriptor newImage)565 	public void setImageDescriptor(ImageDescriptor newImage) {
566 		if (image != newImage) {
567 			ImageDescriptor oldImage = image;
568 			image = newImage;
569 			firePropertyChange(IMAGE, oldImage, newImage);
570 		}
571 	}
572 
573 	/**
574 	 * Sets the menu creator for this action.
575 	 * <p>
576 	 * Note that if this method is called, it overrides the check status.
577 	 * </p>
578 	 *
579 	 * @param creator
580 	 *            the menu creator, or <code>null</code> if none
581 	 */
582 	@Override
setMenuCreator(IMenuCreator creator)583 	public void setMenuCreator(IMenuCreator creator) {
584 		// For backward compatibility, if the style is not
585 		// set yet, then convert it to a drop down menu.
586 		if (value == null) {
587 			value = creator;
588 			return;
589 		}
590 
591 		if (value instanceof IMenuCreator) {
592 			value = creator == null ? VAL_DROP_DOWN_MENU : creator;
593 		}
594 	}
595 
596 	@Override
setText(String text)597 	public void setText(String text) {
598 		String oldText = this.text;
599 		int oldAccel = this.accelerator;
600 		this.text = text;
601 		if (text != null) {
602 			String acceleratorText = LegacyActionTools
603 					.extractAcceleratorText(text);
604 			if (acceleratorText != null) {
605 				int newAccelerator = LegacyActionTools
606 						.convertLocalizedAccelerator(acceleratorText);
607 				// Be sure to not wipe out the accelerator if nothing found
608 				if (newAccelerator > 0) {
609 					setAccelerator(newAccelerator);
610 				}
611 			}
612 		}
613 		if (!(this.accelerator == oldAccel && (oldText == null ? this.text == null
614 				: oldText.equals(this.text)))) {
615 			firePropertyChange(TEXT, oldText, this.text);
616 		}
617 	}
618 
619 	/**
620 	 * Sets the tool tip text for this action.
621 	 * <p>
622 	 * Fires a property change event for the <code>TOOL_TIP_TEXT</code>
623 	 * property if the tool tip text actually changes as a consequence.
624 	 * </p>
625 	 *
626 	 * @param toolTipText
627 	 *            the tool tip text, or <code>null</code> if none
628 	 */
629 	@Override
setToolTipText(String toolTipText)630 	public void setToolTipText(String toolTipText) {
631 		String oldToolTipText = this.toolTipText;
632 		if (!(oldToolTipText == null ? toolTipText == null : oldToolTipText
633 				.equals(toolTipText))) {
634 			this.toolTipText = toolTipText;
635 			firePropertyChange(TOOL_TIP_TEXT, oldToolTipText, toolTipText);
636 		}
637 	}
638 
639 }
640