+/*******************************************************************************
+ * Copyright (c) 2017 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Semantum Oy - (#7066) initial API and implementation
+ *******************************************************************************/
+package org.simantics.views.text.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExecutableExtension;
+import org.eclipse.jface.text.IUndoManager;
+import org.eclipse.jface.text.TextViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/**
+ * Handles the undo/redo command for {@link TextViewer}s through
+ * {@link IUndoManager}.
+ *
+ * <p>
+ * The implementation looks for an IUndoManager from the current focus control
+ * using the {@link TextViewerConstants#KEY_UNDO_MANAGER} data key. Its
+ * existence determines whether this handler {@link #isHandled()} returns
+ * <code>true</code> or <code>false</code>.
+ *
+ * <p>
+ * The handler expects to receive a single string as an argument through the
+ * extension definitions ({@link IExecutableExtension}) that determines which
+ * method is invoked from IUndoManager (<code>undo</code> or <code>redo</code>).
+ *
+ * <p>
+ * Implementation is partially copied from
+ * <code>org.eclipse.ui.internal.handlers.WidgetMethodHandler</code>.
+ *
+ * @since 1.28.0
+ */
+public class TextViewerUndoHandler extends AbstractHandler implements IExecutableExtension {
+
+ /**
+ * The parameters to pass to the method this handler invokes. This handler
+ * always passes no parameters.
+ */
+ protected static final Class<?>[] NO_PARAMETERS = new Class[0];
+
+ public TextViewerUndoHandler() {
+ display = Display.getCurrent();
+ if (display != null) {
+ focusListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ updateEnablement();
+ }
+ };
+ display.addFilter(SWT.FocusIn, focusListener);
+ }
+ }
+
+ void updateEnablement() {
+ boolean rc = isHandled();
+ if (rc != isEnabled()) {
+ setBaseEnabled(rc);
+ }
+ }
+
+ /**
+ * The name of the method to be invoked by this handler. This value should
+ * never be <code>null</code>.
+ */
+ protected String methodName;
+ private Listener focusListener;
+ private Display display;
+
+ @Override
+ public Object execute(final ExecutionEvent event) throws ExecutionException {
+ Callable<?> runnable = getMethodToExecute();
+ if (runnable != null) {
+ try {
+ runnable.call();
+ } catch (ExecutionException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ExecutionException("Unexpected failure executing method " + methodName + " through " + runnable);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public final boolean isHandled() {
+ return getMethodToExecute() != null;
+ }
+
+ /**
+ * Looks up the method on the focus control.
+ *
+ * @return The method on the focus control; <code>null</code> if none.
+ */
+ protected Callable<Boolean> getMethodToExecute() {
+ Display display = Display.getCurrent();
+ if (display == null)
+ return null;
+
+ Control focusControl = display.getFocusControl();
+ if (focusControl == null)
+ return null;
+
+ IUndoManager undoManager = (IUndoManager) focusControl.getData(TextViewerConstants.KEY_UNDO_MANAGER);
+ if (undoManager == null)
+ return null;
+
+ try {
+ Method method = undoManager.getClass().getMethod(methodName, NO_PARAMETERS);
+ if (method != null)
+ return runner(undoManager, method);
+ } catch (NoSuchMethodException e) {
+ // Fall through...
+ }
+
+ return null;
+ }
+
+ protected Callable<Boolean> runner(IUndoManager undoManager, Method method) {
+ return () -> {
+ try {
+ method.invoke(undoManager);
+ return true;
+ } catch (IllegalAccessException e) {
+ // The method is protected, so do nothing.
+ return false;
+ } catch (InvocationTargetException e) {
+ throw new ExecutionException(
+ "An exception occurred while executing " //$NON-NLS-1$
+ + method.getName(), e
+ .getTargetException());
+
+ }
+ };
+ }
+
+ @Override
+ public void setInitializationData(IConfigurationElement config, String propertyName, Object data) {
+ methodName = data.toString();
+ }
+
+ @Override
+ public void dispose() {
+ if (display != null && !display.isDisposed()) {
+ display.removeFilter(SWT.FocusIn, focusListener);
+ }
+ display = null;
+ focusListener = null;
+ }
+
+}
\ No newline at end of file