#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:
+112
-62
@@ -31,6 +31,7 @@ import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -209,8 +210,7 @@ public final class GuiHistoryTab {
|
||||
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||
*/
|
||||
public void notifyRunEnded() {
|
||||
DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
|
||||
if (selected != null) {
|
||||
if (!overviewTable.getSelectionModel().getSelectedItems().isEmpty()) {
|
||||
resetButton.setDisable(false);
|
||||
deleteButton.setDisable(false);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ public final class GuiHistoryTab {
|
||||
|
||||
private void buildOverviewTable() {
|
||||
overviewTable.setItems(overviewItems);
|
||||
overviewTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
overviewTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
overviewTable.setPlaceholder(new Label(EMPTY_DB_TEXT));
|
||||
overviewTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
|
||||
|
||||
@@ -479,17 +479,26 @@ public final class GuiHistoryTab {
|
||||
|
||||
statusFilterBox.setOnAction(e -> loadOverview());
|
||||
|
||||
// Detailbereich bei Zeilenselektion
|
||||
overviewTable.getSelectionModel().selectedItemProperty().addListener(
|
||||
(obs, old, selected) -> {
|
||||
if (selected == null) {
|
||||
// Detailbereich und Buttons bei Selektionsänderung aktualisieren
|
||||
overviewTable.getSelectionModel().getSelectedItems().addListener(
|
||||
(ListChangeListener<DocumentHistoryRow>) change -> {
|
||||
List<DocumentHistoryRow> sel =
|
||||
List.copyOf(overviewTable.getSelectionModel().getSelectedItems());
|
||||
boolean running = runningCheck.getAsBoolean();
|
||||
if (sel.isEmpty()) {
|
||||
clearDetailPane();
|
||||
resetButton.setDisable(true);
|
||||
deleteButton.setDisable(true);
|
||||
} else if (sel.size() == 1) {
|
||||
resetButton.setDisable(running);
|
||||
deleteButton.setDisable(running);
|
||||
loadDetails(sel.get(0).fingerprint());
|
||||
} else {
|
||||
resetButton.setDisable(runningCheck.getAsBoolean());
|
||||
deleteButton.setDisable(runningCheck.getAsBoolean());
|
||||
loadDetails(selected.fingerprint());
|
||||
// Mehrfachauswahl: Detail-Bereich löschen, Buttons aktivieren
|
||||
clearDetailPane();
|
||||
resetButton.setDisable(running);
|
||||
deleteButton.setDisable(running);
|
||||
statusBarLabel.setText(sel.size() + " Einträge ausgewählt.");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -598,47 +607,72 @@ public final class GuiHistoryTab {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) return;
|
||||
List<DocumentHistoryRow> selectedItems =
|
||||
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();
|
||||
if (configPath == null) {
|
||||
showInfo("Keine Konfiguration geladen.");
|
||||
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);
|
||||
deleteButton.setDisable(true);
|
||||
statusBarLabel.setText("Status wird zurückgesetzt …");
|
||||
|
||||
workerPool.submit(() -> {
|
||||
int okCount = 0;
|
||||
int errCount = 0;
|
||||
for (DocumentHistoryRow row : selectedItems) {
|
||||
try {
|
||||
resetPort.resetStatus(configPath, fp);
|
||||
LOG.info("Status-Reset durchgeführt für Fingerprint: {}", fp.sha256Hex());
|
||||
resetPort.resetStatus(configPath, row.fingerprint());
|
||||
LOG.info("Status-Reset durchgeführt für Fingerprint: {}", row.fingerprint().sha256Hex());
|
||||
okCount++;
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Status-Reset fehlgeschlagen für {}: {}",
|
||||
row.fingerprint().sha256Hex(), ex.getMessage(), ex);
|
||||
errCount++;
|
||||
}
|
||||
}
|
||||
final int ok = okCount, err = errCount;
|
||||
Platform.runLater(() -> {
|
||||
statusBarLabel.setText("Status erfolgreich zurückgesetzt.");
|
||||
if (err == 0) {
|
||||
statusBarLabel.setText("Status erfolgreich zurückgesetzt: " + ok + " Eintrag/Einträge.");
|
||||
} else {
|
||||
statusBarLabel.setText("Status zurückgesetzt: " + ok + " OK, " + err + " Fehler.");
|
||||
}
|
||||
loadOverview();
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Status-Reset fehlgeschlagen für {}: {}", fp.sha256Hex(), ex.getMessage(), ex);
|
||||
Platform.runLater(() -> {
|
||||
statusBarLabel.setText("Fehler beim Status-Reset: " + ex.getMessage());
|
||||
resetButton.setDisable(false);
|
||||
deleteButton.setDisable(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -648,47 +682,63 @@ public final class GuiHistoryTab {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) return;
|
||||
List<DocumentHistoryRow> selectedItems =
|
||||
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();
|
||||
if (configPath == null) {
|
||||
showInfo("Keine Konfiguration geladen.");
|
||||
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);
|
||||
deleteButton.setDisable(true);
|
||||
statusBarLabel.setText("Eintrag wird gelöscht …");
|
||||
statusBarLabel.setText("Einträge werden gelöscht …");
|
||||
|
||||
workerPool.submit(() -> {
|
||||
int okCount = 0;
|
||||
int errCount = 0;
|
||||
for (DocumentHistoryRow row : selectedItems) {
|
||||
try {
|
||||
deletePort.deleteHistory(configPath, fp);
|
||||
LOG.info("Dokumenteintrag gelöscht für Fingerprint: {}", fp.sha256Hex());
|
||||
deletePort.deleteHistory(configPath, row.fingerprint());
|
||||
LOG.info("Dokumenteintrag gelöscht für Fingerprint: {}", row.fingerprint().sha256Hex());
|
||||
okCount++;
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Löschen fehlgeschlagen für {}: {}",
|
||||
row.fingerprint().sha256Hex(), ex.getMessage(), ex);
|
||||
errCount++;
|
||||
}
|
||||
}
|
||||
final int ok = okCount, err = errCount;
|
||||
Platform.runLater(() -> {
|
||||
statusBarLabel.setText("Eintrag erfolgreich gelöscht.");
|
||||
if (err == 0) {
|
||||
statusBarLabel.setText("Gelöscht: " + ok + " Eintrag/Einträge.");
|
||||
} else {
|
||||
statusBarLabel.setText("Gelöscht: " + ok + " OK, " + err + " Fehler.");
|
||||
}
|
||||
clearDetailPane();
|
||||
loadOverview();
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Löschen fehlgeschlagen für {}: {}", fp.sha256Hex(), ex.getMessage(), ex);
|
||||
Platform.runLater(() -> {
|
||||
statusBarLabel.setText("Fehler beim Löschen: " + ex.getMessage());
|
||||
resetButton.setDisable(false);
|
||||
deleteButton.setDisable(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user