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.ui.actions;
15 
16 import org.eclipse.jface.action.Action;
17 import org.eclipse.jface.action.IAction;
18 import org.eclipse.jface.util.IPropertyChangeListener;
19 import org.eclipse.jface.util.PropertyChangeEvent;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.dnd.Clipboard;
22 import org.eclipse.swt.dnd.TextTransfer;
23 import org.eclipse.swt.dnd.TransferData;
24 import org.eclipse.swt.events.KeyAdapter;
25 import org.eclipse.swt.events.KeyEvent;
26 import org.eclipse.swt.events.MouseAdapter;
27 import org.eclipse.swt.events.MouseEvent;
28 import org.eclipse.swt.graphics.Point;
29 import org.eclipse.swt.widgets.Event;
30 import org.eclipse.swt.widgets.Listener;
31 import org.eclipse.swt.widgets.Text;
32 import org.eclipse.ui.IActionBars;
33 import org.eclipse.ui.PlatformUI;
34 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
35 import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
36 
37 /**
38  * Handles the redirection of the global Cut, Copy, Paste, and Select All
39  * actions to either the current inline text control or the part's supplied
40  * action handler.
41  * <p>
42  * This class may be instantiated; it is not intended to be subclassed.
43  * </p>
44  * <p>
45  * Example usage:
46  * </p>
47  *
48  * <pre>
49  * textActionHandler = new TextActionHandler(this.getViewSite().getActionBars());
50  * textActionHandler.addText((Text) textCellEditor1.getControl());
51  * textActionHandler.addText((Text) textCellEditor2.getControl());
52  * textActionHandler.setSelectAllAction(selectAllAction);
53  * </pre>
54  *
55  * @noextend This class is not intended to be subclassed by clients.
56  */
57 public class TextActionHandler {
58 	private DeleteActionHandler textDeleteAction = new DeleteActionHandler();
59 
60 	private CutActionHandler textCutAction = new CutActionHandler();
61 
62 	private CopyActionHandler textCopyAction = new CopyActionHandler();
63 
64 	private PasteActionHandler textPasteAction = new PasteActionHandler();
65 
66 	private SelectAllActionHandler textSelectAllAction = new SelectAllActionHandler();
67 
68 	private IAction deleteAction;
69 
70 	private IAction cutAction;
71 
72 	private IAction copyAction;
73 
74 	private IAction pasteAction;
75 
76 	private IAction selectAllAction;
77 
78 	private IPropertyChangeListener deleteActionListener = new PropertyChangeListener(
79 			textDeleteAction);
80 
81 	private IPropertyChangeListener cutActionListener = new PropertyChangeListener(
82 			textCutAction);
83 
84 	private IPropertyChangeListener copyActionListener = new PropertyChangeListener(
85 			textCopyAction);
86 
87 	private IPropertyChangeListener pasteActionListener = new PropertyChangeListener(
88 			textPasteAction);
89 
90 	private IPropertyChangeListener selectAllActionListener = new PropertyChangeListener(
91 			textSelectAllAction);
92 
93 	private Listener textControlListener = new TextControlListener();
94 
95 	private Text activeTextControl;
96 
97 	private IActionBars actionBars;
98 
99 	private MouseAdapter mouseAdapter = new MouseAdapter() {
100 		@Override
101 		public void mouseUp(MouseEvent e) {
102 			updateActionsEnableState();
103 		}
104 	};
105 
106 	private KeyAdapter keyAdapter = new KeyAdapter() {
107 		@Override
108 		public void keyReleased(KeyEvent e) {
109 			updateActionsEnableState();
110 		}
111 	};
112 
113 	private class TextControlListener implements Listener {
114 		@Override
handleEvent(Event event)115 		public void handleEvent(Event event) {
116 			switch (event.type) {
117 			case SWT.Activate:
118 				activeTextControl = (Text) event.widget;
119 				updateActionsEnableState();
120 				break;
121 			case SWT.Deactivate:
122 				activeTextControl = null;
123 				updateActionsEnableState();
124 				break;
125 			default:
126 				break;
127 			}
128 		}
129 	}
130 
131 	private class PropertyChangeListener implements IPropertyChangeListener {
132 		private IAction actionHandler;
133 
PropertyChangeListener(IAction actionHandler)134 		protected PropertyChangeListener(IAction actionHandler) {
135 			super();
136 			this.actionHandler = actionHandler;
137 		}
138 
139 		@Override
propertyChange(PropertyChangeEvent event)140 		public void propertyChange(PropertyChangeEvent event) {
141 			if (activeTextControl != null) {
142 				return;
143 			}
144 			if (event.getProperty().equals(IAction.ENABLED)) {
145 				Boolean bool = (Boolean) event.getNewValue();
146 				actionHandler.setEnabled(bool.booleanValue());
147 			}
148 		}
149 	}
150 
151 	private class DeleteActionHandler extends Action {
DeleteActionHandler()152 		protected DeleteActionHandler() {
153 			super(IDEWorkbenchMessages.Delete);
154 			setId("TextDeleteActionHandler");//$NON-NLS-1$
155 			setEnabled(false);
156 			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
157 					IIDEHelpContextIds.TEXT_DELETE_ACTION);
158 		}
159 
160 		@Override
runWithEvent(Event event)161 		public void runWithEvent(Event event) {
162 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
163 				Point selection = activeTextControl.getSelection();
164 				if (selection.y == selection.x
165 						&& selection.x < activeTextControl.getCharCount()) {
166 					activeTextControl
167 							.setSelection(selection.x, selection.x + 1);
168 				}
169 				activeTextControl.insert(""); //$NON-NLS-1$
170 
171 				updateActionsEnableState();
172 				return;
173 			}
174 			if (deleteAction != null) {
175 				deleteAction.runWithEvent(event);
176 				return;
177 			}
178 		}
179 
180 		/**
181 		 * Update state.
182 		 */
updateEnabledState()183 		public void updateEnabledState() {
184 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
185 				setEnabled(activeTextControl.getEditable()
186 						&& (activeTextControl.getSelectionCount() > 0
187 						|| activeTextControl.getCaretPosition() < activeTextControl
188 								.getCharCount()));
189 				return;
190 			}
191 			if (deleteAction != null) {
192 				setEnabled(deleteAction.isEnabled());
193 				return;
194 			}
195 			setEnabled(false);
196 		}
197 	}
198 
199 	private class CutActionHandler extends Action {
200 		protected CutActionHandler() {
201 			super(IDEWorkbenchMessages.Cut);
202 			setId("TextCutActionHandler");//$NON-NLS-1$
203 			setEnabled(false);
204 			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
205 					IIDEHelpContextIds.TEXT_CUT_ACTION);
206 		}
207 
208 		@Override
209 		public void runWithEvent(Event event) {
210 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
211 				activeTextControl.cut();
212 				updateActionsEnableState();
213 				return;
214 			}
215 			if (cutAction != null) {
216 				cutAction.runWithEvent(event);
217 				return;
218 			}
219 		}
220 
221 		/**
222 		 * Update state.
223 		 */
224 		public void updateEnabledState() {
225 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
226 				setEnabled(activeTextControl.getEditable() && activeTextControl.getSelectionCount() > 0);
227 				return;
228 			}
229 			if (cutAction != null) {
230 				setEnabled(cutAction.isEnabled());
231 				return;
232 			}
233 			setEnabled(false);
234 		}
235 	}
236 
237 	private class CopyActionHandler extends Action {
CopyActionHandler()238 		protected CopyActionHandler() {
239 			super(IDEWorkbenchMessages.Copy);
240 			setId("TextCopyActionHandler");//$NON-NLS-1$
241 			setEnabled(false);
242 			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
243 					IIDEHelpContextIds.TEXT_COPY_ACTION);
244 		}
245 
246 		@Override
runWithEvent(Event event)247 		public void runWithEvent(Event event) {
248 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
249 				activeTextControl.copy();
250 				updateActionsEnableState();
251 				return;
252 			}
253 			if (copyAction != null) {
254 				copyAction.runWithEvent(event);
255 				return;
256 			}
257 		}
258 
259 		/**
260 		 * Update the state.
261 		 */
updateEnabledState()262 		public void updateEnabledState() {
263 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
264 				setEnabled(activeTextControl.getSelectionCount() > 0);
265 				return;
266 			}
267 			if (copyAction != null) {
268 				setEnabled(copyAction.isEnabled());
269 				return;
270 			}
271 			setEnabled(false);
272 		}
273 	}
274 
275 	private class PasteActionHandler extends Action {
PasteActionHandler()276 		protected PasteActionHandler() {
277 			super(IDEWorkbenchMessages.Paste);
278 			setId("TextPasteActionHandler");//$NON-NLS-1$
279 			setEnabled(false);
280 			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
281 					IIDEHelpContextIds.TEXT_PASTE_ACTION);
282 		}
283 
284 		@Override
runWithEvent(Event event)285 		public void runWithEvent(Event event) {
286 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
287 				activeTextControl.paste();
288 				updateActionsEnableState();
289 				return;
290 			}
291 			if (pasteAction != null) {
292 				pasteAction.runWithEvent(event);
293 				return;
294 			}
295 		}
296 
297 		/**
298 		 * Update the state
299 		 */
updateEnabledState()300 		public void updateEnabledState() {
301 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
302 				boolean canPaste = false;
303 				if (activeTextControl.getEditable()) {
304 					Clipboard clipboard = new Clipboard(activeTextControl.getDisplay());
305 					for (TransferData transferData : clipboard.getAvailableTypes()) {
306 						if (TextTransfer.getInstance().isSupportedType(transferData)) {
307 							canPaste = true;
308 							break;
309 						}
310 					}
311 
312 					clipboard.dispose();
313 				}
314 
315 				setEnabled(canPaste);
316 				return;
317 			}
318 			if (pasteAction != null) {
319 				setEnabled(pasteAction.isEnabled());
320 				return;
321 			}
322 			setEnabled(false);
323 		}
324 	}
325 
326 	private class SelectAllActionHandler extends Action {
SelectAllActionHandler()327 		protected SelectAllActionHandler() {
328 			super(IDEWorkbenchMessages.TextAction_selectAll);
329 			setId("TextSelectAllActionHandler");//$NON-NLS-1$
330 			setEnabled(false);
331 			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
332 					IIDEHelpContextIds.TEXT_SELECT_ALL_ACTION);
333 		}
334 
335 		@Override
runWithEvent(Event event)336 		public void runWithEvent(Event event) {
337 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
338 				activeTextControl.selectAll();
339 				updateActionsEnableState();
340 				return;
341 			}
342 			if (selectAllAction != null) {
343 				selectAllAction.runWithEvent(event);
344 				return;
345 			}
346 		}
347 
348 		/**
349 		 * Update the state.
350 		 */
updateEnabledState()351 		public void updateEnabledState() {
352 			if (activeTextControl != null && !activeTextControl.isDisposed()) {
353 				setEnabled(activeTextControl.getCharCount() > 0);
354 				return;
355 			}
356 			if (selectAllAction != null) {
357 				setEnabled(selectAllAction.isEnabled());
358 				return;
359 			}
360 			setEnabled(false);
361 		}
362 	}
363 
364 	/**
365 	 * Creates a <code>Text</code> control action handler
366 	 * for the global Cut, Copy, Paste, Delete, and Select All
367 	 * of the action bar.
368 	 *
369 	 * @param actionBar the action bar to register global
370 	 *    action handlers for Cut, Copy, Paste, Delete,
371 	 * 	  and Select All
372 	 */
TextActionHandler(IActionBars actionBar)373 	public TextActionHandler(IActionBars actionBar) {
374 		super();
375 		actionBars = actionBar;
376 		updateActionBars();
377 	}
378 
379 	/**
380 	 * Updates the actions bars.
381 	 *
382 	 * @since 3.6
383 	 */
updateActionBars()384 	public void updateActionBars() {
385 		actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(),
386 				textCutAction);
387 		actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(),
388 				textCopyAction);
389 		actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(),
390 				textPasteAction);
391 		actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
392 				textSelectAllAction);
393 		actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(),
394 				textDeleteAction);
395 	}
396 
397 
398 	/**
399 	 * Add a <code>Text</code> control to the handler
400 	 * so that the Cut, Copy, Paste, Delete, and Select All
401 	 * actions are redirected to it when active.
402 	 *
403 	 * @param textControl the inline <code>Text</code> control
404 	 */
addText(Text textControl)405 	public void addText(Text textControl) {
406 		if (textControl == null) {
407 			return;
408 		}
409 
410 		textControl.addListener(SWT.Activate, textControlListener);
411 		textControl.addListener(SWT.Deactivate, textControlListener);
412 
413 		// We really want a selection listener but it is not supported so we
414 		// use a key listener and a mouse listener to know when selection changes
415 		// may have occured
416 		textControl.addKeyListener(keyAdapter);
417 		textControl.addMouseListener(mouseAdapter);
418 
419 		if (textControl.isFocusControl()) {
420 			activeTextControl = textControl;
421 			updateActionsEnableState();
422 		}
423 	}
424 
425 	/**
426 	 * Dispose of this action handler
427 	 */
dispose()428 	public void dispose() {
429 		setCutAction(null);
430 		setCopyAction(null);
431 		setPasteAction(null);
432 		setSelectAllAction(null);
433 		setDeleteAction(null);
434 	}
435 
436 	/**
437 	 * Removes a <code>Text</code> control from the handler
438 	 * so that the Cut, Copy, Paste, Delete, and Select All
439 	 * actions are no longer redirected to it when active.
440 	 *
441 	 * @param textControl the inline <code>Text</code> control
442 	 */
removeText(Text textControl)443 	public void removeText(Text textControl) {
444 		if (textControl == null) {
445 			return;
446 		}
447 
448 		textControl.removeListener(SWT.Activate, textControlListener);
449 		textControl.removeListener(SWT.Deactivate, textControlListener);
450 
451 		textControl.removeMouseListener(mouseAdapter);
452 		textControl.removeKeyListener(keyAdapter);
453 
454 		activeTextControl = null;
455 		updateActionsEnableState();
456 	}
457 
458 	/**
459 	 * Set the default <code>IAction</code> handler for the Copy
460 	 * action. This <code>IAction</code> is run only if no active
461 	 * inline text control.
462 	 *
463 	 * @param action the <code>IAction</code> to run for the
464 	 *    Copy action, or <code>null</code> if not interested.
465 	 */
setCopyAction(IAction action)466 	public void setCopyAction(IAction action) {
467 		if (copyAction == action) {
468 			return;
469 		}
470 
471 		if (copyAction != null) {
472 			copyAction.removePropertyChangeListener(copyActionListener);
473 		}
474 
475 		copyAction = action;
476 
477 		if (copyAction != null) {
478 			copyAction.addPropertyChangeListener(copyActionListener);
479 		}
480 
481 		textCopyAction.updateEnabledState();
482 	}
483 
484 	/**
485 	 * Set the default <code>IAction</code> handler for the Cut
486 	 * action. This <code>IAction</code> is run only if no active
487 	 * inline text control.
488 	 *
489 	 * @param action the <code>IAction</code> to run for the
490 	 *    Cut action, or <code>null</code> if not interested.
491 	 */
setCutAction(IAction action)492 	public void setCutAction(IAction action) {
493 		if (cutAction == action) {
494 			return;
495 		}
496 
497 		if (cutAction != null) {
498 			cutAction.removePropertyChangeListener(cutActionListener);
499 		}
500 
501 		cutAction = action;
502 
503 		if (cutAction != null) {
504 			cutAction.addPropertyChangeListener(cutActionListener);
505 		}
506 
507 		textCutAction.updateEnabledState();
508 	}
509 
510 	/**
511 	 * Set the default <code>IAction</code> handler for the Paste
512 	 * action. This <code>IAction</code> is run only if no active
513 	 * inline text control.
514 	 *
515 	 * @param action the <code>IAction</code> to run for the
516 	 *    Paste action, or <code>null</code> if not interested.
517 	 */
setPasteAction(IAction action)518 	public void setPasteAction(IAction action) {
519 		if (pasteAction == action) {
520 			return;
521 		}
522 
523 		if (pasteAction != null) {
524 			pasteAction.removePropertyChangeListener(pasteActionListener);
525 		}
526 
527 		pasteAction = action;
528 
529 		if (pasteAction != null) {
530 			pasteAction.addPropertyChangeListener(pasteActionListener);
531 		}
532 
533 		textPasteAction.updateEnabledState();
534 	}
535 
536 	/**
537 	 * Set the default <code>IAction</code> handler for the Select All
538 	 * action. This <code>IAction</code> is run only if no active
539 	 * inline text control.
540 	 *
541 	 * @param action the <code>IAction</code> to run for the
542 	 *    Select All action, or <code>null</code> if not interested.
543 	 */
setSelectAllAction(IAction action)544 	public void setSelectAllAction(IAction action) {
545 		if (selectAllAction == action) {
546 			return;
547 		}
548 
549 		if (selectAllAction != null) {
550 			selectAllAction
551 					.removePropertyChangeListener(selectAllActionListener);
552 		}
553 
554 		selectAllAction = action;
555 
556 		if (selectAllAction != null) {
557 			selectAllAction.addPropertyChangeListener(selectAllActionListener);
558 		}
559 
560 		textSelectAllAction.updateEnabledState();
561 	}
562 
563 	/**
564 	 * Set the default <code>IAction</code> handler for the Delete
565 	 * action. This <code>IAction</code> is run only if no active
566 	 * inline text control.
567 	 *
568 	 * @param action the <code>IAction</code> to run for the
569 	 *    Delete action, or <code>null</code> if not interested.
570 	 */
setDeleteAction(IAction action)571 	public void setDeleteAction(IAction action) {
572 		if (deleteAction == action) {
573 			return;
574 		}
575 
576 		if (deleteAction != null) {
577 			deleteAction.removePropertyChangeListener(deleteActionListener);
578 		}
579 
580 		deleteAction = action;
581 
582 		if (deleteAction != null) {
583 			deleteAction.addPropertyChangeListener(deleteActionListener);
584 		}
585 
586 		textDeleteAction.updateEnabledState();
587 	}
588 
589 	/**
590 	 * Update the enable state of the Cut, Copy,
591 	 * Paste, Delete, and Select All action handlers
592 	 */
updateActionsEnableState()593 	private void updateActionsEnableState() {
594 		textCutAction.updateEnabledState();
595 		textCopyAction.updateEnabledState();
596 		textPasteAction.updateEnabledState();
597 		textSelectAllAction.updateEnabledState();
598 		textDeleteAction.updateEnabledState();
599 	}
600 }
601