fix(pdf): estabiliza drag do selo, remove razao/local e elimina reticencias em botoes e avisos
This commit is contained in:
+45
-13
@@ -105,13 +105,16 @@ public final class PdfCertificateSignDialog {
|
|||||||
this.stage.initOwner(owner);
|
this.stage.initOwner(owner);
|
||||||
}
|
}
|
||||||
this.stage.setResizable(true);
|
this.stage.setResizable(true);
|
||||||
this.stage.setMinWidth(620);
|
this.stage.setMinWidth(700);
|
||||||
this.stage.setMinHeight(460);
|
this.stage.setMinHeight(490);
|
||||||
this.stage.setTitle("Seleção de Certificado Digital — CyberCore Signer");
|
this.stage.setTitle("Seleção de Certificado Digital — CyberCore Signer");
|
||||||
|
|
||||||
initLayout();
|
initLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HBox windowsStoreBox;
|
||||||
|
private HBox pinRow;
|
||||||
|
|
||||||
private void initLayout() {
|
private void initLayout() {
|
||||||
VBox root = new VBox(14);
|
VBox root = new VBox(14);
|
||||||
root.setPadding(new Insets(18, 20, 18, 20));
|
root.setPadding(new Insets(18, 20, 18, 20));
|
||||||
@@ -146,11 +149,13 @@ public final class PdfCertificateSignDialog {
|
|||||||
lblDocTitle.setStyle("-fx-font-weight: bold; -fx-text-fill: -fx-text-secondary; -fx-font-size: 11.5px;");
|
lblDocTitle.setStyle("-fx-font-weight: bold; -fx-text-fill: -fx-text-secondary; -fx-font-size: 11.5px;");
|
||||||
Label lblDocValue = new Label(sourcePdfFile.getName() + " (" + (sourcePdfFile.length() / 1024) + " KB)");
|
Label lblDocValue = new Label(sourcePdfFile.getName() + " (" + (sourcePdfFile.length() / 1024) + " KB)");
|
||||||
lblDocValue.setStyle("-fx-font-weight: bold; -fx-text-fill: -fx-text-primary; -fx-font-size: 11.5px;");
|
lblDocValue.setStyle("-fx-font-weight: bold; -fx-text-fill: -fx-text-primary; -fx-font-size: 11.5px;");
|
||||||
|
lblDocValue.setWrapText(true);
|
||||||
|
|
||||||
Label lblSealTitle = new Label("Selo Visual:");
|
Label lblSealTitle = new Label("Selo Visual:");
|
||||||
lblSealTitle.setStyle("-fx-font-weight: bold; -fx-text-fill: -fx-text-secondary; -fx-font-size: 11.5px;");
|
lblSealTitle.setStyle("-fx-font-weight: bold; -fx-text-fill: -fx-text-secondary; -fx-font-size: 11.5px;");
|
||||||
Label lblSealValue = new Label(sealSummaryText);
|
Label lblSealValue = new Label(sealSummaryText);
|
||||||
lblSealValue.setStyle("-fx-text-fill: #0284c7; -fx-font-weight: bold; -fx-font-size: 11.5px;");
|
lblSealValue.setStyle("-fx-text-fill: #0284c7; -fx-font-weight: bold; -fx-font-size: 11.5px;");
|
||||||
|
lblSealValue.setWrapText(true);
|
||||||
|
|
||||||
summaryGrid.add(lblDocTitle, 0, 0);
|
summaryGrid.add(lblDocTitle, 0, 0);
|
||||||
summaryGrid.add(lblDocValue, 1, 0);
|
summaryGrid.add(lblDocValue, 1, 0);
|
||||||
@@ -209,7 +214,19 @@ public final class PdfCertificateSignDialog {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
HBox pinRow = new HBox(10);
|
// Banner informativo para certificados da Loja do Windows (sem PIN)
|
||||||
|
windowsStoreBox = new HBox(8);
|
||||||
|
windowsStoreBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
windowsStoreBox.setPadding(new Insets(10, 12, 10, 12));
|
||||||
|
windowsStoreBox.setStyle("-fx-background-color: #ecfdf5; -fx-border-color: #a7f3d0; -fx-border-radius: 6px; -fx-background-radius: 6px;");
|
||||||
|
Label lblWinNotice = new Label("✓ Certificado instalado no computador — Gerenciado nativamente pelo Windows (PIN não necessário)");
|
||||||
|
lblWinNotice.setStyle("-fx-text-fill: #065f46; -fx-font-weight: bold; -fx-font-size: 11.5px;");
|
||||||
|
lblWinNotice.setWrapText(true);
|
||||||
|
HBox.setHgrow(lblWinNotice, Priority.ALWAYS);
|
||||||
|
windowsStoreBox.getChildren().add(lblWinNotice);
|
||||||
|
|
||||||
|
// Linha de PIN / Senha para Tokens A3 e Arquivos A1
|
||||||
|
pinRow = new HBox(10);
|
||||||
pinRow.setAlignment(Pos.CENTER_LEFT);
|
pinRow.setAlignment(Pos.CENTER_LEFT);
|
||||||
lblPinTitle = new Label("PIN / Senha:");
|
lblPinTitle = new Label("PIN / Senha:");
|
||||||
lblPinTitle.getStyleClass().add("dialog-field-title");
|
lblPinTitle.getStyleClass().add("dialog-field-title");
|
||||||
@@ -232,6 +249,8 @@ public final class PdfCertificateSignDialog {
|
|||||||
|
|
||||||
lblPinNotice = new Label();
|
lblPinNotice = new Label();
|
||||||
lblPinNotice.setStyle("-fx-font-size: 11.5px;");
|
lblPinNotice.setStyle("-fx-font-size: 11.5px;");
|
||||||
|
lblPinNotice.setWrapText(true);
|
||||||
|
HBox.setHgrow(lblPinNotice, Priority.ALWAYS);
|
||||||
pinRow.getChildren().addAll(lblPinTitle, pinField, btnClearRememberedPin, lblPinNotice);
|
pinRow.getChildren().addAll(lblPinTitle, pinField, btnClearRememberedPin, lblPinNotice);
|
||||||
|
|
||||||
chkRememberPin = new CheckBox("Lembrar PIN/senha com segurança neste computador");
|
chkRememberPin = new CheckBox("Lembrar PIN/senha com segurança neste computador");
|
||||||
@@ -240,12 +259,14 @@ public final class PdfCertificateSignDialog {
|
|||||||
|
|
||||||
VBox tipBox = new VBox(2);
|
VBox tipBox = new VBox(2);
|
||||||
tipBox.setPadding(new Insets(8, 10, 8, 10));
|
tipBox.setPadding(new Insets(8, 10, 8, 10));
|
||||||
tipBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; -fx-border-radius: 6px;");
|
tipBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; -fx-border-radius: 6px; -fx-background-radius: 6px;");
|
||||||
Label lblTip = new Label("🔒 Segurança: O PIN é criptografado localmente com AES-256-GCM vinculado à máquina e nunca é transmitido na rede.");
|
Label lblTip = new Label("🔒 Segurança: O PIN é criptografado localmente com AES-256-GCM vinculado à máquina e nunca é transmitido na rede.");
|
||||||
lblTip.setStyle("-fx-font-size: 11px; -fx-text-fill: -fx-text-secondary;");
|
lblTip.setStyle("-fx-font-size: 11px; -fx-text-fill: -fx-text-secondary;");
|
||||||
|
lblTip.setWrapText(true);
|
||||||
|
lblTip.setMaxWidth(Double.MAX_VALUE);
|
||||||
tipBox.getChildren().add(lblTip);
|
tipBox.getChildren().add(lblTip);
|
||||||
|
|
||||||
certSection.getChildren().addAll(certHeader, certCombo, pinRow, chkRememberPin, tipBox);
|
certSection.getChildren().addAll(certHeader, certCombo, windowsStoreBox, pinRow, chkRememberPin, tipBox);
|
||||||
|
|
||||||
// 3. Rodapé
|
// 3. Rodapé
|
||||||
HBox footer = new HBox(12);
|
HBox footer = new HBox(12);
|
||||||
@@ -266,8 +287,9 @@ public final class PdfCertificateSignDialog {
|
|||||||
btnCancel.setOnAction(e -> stage.close());
|
btnCancel.setOnAction(e -> stage.close());
|
||||||
|
|
||||||
btnSign = new Button("Assinar Documento PDF");
|
btnSign = new Button("Assinar Documento PDF");
|
||||||
btnSign.getStyleClass().addAll("btn-primary", "btn-dialog");
|
btnSign.getStyleClass().addAll("btn-primary", "btn-dialog-wide");
|
||||||
btnSign.setMinWidth(220);
|
btnSign.setMinWidth(220);
|
||||||
|
btnSign.setPrefWidth(Region.USE_COMPUTED_SIZE);
|
||||||
btnSign.setDefaultButton(true);
|
btnSign.setDefaultButton(true);
|
||||||
btnSign.setOnAction(e -> handleSign());
|
btnSign.setOnAction(e -> handleSign());
|
||||||
|
|
||||||
@@ -275,7 +297,7 @@ public final class PdfCertificateSignDialog {
|
|||||||
|
|
||||||
root.getChildren().addAll(header, summaryBox, certSection, footer);
|
root.getChildren().addAll(header, summaryBox, certSection, footer);
|
||||||
|
|
||||||
Scene scene = new Scene(root, 650, 480);
|
Scene scene = new Scene(root, 720, 500);
|
||||||
ThemeManager.applyTheme(scene);
|
ThemeManager.applyTheme(scene);
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
|
|
||||||
@@ -300,31 +322,40 @@ public final class PdfCertificateSignDialog {
|
|||||||
pinField.setDisable(true);
|
pinField.setDisable(true);
|
||||||
lblPinNotice.setText("");
|
lblPinNotice.setText("");
|
||||||
chkRememberPin.setVisible(false);
|
chkRememberPin.setVisible(false);
|
||||||
|
chkRememberPin.setManaged(false);
|
||||||
btnClearRememberedPin.setVisible(false);
|
btnClearRememberedPin.setVisible(false);
|
||||||
|
windowsStoreBox.setVisible(false);
|
||||||
|
windowsStoreBox.setManaged(false);
|
||||||
|
pinRow.setVisible(true);
|
||||||
|
pinRow.setManaged(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isWindowsStore = "provider-win-store".equalsIgnoreCase(cert.providerId());
|
boolean isWindowsStore = "provider-win-store".equalsIgnoreCase(cert.providerId());
|
||||||
if (isWindowsStore) {
|
if (isWindowsStore) {
|
||||||
// Certificado instalado no repositório do Windows (não exige PIN no aplicativo)
|
// Certificado instalado no repositório do Windows (não exige PIN no aplicativo)
|
||||||
pinField.setVisible(false);
|
windowsStoreBox.setVisible(true);
|
||||||
lblPinTitle.setVisible(false);
|
windowsStoreBox.setManaged(true);
|
||||||
|
pinRow.setVisible(false);
|
||||||
|
pinRow.setManaged(false);
|
||||||
chkRememberPin.setVisible(false);
|
chkRememberPin.setVisible(false);
|
||||||
|
chkRememberPin.setManaged(false);
|
||||||
btnClearRememberedPin.setVisible(false);
|
btnClearRememberedPin.setVisible(false);
|
||||||
lblPinNotice.setText("✓ Certificado instalado no computador — Gerenciado pelo Windows (PIN não necessário)");
|
|
||||||
lblPinNotice.setStyle("-fx-text-fill: #10b981; -fx-font-weight: bold; -fx-font-size: 11.5px;");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token USB (PKCS#11) ou Arquivo A1 (PKCS#12)
|
// Token USB (PKCS#11) ou Arquivo A1 (PKCS#12)
|
||||||
pinField.setVisible(true);
|
windowsStoreBox.setVisible(false);
|
||||||
lblPinTitle.setVisible(true);
|
windowsStoreBox.setManaged(false);
|
||||||
|
pinRow.setVisible(true);
|
||||||
|
pinRow.setManaged(true);
|
||||||
|
|
||||||
char[] storedPin = SecurePinStorage.getInstance().getPin(cert.id());
|
char[] storedPin = SecurePinStorage.getInstance().getPin(cert.id());
|
||||||
if (storedPin != null && storedPin.length > 0) {
|
if (storedPin != null && storedPin.length > 0) {
|
||||||
pinField.setText(new String(storedPin));
|
pinField.setText(new String(storedPin));
|
||||||
pinField.setDisable(true);
|
pinField.setDisable(true);
|
||||||
chkRememberPin.setVisible(false);
|
chkRememberPin.setVisible(false);
|
||||||
|
chkRememberPin.setManaged(false);
|
||||||
btnClearRememberedPin.setVisible(true);
|
btnClearRememberedPin.setVisible(true);
|
||||||
lblPinNotice.setText("✓ PIN lembrado com segurança neste computador");
|
lblPinNotice.setText("✓ PIN lembrado com segurança neste computador");
|
||||||
lblPinNotice.setStyle("-fx-text-fill: #10b981; -fx-font-weight: bold; -fx-font-size: 11.5px;");
|
lblPinNotice.setStyle("-fx-text-fill: #10b981; -fx-font-weight: bold; -fx-font-size: 11.5px;");
|
||||||
@@ -332,6 +363,7 @@ public final class PdfCertificateSignDialog {
|
|||||||
pinField.clear();
|
pinField.clear();
|
||||||
pinField.setDisable(false);
|
pinField.setDisable(false);
|
||||||
chkRememberPin.setVisible(true);
|
chkRememberPin.setVisible(true);
|
||||||
|
chkRememberPin.setManaged(true);
|
||||||
btnClearRememberedPin.setVisible(false);
|
btnClearRememberedPin.setVisible(false);
|
||||||
boolean isA3 = cert.type() == CertificateType.A3;
|
boolean isA3 = cert.type() == CertificateType.A3;
|
||||||
lblPinNotice.setText(isA3 ? "Requer PIN do token" : "Requer senha do certificado");
|
lblPinNotice.setText(isA3 ? "Requer PIN do token" : "Requer senha do certificado");
|
||||||
|
|||||||
+95
-57
@@ -18,6 +18,7 @@ import javafx.application.Platform;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
@@ -67,6 +68,9 @@ public final class PdfSignDialog {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfSignDialog.class);
|
private static final Logger logger = LoggerFactory.getLogger(PdfSignDialog.class);
|
||||||
|
|
||||||
|
private static final String DEFAULT_REASON = "Assinatura Digital ICP-Brasil";
|
||||||
|
private static final String DEFAULT_LOCATION = "Brasil";
|
||||||
|
|
||||||
private final Stage stage;
|
private final Stage stage;
|
||||||
private final Stage ownerStage;
|
private final Stage ownerStage;
|
||||||
private final CertificateManager certManager;
|
private final CertificateManager certManager;
|
||||||
@@ -94,11 +98,10 @@ public final class PdfSignDialog {
|
|||||||
private float currentPagePdfWidth = 595.28f;
|
private float currentPagePdfWidth = 595.28f;
|
||||||
private float currentPagePdfHeight = 841.89f;
|
private float currentPagePdfHeight = 841.89f;
|
||||||
|
|
||||||
// Controle de arraste suave (drag & drop) sem jitter/flicker
|
// Controle de arraste suave, milimétrico e estável (drag & drop)
|
||||||
private double dragStartSceneX;
|
private double dragStartOffsetInSealX;
|
||||||
private double dragStartSceneY;
|
private double dragStartOffsetInSealY;
|
||||||
private double initialSealLayoutX;
|
private boolean sealDragOccurred = false;
|
||||||
private double initialSealLayoutY;
|
|
||||||
|
|
||||||
// Coordenadas PDF (pontos tipográficos nativos do PDF)
|
// Coordenadas PDF (pontos tipográficos nativos do PDF)
|
||||||
private Float customPdfX = null;
|
private Float customPdfX = null;
|
||||||
@@ -122,8 +125,6 @@ public final class PdfSignDialog {
|
|||||||
private CheckBox chkVisibleSeal;
|
private CheckBox chkVisibleSeal;
|
||||||
private ComboBox<PositionOption> positionCombo;
|
private ComboBox<PositionOption> positionCombo;
|
||||||
private ComboBox<PageOption> pageCombo;
|
private ComboBox<PageOption> pageCombo;
|
||||||
private TextField txtReason;
|
|
||||||
private TextField txtLocation;
|
|
||||||
private ImageView sealPreviewImageView;
|
private ImageView sealPreviewImageView;
|
||||||
|
|
||||||
// Visualizador Interativo da Folha
|
// Visualizador Interativo da Folha
|
||||||
@@ -335,21 +336,21 @@ public final class PdfSignDialog {
|
|||||||
sealPreviewImageView.setFitHeight(75);
|
sealPreviewImageView.setFitHeight(75);
|
||||||
previewBox.getChildren().add(sealPreviewImageView);
|
previewBox.getChildren().add(sealPreviewImageView);
|
||||||
|
|
||||||
// Opções Adicionais Rápidas
|
// Opções Rápidas de Posição e Página
|
||||||
GridPane grid = new GridPane();
|
GridPane grid = new GridPane();
|
||||||
grid.setHgap(8);
|
grid.setHgap(8);
|
||||||
grid.setVgap(8);
|
grid.setVgap(8);
|
||||||
|
|
||||||
ColumnConstraints colLabel = new ColumnConstraints();
|
ColumnConstraints colLabel = new ColumnConstraints();
|
||||||
colLabel.setMinWidth(90);
|
colLabel.setMinWidth(75);
|
||||||
colLabel.setPrefWidth(95);
|
colLabel.setPrefWidth(80);
|
||||||
colLabel.setHgrow(Priority.NEVER);
|
colLabel.setHgrow(Priority.NEVER);
|
||||||
|
|
||||||
ColumnConstraints colField = new ColumnConstraints();
|
ColumnConstraints colField = new ColumnConstraints();
|
||||||
colField.setHgrow(Priority.ALWAYS);
|
colField.setHgrow(Priority.ALWAYS);
|
||||||
grid.getColumnConstraints().addAll(colLabel, colField);
|
grid.getColumnConstraints().addAll(colLabel, colField);
|
||||||
|
|
||||||
Label lblPosition = new Label("Posição Fixa:");
|
Label lblPosition = new Label("Posição:");
|
||||||
lblPosition.getStyleClass().add("dialog-field-title");
|
lblPosition.getStyleClass().add("dialog-field-title");
|
||||||
positionCombo = new ComboBox<>(FXCollections.observableArrayList(PositionOption.values()));
|
positionCombo = new ComboBox<>(FXCollections.observableArrayList(PositionOption.values()));
|
||||||
positionCombo.setMaxWidth(Double.MAX_VALUE);
|
positionCombo.setMaxWidth(Double.MAX_VALUE);
|
||||||
@@ -362,28 +363,23 @@ public final class PdfSignDialog {
|
|||||||
pageCombo.setMaxWidth(Double.MAX_VALUE);
|
pageCombo.setMaxWidth(Double.MAX_VALUE);
|
||||||
pageCombo.setValue(PageOption.LAST_PAGE);
|
pageCombo.setValue(PageOption.LAST_PAGE);
|
||||||
|
|
||||||
Label lblReason = new Label("Razão:");
|
|
||||||
lblReason.getStyleClass().add("dialog-field-title");
|
|
||||||
txtReason = new TextField("Assinatura Digital ICP-Brasil");
|
|
||||||
txtReason.textProperty().addListener((obs, oldV, newV) -> updateSealPreview());
|
|
||||||
|
|
||||||
Label lblLocation = new Label("Local:");
|
|
||||||
lblLocation.getStyleClass().add("dialog-field-title");
|
|
||||||
txtLocation = new TextField("Brasil");
|
|
||||||
txtLocation.textProperty().addListener((obs, oldV, newV) -> updateSealPreview());
|
|
||||||
|
|
||||||
grid.add(lblPosition, 0, 0);
|
grid.add(lblPosition, 0, 0);
|
||||||
grid.add(positionCombo, 1, 0);
|
grid.add(positionCombo, 1, 0);
|
||||||
grid.add(lblPage, 0, 1);
|
grid.add(lblPage, 0, 1);
|
||||||
grid.add(pageCombo, 1, 1);
|
grid.add(pageCombo, 1, 1);
|
||||||
grid.add(lblReason, 0, 2);
|
|
||||||
grid.add(txtReason, 1, 2);
|
// Dica amigável de posicionamento livre
|
||||||
grid.add(lblLocation, 0, 3);
|
VBox dragTipBox = new VBox(2);
|
||||||
grid.add(txtLocation, 1, 3);
|
dragTipBox.setPadding(new Insets(8, 10, 8, 10));
|
||||||
|
dragTipBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; -fx-border-radius: 6px; -fx-background-radius: 6px;");
|
||||||
|
Label lblDragTip = new Label("💡 Arraste o selo livremente sobre a folha para posicioná-lo onde desejar.");
|
||||||
|
lblDragTip.setStyle("-fx-font-size: 11px; -fx-text-fill: -fx-text-secondary;");
|
||||||
|
lblDragTip.setWrapText(true);
|
||||||
|
dragTipBox.getChildren().add(lblDragTip);
|
||||||
|
|
||||||
reloadActiveDefaultTheme();
|
reloadActiveDefaultTheme();
|
||||||
|
|
||||||
sealSection.getChildren().addAll(sealHeader, defaultSealCard, previewBox, grid);
|
sealSection.getChildren().addAll(sealHeader, defaultSealCard, previewBox, grid, dragTipBox);
|
||||||
return sealSection;
|
return sealSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +507,15 @@ public final class PdfSignDialog {
|
|||||||
|
|
||||||
interactiveOverlayPane = new Pane();
|
interactiveOverlayPane = new Pane();
|
||||||
interactiveOverlayPane.setCursor(Cursor.CROSSHAIR);
|
interactiveOverlayPane.setCursor(Cursor.CROSSHAIR);
|
||||||
interactiveOverlayPane.setOnMouseClicked(this::handleSheetClick);
|
interactiveOverlayPane.setOnMouseClicked(event -> {
|
||||||
|
if (sealDragOccurred) {
|
||||||
|
// Previne que a soltura do mouse no término do arraste cause clique residual
|
||||||
|
sealDragOccurred = false;
|
||||||
|
event.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleSheetClick(event);
|
||||||
|
});
|
||||||
|
|
||||||
sealStampOverlayView = new ImageView();
|
sealStampOverlayView = new ImageView();
|
||||||
sealStampOverlayView.setPreserveRatio(true);
|
sealStampOverlayView.setPreserveRatio(true);
|
||||||
@@ -520,20 +524,33 @@ public final class PdfSignDialog {
|
|||||||
sealStampOverlayView.setEffect(sealShadow);
|
sealStampOverlayView.setEffect(sealShadow);
|
||||||
sealStampOverlayView.setVisible(false);
|
sealStampOverlayView.setVisible(false);
|
||||||
|
|
||||||
// Arraste 100% suave com âncora fixa na cena (elimina totalmente o tremor/flicker)
|
// Arraste com ancoragem precisa no ponto exato do clique dentro do selo (sem salto e sem drift)
|
||||||
sealStampOverlayView.setOnMousePressed(event -> {
|
sealStampOverlayView.setOnMousePressed(event -> {
|
||||||
dragStartSceneX = event.getSceneX();
|
dragStartOffsetInSealX = event.getX();
|
||||||
dragStartSceneY = event.getSceneY();
|
dragStartOffsetInSealY = event.getY();
|
||||||
initialSealLayoutX = sealStampOverlayView.getLayoutX();
|
sealDragOccurred = false;
|
||||||
initialSealLayoutY = sealStampOverlayView.getLayoutY();
|
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
|
|
||||||
sealStampOverlayView.setOnMouseDragged(event -> {
|
sealStampOverlayView.setOnMouseDragged(event -> {
|
||||||
if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return;
|
if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return;
|
||||||
double deltaX = event.getSceneX() - dragStartSceneX;
|
sealDragOccurred = true;
|
||||||
double deltaY = event.getSceneY() - dragStartSceneY;
|
|
||||||
updateSealPositionFromDrag(initialSealLayoutX + deltaX, initialSealLayoutY + deltaY);
|
// Converte coordenada da cena para coordenadas locais do painel da folha
|
||||||
|
Point2D localInSheet = interactiveOverlayPane.sceneToLocal(event.getSceneX(), event.getSceneY());
|
||||||
|
double targetX = localInSheet.getX() - dragStartOffsetInSealX;
|
||||||
|
double targetY = localInSheet.getY() - dragStartOffsetInSealY;
|
||||||
|
updateSealPositionFromDrag(targetX, targetY);
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
sealStampOverlayView.setOnMouseReleased(event -> {
|
||||||
|
// Garante que o evento de soltura do mouse não borbulhe para a folha
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
sealStampOverlayView.setOnMouseClicked(event -> {
|
||||||
|
sealDragOccurred = false;
|
||||||
event.consume();
|
event.consume();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -752,15 +769,17 @@ public final class PdfSignDialog {
|
|||||||
sealStampOverlayView.setLayoutY(clampedY);
|
sealStampOverlayView.setLayoutY(clampedY);
|
||||||
sealStampOverlayView.setVisible(true);
|
sealStampOverlayView.setVisible(true);
|
||||||
|
|
||||||
this.sealRelativeX = clampedX / sheetW;
|
this.sealRelativeX = (sheetW > 0) ? (clampedX / sheetW) : 0;
|
||||||
this.sealRelativeY = clampedY / sheetH;
|
this.sealRelativeY = (sheetH > 0) ? (clampedY / sheetH) : 0;
|
||||||
|
|
||||||
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
|
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
|
||||||
float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0);
|
float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0);
|
||||||
float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0);
|
float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0);
|
||||||
|
|
||||||
float calculatedPdfX = (float) (sealRelativeX * currentPagePdfWidth);
|
// Conversão direta e exata da escala visual na tela para os pontos do PDF
|
||||||
float calculatedPdfY = (float) (currentPagePdfHeight - (sealRelativeY * currentPagePdfHeight) - sealPdfH);
|
double scale = (currentPagePdfWidth > 0) ? (sheetW / currentPagePdfWidth) : 1.0;
|
||||||
|
float calculatedPdfX = (float) (clampedX / scale);
|
||||||
|
float calculatedPdfY = (float) (currentPagePdfHeight - (clampedY / scale) - sealPdfH);
|
||||||
|
|
||||||
this.customPdfX = Math.max(0, Math.min(calculatedPdfX, currentPagePdfWidth - sealPdfW));
|
this.customPdfX = Math.max(0, Math.min(calculatedPdfX, currentPagePdfWidth - sealPdfW));
|
||||||
this.customPdfY = Math.max(0, Math.min(calculatedPdfY, currentPagePdfHeight - sealPdfH));
|
this.customPdfY = Math.max(0, Math.min(calculatedPdfY, currentPagePdfHeight - sealPdfH));
|
||||||
@@ -779,29 +798,50 @@ public final class PdfSignDialog {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCustomPositionActive = false;
|
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
|
||||||
|
float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0);
|
||||||
|
float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0);
|
||||||
|
float margin = 20f;
|
||||||
|
|
||||||
|
float targetPdfX;
|
||||||
|
float targetPdfY;
|
||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case BOTTOM_LEFT -> {
|
case TOP_LEFT -> {
|
||||||
this.sealRelativeX = 0.05;
|
targetPdfX = margin;
|
||||||
this.sealRelativeY = 0.88;
|
targetPdfY = currentPagePdfHeight - sealPdfH - margin;
|
||||||
}
|
}
|
||||||
case TOP_RIGHT -> {
|
case TOP_RIGHT -> {
|
||||||
this.sealRelativeX = 0.70;
|
targetPdfX = currentPagePdfWidth - sealPdfW - margin;
|
||||||
this.sealRelativeY = 0.05;
|
targetPdfY = currentPagePdfHeight - sealPdfH - margin;
|
||||||
}
|
}
|
||||||
case TOP_LEFT -> {
|
case BOTTOM_LEFT -> {
|
||||||
this.sealRelativeX = 0.05;
|
targetPdfX = margin;
|
||||||
this.sealRelativeY = 0.05;
|
targetPdfY = margin;
|
||||||
}
|
}
|
||||||
default -> { // BOTTOM_RIGHT
|
default -> { // BOTTOM_RIGHT
|
||||||
this.sealRelativeX = 0.70;
|
targetPdfX = currentPagePdfWidth - sealPdfW - margin;
|
||||||
this.sealRelativeY = 0.88;
|
targetPdfY = margin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.customPdfX = Math.max(0, Math.min(targetPdfX, currentPagePdfWidth - sealPdfW));
|
||||||
|
this.customPdfY = Math.max(0, Math.min(targetPdfY, currentPagePdfHeight - sealPdfH));
|
||||||
|
this.customPageNumber = currentPageIndex + 1;
|
||||||
|
this.isCustomPositionActive = true;
|
||||||
|
|
||||||
|
// Projeta as coordenadas calculadas na tela de visualização
|
||||||
|
double sheetW = currentBaseSheetWidth * zoomFactor;
|
||||||
|
double sheetH = currentBaseSheetHeight * zoomFactor;
|
||||||
|
double scale = (currentPagePdfWidth > 0) ? (sheetW / currentPagePdfWidth) : 1.0;
|
||||||
|
double screenX = customPdfX * scale;
|
||||||
|
double screenY = (currentPagePdfHeight - customPdfY - sealPdfH) * scale;
|
||||||
|
|
||||||
|
this.sealRelativeX = (sheetW > 0) ? (screenX / sheetW) : 0;
|
||||||
|
this.sealRelativeY = (sheetH > 0) ? (screenY / sheetH) : 0;
|
||||||
|
|
||||||
repositionSealByRelativeCoords();
|
repositionSealByRelativeCoords();
|
||||||
lblPositionFeedback.setText("Posição fixa: " + option.label);
|
lblPositionFeedback.setText("📍 " + option.label + " (Pág. " + (currentPageIndex + 1) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePositionComboSelection() {
|
private void handlePositionComboSelection() {
|
||||||
@@ -821,8 +861,6 @@ public final class PdfSignDialog {
|
|||||||
private void toggleVisibleSeal(boolean active) {
|
private void toggleVisibleSeal(boolean active) {
|
||||||
positionCombo.setDisable(!active);
|
positionCombo.setDisable(!active);
|
||||||
pageCombo.setDisable(!active);
|
pageCombo.setDisable(!active);
|
||||||
txtReason.setDisable(!active);
|
|
||||||
txtLocation.setDisable(!active);
|
|
||||||
sealPreviewImageView.setOpacity(active ? 1.0 : 0.3);
|
sealPreviewImageView.setOpacity(active ? 1.0 : 0.3);
|
||||||
sealStampOverlayView.setVisible(active && selectedPdfBytes != null);
|
sealStampOverlayView.setVisible(active && selectedPdfBytes != null);
|
||||||
}
|
}
|
||||||
@@ -831,8 +869,8 @@ public final class PdfSignDialog {
|
|||||||
try {
|
try {
|
||||||
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
|
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
|
||||||
|
|
||||||
String reason = (txtReason != null && !txtReason.getText().isBlank()) ? txtReason.getText() : "Assinatura Digital ICP-Brasil";
|
String reason = DEFAULT_REASON;
|
||||||
String location = (txtLocation != null && !txtLocation.getText().isBlank()) ? txtLocation.getText() : "Brasil";
|
String location = DEFAULT_LOCATION;
|
||||||
CpfDisplayMode cpfMode = theme.defaultCpfMode() != null ? theme.defaultCpfMode() : CpfDisplayMode.FULL;
|
CpfDisplayMode cpfMode = theme.defaultCpfMode() != null ? theme.defaultCpfMode() : CpfDisplayMode.FULL;
|
||||||
boolean includeDt = theme.defaultIncludeDateTime();
|
boolean includeDt = theme.defaultIncludeDateTime();
|
||||||
String customInfo = theme.formatCustomField();
|
String customInfo = theme.formatCustomField();
|
||||||
@@ -888,8 +926,8 @@ public final class PdfSignDialog {
|
|||||||
customPdfX,
|
customPdfX,
|
||||||
customPdfY,
|
customPdfY,
|
||||||
customPageNumber,
|
customPageNumber,
|
||||||
txtReason.getText(),
|
DEFAULT_REASON,
|
||||||
txtLocation.getText()
|
DEFAULT_LOCATION
|
||||||
);
|
);
|
||||||
|
|
||||||
String sealSummary;
|
String sealSummary;
|
||||||
|
|||||||
@@ -933,14 +933,14 @@
|
|||||||
/* Botões Padronizados para Diálogos e Modais */
|
/* Botões Padronizados para Diálogos e Modais */
|
||||||
.btn-dialog {
|
.btn-dialog {
|
||||||
-fx-min-width: 110px;
|
-fx-min-width: 110px;
|
||||||
-fx-pref-width: 110px;
|
-fx-pref-width: -fx-pref-size;
|
||||||
-fx-pref-height: 34px;
|
-fx-pref-height: 34px;
|
||||||
-fx-alignment: center;
|
-fx-alignment: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-dialog-wide {
|
.btn-dialog-wide {
|
||||||
-fx-min-width: 230px;
|
-fx-min-width: 220px;
|
||||||
-fx-pref-width: 230px;
|
-fx-pref-width: -fx-pref-size;
|
||||||
-fx-pref-height: 36px;
|
-fx-pref-height: 36px;
|
||||||
-fx-alignment: center;
|
-fx-alignment: center;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user