/******************************************************************************* * Copyright (c) 2016 itemis AG and others. * 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: * Alexander Nyßen (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.mvc.fx.ui.parts; import org.eclipse.core.commands.operations.IOperationHistory; import org.eclipse.core.commands.operations.IOperationHistoryListener; import org.eclipse.core.commands.operations.IUndoContext; import org.eclipse.core.commands.operations.IUndoableOperation; import org.eclipse.core.commands.operations.OperationHistoryEvent; import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation; import org.eclipse.ui.part.WorkbenchPart; import org.eclipse.ui.services.IDisposable; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; /** * A support class to handle the dirty state of a {@link WorkbenchPart} that * uses an {@link IOperationHistory} and an {@link IUndoContext}. * * @author anyssen */ public class HistoryBasedDirtyStateProvider implements IDirtyStateProvider, IDisposable { private ReadOnlyBooleanWrapper dirtyProperty = new ReadOnlyBooleanWrapper( false); private IOperationHistoryListener operationHistoryListener; private IOperationHistory operationHistory; private IUndoContext undoContext; private IUndoableOperation saveLocation = null; /** * Creates a new {@link HistoryBasedDirtyStateProvider}. * * @param operationHistory * The {@link IOperationHistory} to use. * @param undoContext * The {@link IUndoContext} to evaluate. */ public HistoryBasedDirtyStateProvider(IOperationHistory operationHistory, IUndoContext undoContext) { this.operationHistory = operationHistory; this.undoContext = undoContext; if (undoContext == null) { throw new IllegalArgumentException( "WorkbenchPart needs to be adaptable to IUndoContext"); } operationHistoryListener = createOperationHistoryListener(); operationHistory.addOperationHistoryListener(operationHistoryListener); } /** * Returns the {@link IOperationHistoryListener} that is to be used to * update the dirty state of this editor. * * @return The {@link IOperationHistoryListener} that is to be used to * update the dirty state of this editor. */ protected IOperationHistoryListener createOperationHistoryListener() { return new IOperationHistoryListener() { @Override public void historyNotification(final OperationHistoryEvent event) { // XXX: Only react to a subset of the history event // notifications. OPERATION_ADDED is issued when a transaction // is committed on the domain or an operation without a // transaction is executed on the domain; in the latter // case, we would also obtain a DONE notification (which we // ignore here). OPERATION_REMOVED is issued then flushing the // history IUndoableOperation[] undoHistory = event.getHistory() .getUndoHistory(getUndoContext()); if (event.getEventType() == OperationHistoryEvent.UNDONE || event.getEventType() == OperationHistoryEvent.REDONE || event.getEventType() == OperationHistoryEvent.OPERATION_ADDED || event.getEventType() == OperationHistoryEvent.OPERATION_REMOVED) { dirtyProperty.set(getMostRecentDirtyRelevantOperation( undoHistory) != saveLocation); } } }; } @Override public ReadOnlyBooleanProperty dirtyProperty() { return dirtyProperty.getReadOnlyProperty(); } @Override public void dispose() { // unregister operation history listener IOperationHistory operationHistory = getOperationHistory(); if (operationHistory != null) { operationHistory .removeOperationHistoryListener(operationHistoryListener); } operationHistoryListener = null; saveLocation = null; } private IUndoableOperation getMostRecentDirtyRelevantOperation( IUndoableOperation[] undoHistory) { for (int i = undoHistory.length - 1; i >= 0; i--) { if (isContentsRelated(undoHistory[i])) { return undoHistory[i]; } } return null; } private IOperationHistory getOperationHistory() { return operationHistory; } private IUndoContext getUndoContext() { return undoContext; } /** * Tests whether the given {@link IUndoableOperation} is relevant for the * dirty-state of the editor. * * @param operation * The {@link IUndoableOperation} to test. * @return <code>true</code> if the operation encapsulates a dirty-state * relevant change, <code>false</code> otherwise. */ protected boolean isContentsRelated(IUndoableOperation operation) { return operation instanceof ITransactionalOperation && !((ITransactionalOperation) operation).isNoOp() && ((ITransactionalOperation) operation).isContentRelevant(); } @Override public boolean isDirty() { return dirtyProperty.get(); } @Override public void markNonDirty() { IUndoableOperation[] undoHistory = getOperationHistory() .getUndoHistory(getUndoContext()); saveLocation = getMostRecentDirtyRelevantOperation(undoHistory); dirtyProperty.set(false); } }