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 org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import com.dlsc.pdfviewfx.PDFView;
|
import com.dlsc.pdfviewfx.PDFView;
|
||||||
|
import javafx.animation.PauseTransition;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.input.ScrollEvent;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ProgressIndicator;
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.input.ScrollEvent;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detailbereich-Komponente zur asynchronen Anzeige von Seiten einer Quelldatei.
|
* Detailbereich-Komponente zur asynchronen Anzeige von Seiten einer Quelldatei.
|
||||||
@@ -94,6 +97,13 @@ public final class PdfPreviewPane {
|
|||||||
/** Gibt an ob die Navigation bedienbar ist. */
|
/** Gibt an ob die Navigation bedienbar ist. */
|
||||||
private boolean enabled = true;
|
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.
|
* Erstellt die Komponente im deaktivierten Platzhalter-Zustand.
|
||||||
*/
|
*/
|
||||||
@@ -105,10 +115,15 @@ public final class PdfPreviewPane {
|
|||||||
pdfView.setShowToolBar(false);
|
pdfView.setShowToolBar(false);
|
||||||
pdfView.setId("pdf-preview-view");
|
pdfView.setId("pdf-preview-view");
|
||||||
|
|
||||||
// Bug #27: Mausrad-Scrollevents abfangen, damit PDFView keinen Seitenwechsel auslöst.
|
// Bug #27: Nach Skin-Installation den internen ScrollPane suchen und
|
||||||
// Das Mausrad soll ausschließlich innerhalb der aktuellen Seite scrollen.
|
// dort einen gezielten Filter registrieren, der nur an den Scroll-Grenzen
|
||||||
// Seitenwechsel sind nur über die Navigations-Buttons erlaubt.
|
// konsumiert. So wird Mausrad-Seitenwechsel verhindert, ohne das
|
||||||
pdfView.addEventFilter(ScrollEvent.SCROLL, ScrollEvent::consume);
|
// 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.setId("pdf-preview-overlay-label");
|
||||||
overlayLabel.setStyle("-fx-text-fill: #555555;");
|
overlayLabel.setStyle("-fx-text-fill: #555555;");
|
||||||
@@ -246,11 +261,12 @@ public final class PdfPreviewPane {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int targetPage = currentPage - 1;
|
int targetPage = currentPage - 1;
|
||||||
|
pdfView.setPage(targetPage - 1);
|
||||||
currentPage = targetPage;
|
currentPage = targetPage;
|
||||||
updatePageLabel();
|
updatePageLabel();
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
// Bug #29: Seite nach dem Layout-Pass von oben anzeigen (0-basierter Index)
|
// Bug #29: Nach dem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||||
Platform.runLater(() -> pdfView.setPage(targetPage - 1));
|
scrollToTopAfterRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateToNextPage() {
|
private void navigateToNextPage() {
|
||||||
@@ -258,11 +274,81 @@ public final class PdfPreviewPane {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int targetPage = currentPage + 1;
|
int targetPage = currentPage + 1;
|
||||||
|
pdfView.setPage(targetPage - 1);
|
||||||
currentPage = targetPage;
|
currentPage = targetPage;
|
||||||
updatePageLabel();
|
updatePageLabel();
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
// Bug #29: Seite nach dem Layout-Pass von oben anzeigen (0-basierter Index)
|
// Bug #29: Nach dem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||||
Platform.runLater(() -> pdfView.setPage(targetPage - 1));
|
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 ---------------------------------------------------
|
// --- Asynchrones Laden ---------------------------------------------------
|
||||||
@@ -321,8 +407,8 @@ public final class PdfPreviewPane {
|
|||||||
showContent();
|
showContent();
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
updatePageLabel();
|
updatePageLabel();
|
||||||
// Bug #29: Zum Seitenanfang scrollen, nachdem der Layout-Pass abgeschlossen ist
|
// Bug #29: Nach abgeschlossenem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||||
Platform.runLater(() -> pdfView.setPage(0));
|
scrollToTopAfterRender();
|
||||||
LOG.debug("PDF-Vorschau: Rendering erfolgreich – {} Seite(n)", totalPages);
|
LOG.debug("PDF-Vorschau: Rendering erfolgreich – {} Seite(n)", totalPages);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String msg = classifyLoadException(e);
|
String msg = classifyLoadException(e);
|
||||||
|
|||||||
Reference in New Issue
Block a user