mirror of
https://gitlab.com/kauron/jstudy
synced 2024-11-13 07:33:44 +01:00
Table: added undo/redo actions
This commit is contained in:
parent
84d46baa9a
commit
56e1e42d65
4 changed files with 240 additions and 12 deletions
|
@ -51,7 +51,7 @@ public class Controller implements Initializable {
|
||||||
private BorderPane root;
|
private BorderPane root;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem menuCloseTab, menuSave;
|
private MenuItem menuCloseTab, menuSave, menuUndo, menuRedo;
|
||||||
|
|
||||||
private final BooleanProperty tabIsTable = new SimpleBooleanProperty(false);
|
private final BooleanProperty tabIsTable = new SimpleBooleanProperty(false);
|
||||||
private final Map<Tab, Initializable> tabMap = new HashMap<>();
|
private final Map<Tab, Initializable> tabMap = new HashMap<>();
|
||||||
|
@ -67,9 +67,15 @@ public class Controller implements Initializable {
|
||||||
tabIsTable.set(tabMap.get(n) != null);
|
tabIsTable.set(tabMap.get(n) != null);
|
||||||
menuCloseTab.setDisable(!n.isClosable());
|
menuCloseTab.setDisable(!n.isClosable());
|
||||||
menuSave.disableProperty().unbind();
|
menuSave.disableProperty().unbind();
|
||||||
|
menuUndo.disableProperty().unbind();
|
||||||
|
menuRedo.disableProperty().unbind();
|
||||||
menuSave.setDisable(!isTableTab(n) || ((TableController) tabMap.get(n)).saved.get());
|
menuSave.setDisable(!isTableTab(n) || ((TableController) tabMap.get(n)).saved.get());
|
||||||
if (isTableTab(n))
|
if (isTableTab(n))
|
||||||
menuSave.disableProperty().bind(((TableController) tabMap.get(n)).saved);
|
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);
|
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;
|
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) {
|
private boolean isTestTab(Tab tab) {
|
||||||
return tabMap.containsKey(tab) && tabMap.get(tab) instanceof TestController;
|
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() {
|
private void checkUpdate() {
|
||||||
// Check new version via gitlab's REST API
|
// Check new version via gitlab's REST API
|
||||||
String newVersion;
|
String newVersion;
|
||||||
|
|
|
@ -11,7 +11,6 @@ import javafx.collections.ObservableList;
|
||||||
import javafx.collections.transformation.FilteredList;
|
import javafx.collections.transformation.FilteredList;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
|
@ -21,8 +20,10 @@ import javafx.stage.FileChooser;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class TableController implements Initializable {
|
public class TableController extends UndoController {
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<TestItem> table;
|
private TableView<TestItem> table;
|
||||||
@FXML
|
@FXML
|
||||||
|
@ -130,14 +131,15 @@ public class TableController implements Initializable {
|
||||||
newQuestionField.requestFocus();
|
newQuestionField.requestFocus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Action<TestItem> action;
|
||||||
if (editing.get() == null) {
|
if (editing.get() == null) {
|
||||||
TestItem item = new TestItem(newQuestionField.getText().trim(), newAnswerField.getText().trim());
|
TestItem item = new TestItem(newQuestionField.getText().trim(), newAnswerField.getText().trim());
|
||||||
data.add(item);
|
action = new AddAction<>(item, data);
|
||||||
saved.set(false);
|
saved.set(false);
|
||||||
} else {
|
} else {
|
||||||
editing.get().answerProperty().set(newAnswerField.getText().trim());
|
action = new EditItemAction(editing.get(), newQuestionField.getText(), newAnswerField.getText());
|
||||||
editing.get().questionProperty().set(newQuestionField.getText().trim());
|
|
||||||
}
|
}
|
||||||
|
doIt(action);
|
||||||
onCancelAction(event);
|
onCancelAction(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,27 +164,53 @@ public class TableController implements Initializable {
|
||||||
|
|
||||||
protected void onSwapAction(ActionEvent event) {
|
protected void onSwapAction(ActionEvent event) {
|
||||||
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
|
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
|
||||||
|
Collection<Action<TestItem>> actionList = new ArrayList<>();
|
||||||
for (TestItem item : table.getSelectionModel().getSelectedItems()) {
|
for (TestItem item : table.getSelectionModel().getSelectedItems()) {
|
||||||
|
actionList.add(Action.Reversible.genAction(item, () -> {
|
||||||
String question = item.getQuestion();
|
String question = item.getQuestion();
|
||||||
item.questionProperty().setValue(item.getAnswer());
|
item.questionProperty().setValue(item.getAnswer());
|
||||||
item.answerProperty().setValue(question);
|
item.answerProperty().setValue(question);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
doIt(new CollectionAction<>(actionList));
|
||||||
table.requestFocus();
|
table.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onDuplicateAction(ActionEvent event) {
|
protected void onDuplicateAction(ActionEvent event) {
|
||||||
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
|
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
|
||||||
|
Collection<Action<TestItem>> actionList = new ArrayList<>();
|
||||||
for (int i : table.getSelectionModel().getSelectedIndices()) {
|
for (int i : table.getSelectionModel().getSelectedIndices()) {
|
||||||
TestItem item = new TestItem(filtered.get(i));
|
TestItem item = new TestItem(filtered.get(i));
|
||||||
item.questionProperty().setValue(item.getQuestion() + " 2");
|
Pattern p = Pattern.compile("^(.*) (\\d*)$");
|
||||||
data.add(item);
|
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();
|
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) {
|
protected void onDeleteAction(ActionEvent event) {
|
||||||
if (table.getSelectionModel().getSelectedIndices().size() > 0) saved.set(false);
|
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();
|
table.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
164
src/main/java/es/kauron/jstudy/controller/UndoController.java
Normal file
164
src/main/java/es/kauron/jstudy/controller/UndoController.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -148,6 +148,20 @@
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</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>
|
</menus>
|
||||||
</MenuBar>
|
</MenuBar>
|
||||||
</top>
|
</top>
|
||||||
|
|
Loading…
Reference in a new issue