/*******************************************************************************
* Copyright (c) 2013 Arapiki Solutions Inc.
* 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:
* psmith - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.eclipse.utils;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.AbstractOperation;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.swt.widgets.Display;
import com.buildml.eclipse.ISubEditor;
import com.buildml.eclipse.MainEditor;
import com.buildml.model.FatalBuildStoreError;
import com.buildml.model.IBuildStore;
import com.buildml.model.undo.IUndoOp;
/**
* An abstract class that wraps any undo/redo operations. The IUndoOp interface
* is defined in the com.buildml.model.undo package, and is independent from Eclipse
* (can be used outside of Eclipse). This adapter class connects IUndoOp objects into
* the Eclipse framework, as well as providing extra functionality, such as remembering
* which editor tab the operation was invoke on.
*
* @author Peter Smith <psmith@arapiki.com>
*/
public class UndoOpAdapter extends AbstractOperation {
/*=====================================================================================*
* TYPES/FIELDS
*=====================================================================================*/
/** The main BuildML editor we're associated with */
private MainEditor mainEditor = null;
/** The sub-editor (tab) that this operation was performed on */
private ISubEditor subEditor = null;
/** The BuildStore this operation is acting upon */
private IBuildStore buildStore = null;
/** The operation that this adapter is wrapper */
private IUndoOp operation;
/*=====================================================================================*
* CONSTRUCTORS
*=====================================================================================*/
/**
* Create a new UndoOpAdapter object to connect an existing IUndoOp into the Eclipse
* undo/redo framework.
*
* @param label The operation label, as displayed in the "Edit->Undo" menu.
* @param operation The operation to perform.
*/
public UndoOpAdapter(String label, IUndoOp operation) {
super(label);
buildStore = EclipsePartUtils.getActiveBuildStore();
if (buildStore == null) {
throw new FatalBuildStoreError("Couldn't determine active BuildStore");
}
this.operation = operation;
}
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/**
* Execute the operation for the first time.
*/
@Override
public IStatus execute(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
return execute();
}
/*-------------------------------------------------------------------------------------*/
/**
* Default implementation of no-arg execute(). Make the correct sub-editor visible then
* invoke the underlying redo() operation on the IBuildStore.
*
* @return The status of the operation (typically Status.OK_STATUS).
* @throws ExecutionException Something went wrong during execution.
*/
public IStatus execute() throws ExecutionException {
/* bring the appropriate sub-editor to the top */
if (!subEditor.isDisposed()) {
if (mainEditor.getActiveSubEditor() != subEditor) {
mainEditor.setActiveEditor(subEditor);
}
}
/*
* Perform the operation. If there's a change to the database, mark the editor as
* dirty so that it reflects in the editor tab's title with a "*"
*/
IBuildStore buildStore = mainEditor.getBuildStore();
boolean prevState = buildStore.setFastAccessMode(true);
boolean opState = operation.redo();
buildStore.setFastAccessMode(prevState);
if (opState) {
MainEditor editor = EclipsePartUtils.getActiveMainEditor();
if (editor != null) {
editor.markDirty(1);
}
}
return Status.OK_STATUS;
}
/*-------------------------------------------------------------------------------------*/
/**
* Undo a previous operation. This method is called by the Eclipse undo/redo framework.
* We ensure the correct editor tab is open, then invoke the underlying undo/redo
* on the IBuildStore.
*/
@Override
public IStatus undo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
/* bring the appropriate sub-editor to the top */
if (!subEditor.isDisposed()) {
if (mainEditor.getActiveSubEditor() != subEditor) {
mainEditor.setActiveEditor(subEditor);
}
}
/*
* Un-perform the operation. If there's a change to the database, mark the editor as
* dirty so that it reflects in the editor tab's title with a "*"
*/
IBuildStore buildStore = mainEditor.getBuildStore();
boolean prevState = buildStore.setFastAccessMode(true);
boolean opState = operation.undo();
buildStore.setFastAccessMode(prevState);
if (opState) {
MainEditor editor = EclipsePartUtils.getActiveMainEditor();
if (editor != null) {
editor.markDirty(-1);
}
}
return Status.OK_STATUS;
}
/*-------------------------------------------------------------------------------------*/
/**
* Redo a previously "undone" operation. This method is called by the undo/redo framework
* and simply invokes execute().
*/
@Override
public IStatus redo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
return execute();
}
/*-------------------------------------------------------------------------------------*/
/**
* Add this operation to the editor's undo history, then invoke the operation
* for the first time.
*/
public void invoke() {
recordAndInvokeCommon(true);
}
/*-------------------------------------------------------------------------------------*/
/**
* Similar to invoke(), but don't execute the operation (until undo/redo is invoked).
*/
public void record() {
recordAndInvokeCommon(false);
}
/*=====================================================================================*
* PRIVATE METHODS
*=====================================================================================*/
/**
* Common functionality, shared between recordAndInvoke() and recordOnly().
* @param executeIt True if the operation should be executed immediately.
*/
private void recordAndInvokeCommon(final boolean executeIt) {
final UndoOpAdapter adapter = this;
/*
* Execute the operation using the UI thread (if not already on the UI thread).
*/
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
/* add the operation to the undo/redo stack */
mainEditor = EclipsePartUtils.getActiveMainEditor();
subEditor = mainEditor.getActiveSubEditor();
adapter.addContext(mainEditor.getUndoContext());
/* make it so... */
IOperationHistory history = OperationHistoryFactory.getOperationHistory();
try {
if (executeIt) {
history.execute(adapter, null, null);
} else {
history.add(adapter);
mainEditor.markDirty(1);
}
} catch (ExecutionException e) {
throw new FatalBuildStoreError("Exception occurred during execution of operation", e);
}
}
});
}
/*-------------------------------------------------------------------------------------*/
}