mirror of
https://gitlab.com/kauron/jstudy
synced 2024-12-22 08:23:33 +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;
|
||||
|
||||
@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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
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>
|
||||
</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>
|
||||
|
|
Loading…
Reference in a new issue