Bug #27: Den zu aggressiven ScrollEvent::consume-Filter durch einen gezielten Filter auf dem internen ScrollPane der PDFView-Skin ersetzt. Der Filter konsumiert nur dann, wenn die Seite keinen ueberlaufenden Inhalt hat oder der Scroll-Inhalt an der oberen bzw. unteren Grenze angekommen ist. Dadurch bleibt Inhalts-Scrolling innerhalb einer Seite weiterhin moeglich; nur der Seitenwechsel per Mausrad wird verhindert. Bug #29: Platform.runLater() durch eine PauseTransition (100 ms) ersetzt, die nach dem vollstaendigen Rendering-Durchlauf der PDFView-Skin den internen ScrollPane explizit auf vValue=0 zuruecksetzt. So wird der Seitenanfang zuverlaessig angezeigt, ohne dass die Skin die Position nachtraeglich ueberschreibt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+97
-11
@@ -10,18 +10,21 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.dlsc.pdfviewfx.PDFView;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* Detailbereich-Komponente zur asynchronen Anzeige von Seiten einer Quelldatei.
|
||||
@@ -94,6 +97,13 @@ public final class PdfPreviewPane {
|
||||
/** Gibt an ob die Navigation bedienbar ist. */
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Interner ScrollPane der PDFView-Skin. Wird nach der Skin-Installation
|
||||
* per Lookup gesetzt und für den Scroll-Schutz (Bug #27) sowie das
|
||||
* Zurücksetzen auf den Seitenanfang (Bug #29) verwendet.
|
||||
*/
|
||||
private ScrollPane pdfViewScrollPane = null;
|
||||
|
||||
/**
|
||||
* Erstellt die Komponente im deaktivierten Platzhalter-Zustand.
|
||||
*/
|
||||
@@ -105,10 +115,15 @@ public final class PdfPreviewPane {
|
||||
pdfView.setShowToolBar(false);
|
||||
pdfView.setId("pdf-preview-view");
|
||||
|
||||
// Bug #27: Mausrad-Scrollevents abfangen, damit PDFView keinen Seitenwechsel auslöst.
|
||||
// Das Mausrad soll ausschließlich innerhalb der aktuellen Seite scrollen.
|
||||
// Seitenwechsel sind nur über die Navigations-Buttons erlaubt.
|
||||
pdfView.addEventFilter(ScrollEvent.SCROLL, ScrollEvent::consume);
|
||||
// Bug #27: Nach Skin-Installation den internen ScrollPane suchen und
|
||||
// dort einen gezielten Filter registrieren, der nur an den Scroll-Grenzen
|
||||
// konsumiert. So wird Mausrad-Seitenwechsel verhindert, ohne das
|
||||
// Inhalts-Scrolling innerhalb einer Seite zu blockieren.
|
||||
pdfView.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||
if (newSkin != null) {
|
||||
Platform.runLater(this::installInternalScrollFilter);
|
||||
}
|
||||
});
|
||||
|
||||
overlayLabel.setId("pdf-preview-overlay-label");
|
||||
overlayLabel.setStyle("-fx-text-fill: #555555;");
|
||||
@@ -246,11 +261,12 @@ public final class PdfPreviewPane {
|
||||
return;
|
||||
}
|
||||
int targetPage = currentPage - 1;
|
||||
pdfView.setPage(targetPage - 1);
|
||||
currentPage = targetPage;
|
||||
updatePageLabel();
|
||||
updateNavigationButtons();
|
||||
// Bug #29: Seite nach dem Layout-Pass von oben anzeigen (0-basierter Index)
|
||||
Platform.runLater(() -> pdfView.setPage(targetPage - 1));
|
||||
// Bug #29: Nach dem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||
scrollToTopAfterRender();
|
||||
}
|
||||
|
||||
private void navigateToNextPage() {
|
||||
@@ -258,11 +274,81 @@ public final class PdfPreviewPane {
|
||||
return;
|
||||
}
|
||||
int targetPage = currentPage + 1;
|
||||
pdfView.setPage(targetPage - 1);
|
||||
currentPage = targetPage;
|
||||
updatePageLabel();
|
||||
updateNavigationButtons();
|
||||
// Bug #29: Seite nach dem Layout-Pass von oben anzeigen (0-basierter Index)
|
||||
Platform.runLater(() -> pdfView.setPage(targetPage - 1));
|
||||
// Bug #29: Nach dem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||
scrollToTopAfterRender();
|
||||
}
|
||||
|
||||
// --- Interne Scroll-Hilfsmethoden ----------------------------------------
|
||||
|
||||
/**
|
||||
* Sucht nach der Skin-Installation den internen ScrollPane der PDFView-Skin
|
||||
* und registriert dort einen EventFilter für Bug #27.
|
||||
*
|
||||
* <p>Der Filter konsumiert ScrollEvents ausschließlich dann, wenn kein
|
||||
* überlaufender Seiteninhalt vorhanden ist oder der Scroll-Inhalt an der
|
||||
* Grenze (oben/unten) angekommen ist. Dadurch wird der interne
|
||||
* Seitenwechsel-Handler der Skin blockiert, ohne normales Inhalts-Scrolling
|
||||
* zu unterbinden.
|
||||
*/
|
||||
private void installInternalScrollFilter() {
|
||||
javafx.scene.Node found = pdfView.lookup(".scroll-pane");
|
||||
if (!(found instanceof ScrollPane sp)) {
|
||||
LOG.warn("PDF-Vorschau: Interner ScrollPane nicht gefunden – Mausrad-Schutz nicht aktiv");
|
||||
return;
|
||||
}
|
||||
pdfViewScrollPane = sp;
|
||||
LOG.debug("PDF-Vorschau: Interner ScrollPane gefunden, Mausrad-Schutz wird installiert");
|
||||
|
||||
sp.addEventFilter(ScrollEvent.SCROLL, event -> {
|
||||
if (event.isInertia()) {
|
||||
return;
|
||||
}
|
||||
// Prüfen ob die Seite überhaupt scrollbaren Inhalt hat
|
||||
javafx.scene.Node content = sp.getContent();
|
||||
if (content == null) {
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
double contentH = content.getBoundsInLocal().getHeight();
|
||||
double viewportH = sp.getViewportBounds().getHeight();
|
||||
boolean hatUeberlauf = contentH > viewportH + 1.0;
|
||||
|
||||
if (!hatUeberlauf) {
|
||||
// Seite passt vollständig in den Viewport: kein Inhalts-Scrolling möglich,
|
||||
// daher Event konsumieren, damit kein Seitenwechsel ausgelöst wird
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
// Seite hat überlaufenden Inhalt: Event nur an den Scroll-Grenzen konsumieren
|
||||
boolean scrolltHoch = event.getDeltaY() > 0;
|
||||
double vVal = sp.getVvalue();
|
||||
boolean anGrenze = scrolltHoch ? (vVal <= 0.0) : (vVal >= 1.0);
|
||||
if (anGrenze) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrollt den internen ScrollPane nach einer kurzen Pause (100 ms) zum
|
||||
* Seitenanfang. Die Verzögerung stellt sicher, dass der PDFView-interne
|
||||
* Rendering-Durchlauf und etwaige nachgelagerte Scroll-Anpassungen der
|
||||
* Skin abgeschlossen sind, bevor die Position zurückgesetzt wird (Bug #29).
|
||||
*/
|
||||
private void scrollToTopAfterRender() {
|
||||
if (pdfViewScrollPane == null) {
|
||||
return;
|
||||
}
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> {
|
||||
pdfViewScrollPane.setVvalue(0.0);
|
||||
pdfViewScrollPane.setHvalue(0.0);
|
||||
});
|
||||
pause.play();
|
||||
}
|
||||
|
||||
// --- Asynchrones Laden ---------------------------------------------------
|
||||
@@ -321,8 +407,8 @@ public final class PdfPreviewPane {
|
||||
showContent();
|
||||
updateNavigationButtons();
|
||||
updatePageLabel();
|
||||
// Bug #29: Zum Seitenanfang scrollen, nachdem der Layout-Pass abgeschlossen ist
|
||||
Platform.runLater(() -> pdfView.setPage(0));
|
||||
// Bug #29: Nach abgeschlossenem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||
scrollToTopAfterRender();
|
||||
LOG.debug("PDF-Vorschau: Rendering erfolgreich – {} Seite(n)", totalPages);
|
||||
} catch (Exception e) {
|
||||
String msg = classifyLoadException(e);
|
||||
|
||||
Reference in New Issue
Block a user