#86: Mehrfachauswahl im Verlauf-Tab (SelectionMode.MULTIPLE)
Strg+Klick, Shift+Klick und Strg+A (alle sichtbaren Eintraege) werden durch JavaFX natuerlich unterstuetzt. Aktionsbuttons (Reset, Loeschen) arbeiten nun auf allen selektierten Eintraegen. Bei Status-Reset wird ein Hinweis angezeigt, wenn SUCCESS-Eintraege in der Auswahl enthalten sind (Partial-Success-Dialog). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+119
-69
@@ -31,6 +31,7 @@ import javafx.util.Duration;
|
|||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
@@ -209,8 +210,7 @@ public final class GuiHistoryTab {
|
|||||||
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||||
*/
|
*/
|
||||||
public void notifyRunEnded() {
|
public void notifyRunEnded() {
|
||||||
DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
|
if (!overviewTable.getSelectionModel().getSelectedItems().isEmpty()) {
|
||||||
if (selected != null) {
|
|
||||||
resetButton.setDisable(false);
|
resetButton.setDisable(false);
|
||||||
deleteButton.setDisable(false);
|
deleteButton.setDisable(false);
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ public final class GuiHistoryTab {
|
|||||||
|
|
||||||
private void buildOverviewTable() {
|
private void buildOverviewTable() {
|
||||||
overviewTable.setItems(overviewItems);
|
overviewTable.setItems(overviewItems);
|
||||||
overviewTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
overviewTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
overviewTable.setPlaceholder(new Label(EMPTY_DB_TEXT));
|
overviewTable.setPlaceholder(new Label(EMPTY_DB_TEXT));
|
||||||
overviewTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
|
overviewTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
|
||||||
|
|
||||||
@@ -479,17 +479,26 @@ public final class GuiHistoryTab {
|
|||||||
|
|
||||||
statusFilterBox.setOnAction(e -> loadOverview());
|
statusFilterBox.setOnAction(e -> loadOverview());
|
||||||
|
|
||||||
// Detailbereich bei Zeilenselektion
|
// Detailbereich und Buttons bei Selektionsänderung aktualisieren
|
||||||
overviewTable.getSelectionModel().selectedItemProperty().addListener(
|
overviewTable.getSelectionModel().getSelectedItems().addListener(
|
||||||
(obs, old, selected) -> {
|
(ListChangeListener<DocumentHistoryRow>) change -> {
|
||||||
if (selected == null) {
|
List<DocumentHistoryRow> sel =
|
||||||
|
List.copyOf(overviewTable.getSelectionModel().getSelectedItems());
|
||||||
|
boolean running = runningCheck.getAsBoolean();
|
||||||
|
if (sel.isEmpty()) {
|
||||||
clearDetailPane();
|
clearDetailPane();
|
||||||
resetButton.setDisable(true);
|
resetButton.setDisable(true);
|
||||||
deleteButton.setDisable(true);
|
deleteButton.setDisable(true);
|
||||||
|
} else if (sel.size() == 1) {
|
||||||
|
resetButton.setDisable(running);
|
||||||
|
deleteButton.setDisable(running);
|
||||||
|
loadDetails(sel.get(0).fingerprint());
|
||||||
} else {
|
} else {
|
||||||
resetButton.setDisable(runningCheck.getAsBoolean());
|
// Mehrfachauswahl: Detail-Bereich löschen, Buttons aktivieren
|
||||||
deleteButton.setDisable(runningCheck.getAsBoolean());
|
clearDetailPane();
|
||||||
loadDetails(selected.fingerprint());
|
resetButton.setDisable(running);
|
||||||
|
deleteButton.setDisable(running);
|
||||||
|
statusBarLabel.setText(sel.size() + " Einträge ausgewählt.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -598,47 +607,72 @@ public final class GuiHistoryTab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
|
List<DocumentHistoryRow> selectedItems =
|
||||||
if (selected == null) return;
|
List.copyOf(overviewTable.getSelectionModel().getSelectedItems());
|
||||||
|
if (selectedItems.isEmpty()) return;
|
||||||
|
|
||||||
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
|
||||||
confirm.setTitle("Status zurücksetzen");
|
|
||||||
confirm.setHeaderText("Status zurücksetzen?");
|
|
||||||
confirm.setContentText(
|
|
||||||
"Setzt den Status des Dokuments auf READY_FOR_AI zurück.\n"
|
|
||||||
+ "Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n"
|
|
||||||
+ "Die Versuchshistorie bleibt vollständig erhalten.\n\n"
|
|
||||||
+ "Das Dokument wird beim nächsten Verarbeitungslauf erneut verarbeitet.\n\n"
|
|
||||||
+ "Quelldatei: " + selected.sourceFileName());
|
|
||||||
Optional<ButtonType> choice = confirm.showAndWait();
|
|
||||||
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
|
||||||
|
|
||||||
DocumentFingerprint fp = selected.fingerprint();
|
|
||||||
Path configPath = configPathSupplier.get();
|
Path configPath = configPathSupplier.get();
|
||||||
if (configPath == null) {
|
if (configPath == null) {
|
||||||
showInfo("Keine Konfiguration geladen.");
|
showInfo("Keine Konfiguration geladen.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long successCount = selectedItems.stream()
|
||||||
|
.filter(r -> r.overallStatus() == ProcessingStatus.SUCCESS)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
|
||||||
|
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
|
||||||
|
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
|
||||||
|
if (selectedItems.size() == 1) {
|
||||||
|
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
|
||||||
|
} else {
|
||||||
|
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
|
||||||
|
}
|
||||||
|
if (successCount > 0) {
|
||||||
|
sb.append("\n\nHinweis: ").append(successCount)
|
||||||
|
.append(" der ausgewählten Einträge ")
|
||||||
|
.append(successCount == 1 ? "hat" : "haben")
|
||||||
|
.append(" Status \"Erfolgreich\". ")
|
||||||
|
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
|
||||||
|
.append(" erneut verarbeitet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
confirm.setTitle("Status zurücksetzen");
|
||||||
|
confirm.setHeaderText("Status zurücksetzen?");
|
||||||
|
confirm.setContentText(sb.toString());
|
||||||
|
Optional<ButtonType> choice = confirm.showAndWait();
|
||||||
|
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
||||||
|
|
||||||
resetButton.setDisable(true);
|
resetButton.setDisable(true);
|
||||||
deleteButton.setDisable(true);
|
deleteButton.setDisable(true);
|
||||||
statusBarLabel.setText("Status wird zurückgesetzt …");
|
statusBarLabel.setText("Status wird zurückgesetzt …");
|
||||||
|
|
||||||
workerPool.submit(() -> {
|
workerPool.submit(() -> {
|
||||||
try {
|
int okCount = 0;
|
||||||
resetPort.resetStatus(configPath, fp);
|
int errCount = 0;
|
||||||
LOG.info("Status-Reset durchgeführt für Fingerprint: {}", fp.sha256Hex());
|
for (DocumentHistoryRow row : selectedItems) {
|
||||||
Platform.runLater(() -> {
|
try {
|
||||||
statusBarLabel.setText("Status erfolgreich zurückgesetzt.");
|
resetPort.resetStatus(configPath, row.fingerprint());
|
||||||
loadOverview();
|
LOG.info("Status-Reset durchgeführt für Fingerprint: {}", row.fingerprint().sha256Hex());
|
||||||
});
|
okCount++;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LOG.error("Status-Reset fehlgeschlagen für {}: {}", fp.sha256Hex(), ex.getMessage(), ex);
|
LOG.error("Status-Reset fehlgeschlagen für {}: {}",
|
||||||
Platform.runLater(() -> {
|
row.fingerprint().sha256Hex(), ex.getMessage(), ex);
|
||||||
statusBarLabel.setText("Fehler beim Status-Reset: " + ex.getMessage());
|
errCount++;
|
||||||
resetButton.setDisable(false);
|
}
|
||||||
deleteButton.setDisable(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
final int ok = okCount, err = errCount;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (err == 0) {
|
||||||
|
statusBarLabel.setText("Status erfolgreich zurückgesetzt: " + ok + " Eintrag/Einträge.");
|
||||||
|
} else {
|
||||||
|
statusBarLabel.setText("Status zurückgesetzt: " + ok + " OK, " + err + " Fehler.");
|
||||||
|
}
|
||||||
|
loadOverview();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -648,47 +682,63 @@ public final class GuiHistoryTab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
|
List<DocumentHistoryRow> selectedItems =
|
||||||
if (selected == null) return;
|
List.copyOf(overviewTable.getSelectionModel().getSelectedItems());
|
||||||
|
if (selectedItems.isEmpty()) return;
|
||||||
|
|
||||||
Alert confirm = new Alert(Alert.AlertType.WARNING);
|
|
||||||
confirm.setTitle("Eintrag löschen");
|
|
||||||
confirm.setHeaderText("Eintrag vollständig löschen?");
|
|
||||||
confirm.setContentText(
|
|
||||||
"Der Stammsatz und ALLE Verarbeitungsversuche werden unwiderruflich gelöscht.\n"
|
|
||||||
+ "Diese Aktion kann nicht rückgängig gemacht werden.\n\n"
|
|
||||||
+ "Quelldatei: " + selected.sourceFileName());
|
|
||||||
confirm.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
|
|
||||||
Optional<ButtonType> choice = confirm.showAndWait();
|
|
||||||
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
|
||||||
|
|
||||||
DocumentFingerprint fp = selected.fingerprint();
|
|
||||||
Path configPath = configPathSupplier.get();
|
Path configPath = configPathSupplier.get();
|
||||||
if (configPath == null) {
|
if (configPath == null) {
|
||||||
showInfo("Keine Konfiguration geladen.");
|
showInfo("Keine Konfiguration geladen.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String contentText;
|
||||||
|
if (selectedItems.size() == 1) {
|
||||||
|
contentText = "Der Stammsatz und ALLE Verarbeitungsversuche werden unwiderruflich gelöscht.\n"
|
||||||
|
+ "Diese Aktion kann nicht rückgängig gemacht werden.\n\n"
|
||||||
|
+ "Quelldatei: " + selectedItems.get(0).sourceFileName();
|
||||||
|
} else {
|
||||||
|
contentText = selectedItems.size() + " Einträge werden mit allen Versuchen unwiderruflich gelöscht.\n"
|
||||||
|
+ "Diese Aktion kann nicht rückgängig gemacht werden.";
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert confirm = new Alert(Alert.AlertType.WARNING);
|
||||||
|
confirm.setTitle("Eintrag löschen");
|
||||||
|
confirm.setHeaderText(selectedItems.size() == 1 ? "Eintrag vollständig löschen?"
|
||||||
|
: selectedItems.size() + " Einträge vollständig löschen?");
|
||||||
|
confirm.setContentText(contentText);
|
||||||
|
confirm.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
|
||||||
|
Optional<ButtonType> choice = confirm.showAndWait();
|
||||||
|
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
||||||
|
|
||||||
resetButton.setDisable(true);
|
resetButton.setDisable(true);
|
||||||
deleteButton.setDisable(true);
|
deleteButton.setDisable(true);
|
||||||
statusBarLabel.setText("Eintrag wird gelöscht …");
|
statusBarLabel.setText("Einträge werden gelöscht …");
|
||||||
|
|
||||||
workerPool.submit(() -> {
|
workerPool.submit(() -> {
|
||||||
try {
|
int okCount = 0;
|
||||||
deletePort.deleteHistory(configPath, fp);
|
int errCount = 0;
|
||||||
LOG.info("Dokumenteintrag gelöscht für Fingerprint: {}", fp.sha256Hex());
|
for (DocumentHistoryRow row : selectedItems) {
|
||||||
Platform.runLater(() -> {
|
try {
|
||||||
statusBarLabel.setText("Eintrag erfolgreich gelöscht.");
|
deletePort.deleteHistory(configPath, row.fingerprint());
|
||||||
clearDetailPane();
|
LOG.info("Dokumenteintrag gelöscht für Fingerprint: {}", row.fingerprint().sha256Hex());
|
||||||
loadOverview();
|
okCount++;
|
||||||
});
|
} catch (Exception ex) {
|
||||||
} catch (Exception ex) {
|
LOG.error("Löschen fehlgeschlagen für {}: {}",
|
||||||
LOG.error("Löschen fehlgeschlagen für {}: {}", fp.sha256Hex(), ex.getMessage(), ex);
|
row.fingerprint().sha256Hex(), ex.getMessage(), ex);
|
||||||
Platform.runLater(() -> {
|
errCount++;
|
||||||
statusBarLabel.setText("Fehler beim Löschen: " + ex.getMessage());
|
}
|
||||||
resetButton.setDisable(false);
|
|
||||||
deleteButton.setDisable(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
final int ok = okCount, err = errCount;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (err == 0) {
|
||||||
|
statusBarLabel.setText("Gelöscht: " + ok + " Eintrag/Einträge.");
|
||||||
|
} else {
|
||||||
|
statusBarLabel.setText("Gelöscht: " + ok + " OK, " + err + " Fehler.");
|
||||||
|
}
|
||||||
|
clearDetailPane();
|
||||||
|
loadOverview();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user