Table: added undo/redo actions

This commit is contained in:
Carlos Galindo 2019-09-13 16:57:34 +02:00
parent 84d46baa9a
commit 56e1e42d65
Signed by: kauron
GPG Key ID: 83E68706DEE119A3
4 changed files with 240 additions and 12 deletions

View File

@ -51,7 +51,7 @@ public class Controller implements Initializable {
private BorderPane root;
@FXML
private MenuItem menuCloseTab, menuSave;
private MenuItem menuCloseTab, menuSave, menuUndo, menuRedo;
private final BooleanProperty tabIsTable = new SimpleBooleanProperty(false);
private final Map<Tab, Initializable> tabMap = new HashMap<>();
@ -67,9 +67,15 @@ public class Controller implements Initializable {
tabIsTable.set(tabMap.get(n) != null);
menuCloseTab.setDisable(!n.isClosable());
menuSave.disableProperty().unbind();
menuUndo.disableProperty().unbind();
menuRedo.disableProperty().unbind();
menuSave.setDisable(!isTableTab(n) || ((TableController) tabMap.get(n)).saved.get());
if (isTableTab(n))
menuSave.disableProperty().bind(((TableController) tabMap.get(n)).saved);
if (isUndoTab(n)) {
menuUndo.disableProperty().bind(((UndoController) tabMap.get(n)).undoProperty.not());
menuRedo.disableProperty().bind(((UndoController) tabMap.get(n)).redoProperty.not());
}
}
});
tabPane.getTabs().removeListener((ListChangeListener<Tab>) c -> theTest = null);
@ -96,10 +102,26 @@ public class Controller implements Initializable {
return tabMap.containsKey(tab) && tabMap.get(tab) instanceof TableController;
}
private boolean isUndoTab(Tab tab) {
return tabMap.containsKey(tab) && tabMap.get(tab) instanceof UndoController;
}
private boolean isTestTab(Tab tab) {
return tabMap.containsKey(tab) && tabMap.get(tab) instanceof TestController;
}
@FXML
protected void onUndo(ActionEvent event) {
Tab tab = tabPane.getSelectionModel().getSelectedItem();
if (isUndoTab(tab)) ((UndoController) tabMap.get(tab)).undo();
}
@FXML
protected void onRedo(ActionEvent event) {
Tab tab = tabPane.getSelectionModel().getSelectedItem();
if (isUndoTab(tab)) ((UndoController) tabMap.get(tab)).redo();
}
private void checkUpdate() {
// Check new version via gitlab's REST API
String newVersion;

View File

@ -11,7 +11,6 @@ import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
@ -21,8 +20,10 @@ import javafx.stage.FileChooser;
import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TableController implements Initializable {
public class TableController extends UndoController {
@FXML
private TableView<TestItem> table;
@FXML
@ -130,14 +131,15 @@ public class TableController implements Initializable {
newQuestionField.requestFocus();
return;
}
Action<TestItem> action;
if (editing.get() == null) {
TestItem item = new TestItem(newQuestionField.getText().trim(), newAnswerField.getText().trim());
data.add(item);
action = new AddAction<>(item, data);
saved.set(false);
} else {
editing.get().answerProperty().set(newAnswerField.getText().trim());
editing.get().questionProperty().set(newQuestionField.getText().trim());
action = new EditItemAction(editing.get(), newQuestionField.getText(), newAnswerField.getText());
}
doIt(action);
onCancelAction(event);
}
@ -162,27 +164,53 @@ public class TableController implements Initializable {
protected void onSwapAction(ActionEvent event) {
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
Collection<Action<TestItem>> actionList = new ArrayList<>();
for (TestItem item : table.getSelectionModel().getSelectedItems()) {
String question = item.getQuestion();
item.questionProperty().setValue(item.getAnswer());
item.answerProperty().setValue(question);
actionList.add(Action.Reversible.genAction(item, () -> {
String question = item.getQuestion();
item.questionProperty().setValue(item.getAnswer());
item.answerProperty().setValue(question);
}));
}
doIt(new CollectionAction<>(actionList));
table.requestFocus();
}
protected void onDuplicateAction(ActionEvent event) {
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
Collection<Action<TestItem>> actionList = new ArrayList<>();
for (int i : table.getSelectionModel().getSelectedIndices()) {
TestItem item = new TestItem(filtered.get(i));
item.questionProperty().setValue(item.getQuestion() + " 2");
data.add(item);
Pattern p = Pattern.compile("^(.*) (\\d*)$");
String base = item.getQuestion();
int n = 2;
Matcher m = p.matcher(base);
if (m.find()) {
n = Integer.parseInt(m.group(2)) + 1;
base = m.group(1);
}
while (isAQuestion(base + " " + n))
n++;
item.questionProperty().setValue(base + " " + n);
actionList.add(new AddAction<>(item, data));
}
doIt(new CollectionAction<>(actionList));
table.requestFocus();
}
private boolean isAQuestion(String q) {
for (TestItem t : data)
if (t.getQuestion().equals(q))
return true;
return false;
}
protected void onDeleteAction(ActionEvent event) {
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
data.removeAll(table.getSelectionModel().getSelectedItems());
Collection<Action<TestItem>> actionList = new ArrayList<>();
for (TestItem i : table.getSelectionModel().getSelectedItems())
actionList.add(Action.invert(new AddAction<>(i, data, data.indexOf(i))));
doIt(new CollectionAction<>(actionList));
table.requestFocus();
}

View File

@ -0,0 +1,164 @@
package es.kauron.jstudy.controller;
import es.kauron.jstudy.model.TestItem;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.Initializable;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
public abstract class UndoController implements Initializable {
private Stack<Action<?>> undoStack = new Stack<>();
private Stack<Action<?>> redoStack = new Stack<>();
public BooleanProperty undoProperty = new SimpleBooleanProperty(false);
public BooleanProperty redoProperty = new SimpleBooleanProperty(false);
public void doIt(Action<?> action) {
action.action();
redoStack.clear();
redoProperty.set(false);
if (undoStack.empty()) undoProperty.set(true);
undoStack.push(action);
undoProperty.set(true);
}
public void undo() {
Action<?> a = undoStack.pop();
a.reverse();
if (undoStack.empty())
undoProperty.set(false);
if (redoStack.empty())
redoProperty.set(true);
redoStack.push(a);
}
public void redo() {
Action<?> a = redoStack.pop();
a.action();
if (redoStack.empty())
redoProperty.set(false);
if (undoStack.empty())
undoProperty.set(true);
undoStack.push(a);
}
public interface Call {
void call();
}
public abstract static class Action<A> {
protected final A item;
public Action(A item) {
this.item = item;
}
public abstract void action();
public abstract void reverse();
public static <X> Action<X> invert(Action<X> action) {
return new Action<X>(action.item) {
@Override
public void action() {
action.reverse();
}
@Override
public void reverse() {
action.action();
}
};
}
public static <E> Action<E> genAction(E item, Call doit, Call undoit) {
return new Action<E>(item) {
@Override
public void action() { doit.call(); }
@Override
public void reverse() { undoit.call(); }
};
}
public abstract static class Reversible<R> extends Action<R> {
public Reversible(R item) {
super(item);
}
@Override
public void reverse() {
action();
}
public static <R> Action<R> genAction(R item, Call doit) {
return new Reversible<R>(item) {
@Override
public void action() { doit.call(); }
};
}
}
}
public static class AddAction<N> extends Action<N> {
private final List<N> list;
private final int index;
public AddAction(N item, List<N> list) {
this(item, list, list.size());
}
public AddAction(N item, List<N> list, int index) {
super(item);
this.list = list;
this.index = index;
}
@Override
public void action() {
list.add(index, item);
}
@Override
public void reverse() {
list.remove(item);
}
}
public static class CollectionAction<N> extends Action<Collection<Action<N>>> {
public CollectionAction(Collection<Action<N>> item) {
super(item);
}
@Override
public void action() {
for (Action<N> a : item)
a.action();
}
@Override
public void reverse() {
for (Action<N> a : item)
a.reverse();
}
}
public static class EditItemAction extends Action.Reversible<TestItem> {
private String question, answer;
public EditItemAction(TestItem item, String newQuestion, String newAnswer) {
super(item);
this.question = newQuestion;
this.answer = newAnswer;
}
@Override
public void action() {
String q = item.getQuestion();
String a = item.getAnswer();
item.questionProperty().setValue(question);
item.answerProperty().setValue(answer);
question = q;
answer = a;
}
}
}

View File

@ -148,6 +148,20 @@
</MenuItem>
</items>
</Menu>
<Menu text="_Edit">
<items>
<MenuItem onAction="#onUndo" fx:id="menuUndo" disable="true" text="_Undo">
<accelerator>
<KeyCodeCombination control="DOWN" code="Z" shortcut="UP" shift="UP" meta="UP" alt="UP" />
</accelerator>
</MenuItem>
<MenuItem onAction="#onRedo" fx:id="menuRedo" disable="true" text="_Redo">
<accelerator>
<KeyCodeCombination control="DOWN" code="Y" shortcut="UP" shift="UP" meta="UP" alt="UP" />
</accelerator>
</MenuItem>
</items>
</Menu>
</menus>
</MenuBar>
</top>