-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make DAG implementation a base class for easier FP handling; account …
…for possibility of a stored redo also having edges
- Loading branch information
1 parent
e49ae99
commit 6c58441
Showing
4 changed files
with
196 additions
and
217 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphBase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package org.fxmisc.undo.impl.nonlinear; | ||
|
||
import org.reactfx.Subscription; | ||
import org.reactfx.SuspendableNo; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
|
||
public abstract class DirectedAcyclicGraphBase<C> implements DirectedAcyclicGraph<C> { | ||
private final SuspendableNo performingAction = new SuspendableNo(); | ||
public final SuspendableNo performingActionProperty() { return performingAction; } | ||
public final boolean isPerformingAction() { return performingAction.get(); } | ||
|
||
private final List<NonLinearChangeQueue<C>> queues = new ArrayList<>(1); | ||
private final HashMap<C, List<C>> toFromEdges = new HashMap<>(); | ||
private final Subscription subscription; | ||
|
||
public DirectedAcyclicGraphBase() { | ||
subscription = performingAction.values() | ||
.filter(perfomingUpdate -> !perfomingUpdate) | ||
.subscribe(ignore -> recalculateAllValidChanges()); | ||
} | ||
|
||
/** | ||
* Determines if the new pushed change alters or modifies the stored change. If so, an edge is added from | ||
* the pushed change to the stored change | ||
*/ | ||
protected abstract boolean firstDependsOnSecond(C pushedChange, C storedChange); | ||
|
||
/** | ||
* Given a new change and an undoable change, either returns an updated version of the undoable change or | ||
* the original undoable change if no update is needed | ||
*/ | ||
public abstract C updateUndo(C pushedChange, C undoableChange); | ||
|
||
/** | ||
* returns true if the given undo is mutually independent or could be bubbled | ||
*/ | ||
public abstract boolean isUndoValid(C undo); | ||
|
||
public BubbledResult<C> bubbleUndo(C change) { | ||
List<C> dependencies = toFromEdges.get(change); | ||
return bubbleUndo0(change, dependencies); | ||
} | ||
|
||
/** | ||
* bubbles a "bubbly" undo. See also {@link BubbledResult}. | ||
*/ | ||
protected abstract BubbledResult<C> bubbleUndo0(C change, List<C> dependencies); | ||
|
||
/** | ||
* updates all changes after a "bubbly" undo has been bubbled to refactor the result's grounded change | ||
* and to ignore the result's bubbled change | ||
*/ | ||
public abstract C updateUndoPostBubble(C outdatedUndo, C bubblyUndo, BubbledResult<C> bubbledResult); | ||
|
||
/** | ||
* Given a new change and an redoable change, either returns an updated version of the redoable change or | ||
* the original redoable change if no update is needed | ||
*/ | ||
public abstract C updateRedo(C pushedChange, C redoableChange); | ||
|
||
/** | ||
* returns true if the given redo is redoable in whole without bubbling it or in part after bubbling it | ||
*/ | ||
public abstract boolean isRedoValid(C redo); | ||
|
||
public BubbledResult<C> bubbleRedo(C change) { | ||
return bubbledRedo0(change, toFromEdges.containsKey(change) ? toFromEdges.get(change) : Collections.emptyList()); | ||
} | ||
|
||
/** | ||
* bubbles a "bubbly" redo. See also {@link BubbledResult} | ||
*/ | ||
protected abstract BubbledResult<C> bubbledRedo0(C change, List<C> dependencies); | ||
|
||
/** | ||
* updates all redos after a "bubbly" undo/redo has been bubbled | ||
*/ | ||
public abstract C updateRedoPostBubble(C outdatedRedo, C bubblyRedo, BubbledResult<C> bubbledResult); | ||
|
||
public boolean close() { | ||
if (queues.size() == 0) { | ||
subscription.unsubscribe(); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
public final void registerQueue(NonLinearChangeQueue<C> queue) { | ||
if (!queue.getChanges().isEmpty()) { | ||
throw new IllegalArgumentException("A ChangeQueue cannot be registered if it already has 1 or more changes."); | ||
} | ||
queues.add(queue); | ||
} | ||
|
||
public final void unregisterQueue(NonLinearChangeQueue<C> queue) { | ||
forget(queue.getChanges()); | ||
queues.remove(queue); | ||
} | ||
|
||
private NonLinearChangeQueue<C> latestChangeSource; | ||
public final void setLatestChangeSource(NonLinearChangeQueue<C> source) { | ||
latestChangeSource = source; | ||
} | ||
public final NonLinearChangeQueue<C> getLatestChangeSource() { | ||
return latestChangeSource; | ||
} | ||
|
||
public final void updateChangesPostUndoBubble(C original, BubbledResult<C> bubbledResult) { | ||
queues.forEach(q -> q.updateChangesPostBubble(original, bubbledResult)); | ||
} | ||
|
||
public final void updateRedosPostRedoBubble(C original, BubbledResult<C> bubbledResult) { | ||
queues.forEach(q -> q.updateRedosPostChangeBubble(original, bubbledResult)); | ||
} | ||
|
||
public final void updateQueueChanges(C pushedChange) { | ||
queues.forEach(q -> q.updateChanges(pushedChange)); | ||
} | ||
|
||
public final void recalculateAllValidChanges() { | ||
queues.forEach(NonLinearChangeQueue::recalculateValidChanges); | ||
} | ||
|
||
public final void remapEdges(C outdated, C updated) { | ||
toFromEdges.forEach((key, list) -> { | ||
if (key.equals(outdated)) { | ||
toFromEdges.put(updated, toFromEdges.remove(outdated)); | ||
} else { | ||
int index = list.indexOf(outdated); | ||
if (index != -1) { | ||
list.set(index, updated); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
public final void testForDependency(C pushedChange, C storedChange) { | ||
if (firstDependsOnSecond(pushedChange, storedChange)) { | ||
addEdgeFromTo(pushedChange, storedChange); | ||
} | ||
} | ||
|
||
private void addEdgeFromTo(C from, C to) { | ||
if (toFromEdges.containsKey(to)) { | ||
toFromEdges.get(to).add(from); | ||
} else { | ||
List<C> list = new ArrayList<>(1); | ||
list.add(from); | ||
toFromEdges.put(to, list); | ||
} | ||
} | ||
|
||
public final void forget(List<C> changes) { | ||
changes.forEach(toFromEdges::remove); | ||
toFromEdges.values().forEach(ls -> ls.removeAll(changes)); | ||
} | ||
|
||
public final void removeRelatedEdgesOf(C change) { | ||
toFromEdges.forEach((key, list) -> { | ||
if (key.equals(change)) { | ||
toFromEdges.remove(key); | ||
} else { | ||
list.remove(change); | ||
} | ||
}); | ||
} | ||
|
||
public final boolean isMutuallyIndependent(C change) { | ||
return toFromEdges.get(change).isEmpty(); | ||
} | ||
|
||
} |
Oops, something went wrong.