• R/O
  • SSH
  • HTTPS

jpki: コミット


コミットメタ情報

リビジョン57 (tree)
日時2020-11-18 16:18:58
作者hirukawa_ryo

ログメッセージ

* jpki-pdf-signer 1.2.5
依存ライブラリ fx-util 0.3.8 -> 0.4.0
SingletonApplicationを継承するMainクラスをMainAppクラスにリネームして、エントリーメソッドmainを持つMainクラスを追加しました。

変更サマリ

差分

Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
--- jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/Main.css (revision 56)
+++ jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/Main.css (nonexistent)
@@ -1,108 +0,0 @@
1-.root {
2- -fx-font-family: "Meiryo";
3- -fx-font-size: 12px;
4-}
5-
6-FontIcon {
7- -fx-icon-size: 17;
8- -fx-icon-color: -fx-selection-bar;
9-}
10-.menu-item:focused FontIcon {
11- -fx-icon-color: -fx-selection-bar-text;
12-}
13-
14-.list-view {
15- -fx-padding: 0;
16- -fx-background-insets: 0;
17-}
18-.list-cell {
19- -fx-padding: 0px;
20-}
21-.list-cell:selected {
22- -fx-background-color: -fx-accent;
23-}
24-.list-cell:selected .label {
25- -fx-text-fill: white;
26-}
27-
28-#buttonBox {
29- -fx-background-color: #303030;
30-}
31-#buttonBox > .label {
32- -fx-alignment: center;
33- -fx-min-width: 80px;
34- -fx-font-size: 14px;
35- -fx-text-fill: #F2F2F2;
36-}
37-#buttonBox .button {
38- -fx-padding: 0 10px 0 7px;
39- -fx-min-height: 25px;
40- -fx-pref-height: 25px;
41- -fx-min-width: 40px;
42- -fx-background-insets: 0;
43- -fx-background-radius: 0;
44- -fx-background-color: transparent;
45- -fx-text-fill: #CCCCCC;
46- -fx-skin: "net.osdn.util.javafx.scene.control.pdf.TransitionButtonSkin";
47-}
48-#buttonBox .button:focused {
49- -fx-background-color: rgba(255, 255, 255, 0.2);
50-}
51-#buttonBox .button:hover {
52- -fx-background-color: rgba(255, 255, 255, 0.2);
53- -fx-text-fill: #FFFFFF;
54-}
55-#buttonBox .button:pressed {
56- -fx-background-color: rgba(255, 255, 255, 0.1);
57- -fx-text-fill: #CCCCCC;
58-}
59-#buttonBox FontIcon {
60- -fx-icon-size: 17;
61- -fx-icon-color: #CCCCCC;
62-}
63-#buttonBox .button:hover FontIcon {
64- -fx-icon-color: #FFFFFF;
65-}
66-
67-.scroll-bar:horizontal{
68- -fx-background-color: #F1F1F1;
69- -fx-background-insets: 0;
70- -fx-padding: 1 0 1 0;
71-}
72-.scroll-bar:vertical{
73- -fx-background-color: #F1F1F1;
74- -fx-background-insets: 0;
75- -fx-padding: 0 1 0 1;
76-}
77-.scroll-bar:horizontal .track ,
78-.scroll-bar:vertical .track{
79- -fx-background-color: #F1F1F1;
80-}
81-.scroll-bar:horizontal .thumb,
82-.scroll-bar:vertical .thumb {
83- -fx-background-color: #C1C1C1;
84- -fx-background-insets: 0, 0, 0;
85- -fx-background-radius: 0;
86-}
87-.scroll-bar:horizontal .thumb:hover,
88-.scroll-bar:vertical .thumb:hover {
89- -fx-background-color: #A8A8A8;
90-}
91-.scroll-bar:horizontal .thumb:pressed,
92-.scroll-bar:vertical .thumb:pressed {
93- -fx-background-color: #787878;
94-}
95-.scroll-bar:vertical .increment-button,
96-.scroll-bar:vertical .decrement-button,
97-.scroll-bar:horizontal .increment-button,
98-.scroll-bar:horizontal .decrement-button {
99- -fx-padding: 0px;
100-}
101-.scroll-bar:vertical .increment-arrow,
102-.scroll-bar:vertical .decrement-arrow {
103- -fx-padding: 0 5 0 5;
104-}
105-.scroll-bar:horizontal .increment-arrow,
106-.scroll-bar:horizontal .decrement-arrow {
107- -fx-padding: 5 0 5 0;
108-}
--- jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/Main.java (revision 56)
+++ jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/Main.java (revision 57)
@@ -1,614 +1,12 @@
11 package net.osdn.jpki.pdf_signer;
22
33 import javafx.application.Platform;
4-import javafx.beans.binding.Bindings;
5-import javafx.beans.binding.ObjectBinding;
6-import javafx.beans.binding.StringBinding;
7-import javafx.beans.property.BooleanProperty;
8-import javafx.beans.property.ObjectProperty;
9-import javafx.beans.property.SimpleBooleanProperty;
10-import javafx.beans.property.SimpleObjectProperty;
11-import javafx.event.ActionEvent;
12-import javafx.fxml.FXML;
13-import javafx.fxml.Initializable;
14-import javafx.geometry.Rectangle2D;
15-import javafx.scene.Cursor;
16-import javafx.scene.Parent;
17-import javafx.scene.Scene;
18-import javafx.scene.control.Button;
19-import javafx.scene.control.ButtonType;
20-import javafx.scene.control.ListCell;
21-import javafx.scene.control.ListView;
22-import javafx.scene.control.MenuItem;
23-import javafx.scene.control.ProgressIndicator;
24-import javafx.scene.image.Image;
25-import javafx.scene.image.ImageView;
26-import javafx.scene.input.DragEvent;
27-import javafx.scene.input.MouseButton;
28-import javafx.scene.input.MouseEvent;
29-import javafx.scene.input.TransferMode;
30-import javafx.stage.FileChooser;
31-import javafx.stage.Stage;
32-import javafx.stage.WindowEvent;
33-import net.osdn.jpki.pdf_signer.control.LicenseDialog;
34-import net.osdn.jpki.pdf_signer.control.Toast;
35-import net.osdn.jpki.wrapper.JpkiException;
36-import net.osdn.jpki.wrapper.JpkiWrapper;
374 import net.osdn.util.javafx.application.SingletonApplication;
38-import net.osdn.util.javafx.concurrent.Async;
39-import net.osdn.util.javafx.fxml.Fxml;
40-import net.osdn.util.javafx.scene.control.Dialogs;
41-import net.osdn.util.javafx.scene.control.pdf.Pager;
42-import net.osdn.util.javafx.scene.control.pdf.PdfView;
43-import net.osdn.util.javafx.stage.StageUtil;
44-import org.apache.pdfbox.pdmodel.PDDocument;
45-import org.apache.pdfbox.pdmodel.common.PDRectangle;
46-import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
47-import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
48-import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;
495
50-import java.awt.Desktop;
51-import java.io.File;
52-import java.io.FileInputStream;
53-import java.io.FileOutputStream;
54-import java.io.IOException;
55-import java.io.InputStream;
56-import java.io.OutputStream;
57-import java.io.UncheckedIOException;
58-import java.net.URI;
59-import java.net.URL;
60-import java.nio.file.Files;
61-import java.nio.file.StandardCopyOption;
62-import java.util.List;
63-import java.util.ResourceBundle;
64-import java.util.prefs.Preferences;
6+public class Main {
657
66-public class Main extends SingletonApplication implements Initializable {
67-
68- public static final String APPLICATION_NAME = "JPKI PDF SIGNER";
69- public static final String APPLICATION_VERSION;
70-
71- static {
72- System.setProperty(
73- "org.apache.commons.logging.LogFactory", "net.osdn.jpki.pdf_signer.LogFilter");
74- LogFilter.setLevel("org.apache.pdfbox", LogFilter.Level.ERROR);
75- LogFilter.setLevel("org.apache.fontbox", LogFilter.Level.ERROR);
76-
77- int[] version = Datastore.getApplicationVersion();
78- if(version != null) {
79- if (version[2] == 0) {
80- APPLICATION_VERSION = String.format("%d.%d", version[0], version[1]);
81- } else {
82- APPLICATION_VERSION = String.format("%d.%d.%d", version[0], version[1], version[2]);
83- }
84- } else {
85- APPLICATION_VERSION = "";
86- }
87- }
88-
89- public static void main(String[] args) {
90- launch(args);
91- }
92-
93- @Override
94- public void start(Stage primaryStage) throws Exception {
95- primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/img/app-icon-48px.png")));
96- primaryStage.titleProperty().bind(new StringBinding() {
97- {
98- bind(inputFileProperty);
99- }
100- @Override
101- protected String computeValue() {
102- try {
103- return (inputFileProperty.get() != null ? inputFileProperty.get().getCanonicalPath() + " - " : "")
104- + APPLICATION_NAME + " " + APPLICATION_VERSION;
105- } catch(IOException e) {
106- throw new UncheckedIOException(e);
107- }
108- }
109- });
110-
111- Parent root = Fxml.load(this);
112-
113- Scene scene = new Scene(root);
114- scene.setOnDragOver(wrap(this::scene_onDragOver));
115- scene.setOnDragDropped(wrap(this::scene_onDragDropped));
116- scene.getAccelerators().putAll(pager.createDefaultAccelerators());
117-
118- StageUtil.setRestorable(primaryStage, Preferences.userNodeForPackage(getClass()));
119- primaryStage.setOnShown(event -> { Platform.runLater(wrap(this::stage_onReady)); });
120- primaryStage.setOnCloseRequest(wrap(this::stage_onCloseRequest));
121- primaryStage.setMinWidth(448.0);
122- primaryStage.setMinHeight(396.0);
123- primaryStage.setOpacity(0.0);
124- primaryStage.setScene(scene);
125- primaryStage.show();
126-
127- Thread.currentThread().setUncaughtExceptionHandler(handler);
128- }
129-
130- protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
131- @Override
132- public void uncaughtException(Thread t, Throwable e) {
133- showException(e);
134- }
135- };
136-
137- protected void showException(Throwable exception) {
138- exception.printStackTrace();
139-
140- Runnable r = ()-> {
141- String title;
142- if(exception instanceof JpkiException) {
143- title = "エラー";
144- } else {
145- title = exception.getClass().getName();
146- }
147- String message = exception.getLocalizedMessage();
148- if(message != null) {
149- message = message.trim();
150- }
151- toast.show(Toast.RED, title, message, null);
152- };
153- if(Platform.isFxApplicationThread()) {
154- r.run();
155- } else {
156- Platform.runLater(r);
157- }
158- }
159-
160- @FXML Toast toast;
161- @FXML MenuItem menuFileOpen;
162- @FXML MenuItem menuFileSave;
163- @FXML MenuItem menuFileExit;
164- @FXML MenuItem menuHelpAbout;
165- @FXML Pager pager;
166- @FXML PdfView pdfView;
167- @FXML ImageView ivCursor;
168- @FXML ProgressIndicator piSign;
169- @FXML Button btnRemoveSignature;
170- @FXML Button btnEditSignature;
171- @FXML Button btnAddSignature;
172- @FXML ListView<Signature> lvSignature;
173- ObjectBinding<Signature> signatureBinding;
174- ObjectProperty<File> inputFileProperty = new SimpleObjectProperty<File>();
175- ObjectProperty<File> signedTemporaryFileProperty = new SimpleObjectProperty<File>();
176- BooleanProperty busyProperty = new SimpleBooleanProperty();
177-
178- @Override
179- public void initialize(URL location, ResourceBundle resources) {
180- lvSignature.setCellFactory(new SignatureListCell.Factory(this));
181-
182- //
183- // event handlers
184- //
185- menuFileOpen.setOnAction(wrap(this::menuFileOpen_onAction));
186- menuFileSave.setOnAction(wrap(this::menuFileSave_onAction));
187- menuFileExit.setOnAction(wrap(this::menuFileExit_onAction));
188- menuHelpAbout.setOnAction(wrap(this::menuHelpAbout_onAction));
189- pdfView.setOnMouseMoved(wrap(this::pdfView_onMouseMoved));
190- pdfView.setOnMouseClicked(wrap(this::pdfView_onMouseClicked));
191- btnRemoveSignature.setOnAction(wrap(this::btnRemoveSignature_onAction));
192- btnEditSignature.setOnAction(wrap(this::btnEditSignature_onAction));
193- btnAddSignature.setOnAction(wrap(this::btnAddSignature_onAction));
194-
195- //
196- // bindings
197- //
198- signatureBinding = Bindings
199- .when(lvSignature.getSelectionModel().selectedItemProperty().isNotNull())
200- .then(lvSignature.getSelectionModel().selectedItemProperty())
201- .otherwise(Signature.EMPTY);
202-
203- menuFileSave.disableProperty().bind(signedTemporaryFileProperty.isNull());
204-
205- pager.maxPageIndexProperty().bind(pdfView.maxPageIndexProperty());
206- pager.pageIndexProperty().bindBidirectional(pdfView.pageIndexProperty());
207-
208- pdfView.cursorProperty().bind(Bindings
209- .when(pdfView.documentProperty().isNotNull()
210- .and(ivCursor.visibleProperty())
211- .and(ivCursor.imageProperty().isNotNull()))
212- .then(Cursor.NONE)
213- .otherwise(Cursor.DEFAULT));
214-
215- ivCursor.imageProperty().bind(Bindings.select(signatureBinding, "image"));
216- ivCursor.scaleXProperty().bind(
217- pdfView.renderScaleProperty().multiply(Bindings.selectDouble(signatureBinding, "imageScaleX")));
218- ivCursor.scaleYProperty().bind(
219- pdfView.renderScaleProperty().multiply(Bindings.selectDouble(signatureBinding, "imageScaleY")));
220- ivCursor.visibleProperty().bind(Bindings
221- .selectBoolean(signatureBinding, "visible")
222- .and(pdfView.hoverProperty()));
223-
224- btnRemoveSignature.disableProperty().bind(
225- Bindings.not(Bindings.selectBoolean(signatureBinding, "visible")));
226- btnEditSignature.disableProperty().bind(
227- Bindings.not(Bindings.selectBoolean(signatureBinding, "visible")));
228-
229- piSign.visibleProperty().bind(busyProperty);
230-
231- toast.maxWidthProperty().bind(getPrimaryStage().widthProperty().subtract(32));
232- toast.maxHeightProperty().bind(getPrimaryStage().heightProperty().subtract(32));
233- }
234-
235- void stage_onReady() {
236- getPrimaryStage().setOpacity(1.0);
237-
238- lvSignature.getItems().clear();
239- lvSignature.getItems().add(Signature.INVISIBLE);
240- Async.execute(() -> {
241- return Datastore.loadSignatures();
242- }).onSucceeded(signatures -> {
243- for (Signature signature : signatures) {
244- lvSignature.getItems().add(signature);
245- }
246- Platform.runLater(() -> {
247- checkJpkiAvailability();
248- });
249- }).onFailed(exception -> {
250- showException(exception);
251- });
252- }
253-
254- void stage_onCloseRequest(WindowEvent event) {
255- Platform.exit();
256-
257- // UWPアプリとして実行していてアプリを終了せずにアンインストール操作をすると、onCloseRequestは呼ばれるものの、
258- // なぜかプロセスが約30秒残留してしまいアンインストールに時間がかかります。
259- // この問題を回避するために、UWPとして実行されている場合は
260- // 500ミリ秒後に System.exit(0); を呼び出して強制的にプロセスを終了させます。
261- // アプリケーションディレクトリに "AppxManifest.xml" があるかどうかで、UWPアプリ実行かどうか判定します。
262- if (Datastore.isRunningAsUWP()) {
263- new Thread() {
264- {
265- setDaemon(true);
266- }
267- @Override
268- public void run() {
269- try { Thread.sleep(500); } catch(InterruptedException ignore) {}
270- System.exit(0);
271- }
272- }.start();
273- }
274- }
275-
276- void scene_onDragOver(DragEvent event) {
277- if(isAcceptable(getFile(event))) {
278- event.acceptTransferModes(TransferMode.COPY);
279- } else {
280- event.consume();
281- }
282- }
283-
284- void scene_onDragDropped(DragEvent event) {
285- File file = getFile(event);
286- if(isAcceptable(file)) {
287- getPrimaryStage().toFront();
288- toast.hide();
289- signedTemporaryFileProperty.set(null);
290- inputFileProperty.set(file);
291- pdfView.load(file);
292- event.setDropCompleted(true);
293- }
294- event.consume();
295- }
296-
297- void menuFileOpen_onAction(ActionEvent event) {
298- toast.hide();
299- Preferences preferences = Preferences.userNodeForPackage(getClass());
300-
301- FileChooser fc = new FileChooser();
302- fc.setTitle("開く");
303- fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "*.pdf"));
304-
305- String lastOpenDirectory = preferences.get("lastOpenDirectory", null);
306- if(lastOpenDirectory != null) {
307- File dir = new File(lastOpenDirectory);
308- if(dir.isDirectory()) {
309- fc.setInitialDirectory(dir);
310- }
311- }
312- File file = fc.showOpenDialog(getPrimaryStage());
313- if(file != null) {
314- preferences.put("lastOpenDirectory", file.getParentFile().getAbsolutePath());
315- if(isAcceptable(file)) {
316- signedTemporaryFileProperty.set(null);
317- inputFileProperty.set(file);
318- pdfView.load(file);
319- }
320- }
321- }
322-
323- void menuFileSave_onAction(ActionEvent event) throws IOException {
324- toast.hide();
325- String defaultName = inputFileProperty.get().getName();
326- int i = defaultName.lastIndexOf('.');
327- if(i > 0) {
328- defaultName = defaultName.substring(0, i);
329- }
330- defaultName += "-signed.pdf";
331-
332- FileChooser fc = new FileChooser();
333- fc.setTitle("名前を付けて保存");
334- fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "*.pdf"));
335- fc.setInitialDirectory(inputFileProperty.get().getParentFile());
336- fc.setInitialFileName(defaultName);
337-
338- File file = fc.showSaveDialog(getPrimaryStage());
339- if(file != null) {
340- Files.copy(signedTemporaryFileProperty.get().toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
341- toast.show(Toast.GREEN, "保存しました", file.getPath(), Toast.LONG);
342- }
343- }
344-
345- void menuFileExit_onAction(ActionEvent event) {
346- Platform.exit();
347- }
348-
349- void menuHelpAbout_onAction(ActionEvent event) throws IOException {
350- String license = Datastore.getLicense();
351- LicenseDialog dialog = new LicenseDialog(getPrimaryStage(), APPLICATION_NAME + " " + APPLICATION_VERSION, license);
352- dialog.showAndWait();
353- }
354-
355- void btnAddSignature_onAction(ActionEvent event) throws IOException {
356- toast.hide();
357- SignatureDialog dialog = new SignatureDialog(getPrimaryStage(), null);
358- Signature newSignature = dialog.showAndWait().orElse(null);
359- if(newSignature == null) {
360- return;
361- }
362- lvSignature.getItems().add(newSignature);
363- Datastore.saveSignatures(lvSignature.getItems().subList(0, lvSignature.getItems().size()));
364- }
365-
366- void btnEditSignature_onAction(ActionEvent event) throws IOException {
367- toast.hide();
368- Signature currentSignature = lvSignature.getSelectionModel().getSelectedItem();
369- if(currentSignature == null || currentSignature.getImage() == null) {
370- return;
371- }
372- SignatureDialog dialog = new SignatureDialog(getPrimaryStage(), currentSignature);
373- Signature newSignature = dialog.showAndWait().orElse(null);
374- if(newSignature == null) {
375- return;
376- }
377- lvSignature.getItems().add(lvSignature.getSelectionModel().getSelectedIndex(), newSignature);
378- lvSignature.getItems().remove(lvSignature.getSelectionModel().getSelectedIndex());
379- Datastore.saveSignatures(lvSignature.getItems().subList(0, lvSignature.getItems().size()));
380- }
381-
382- void btnRemoveSignature_onAction(ActionEvent event) throws IOException {
383- toast.hide();
384- Signature currentSignature = lvSignature.getSelectionModel().getSelectedItem();
385- if(currentSignature == null || currentSignature.getImage() == null) {
386- return;
387- }
388- ButtonType result = Dialogs.showConfirmation(getPrimaryStage(),
389- "印影の削除",
390- currentSignature.getTitle() + " を削除しますか?");
391- if(result != ButtonType.YES) {
392- return;
393- }
394-
395- lvSignature.getItems().remove(lvSignature.getSelectionModel().getSelectedIndex());
396- lvSignature.getSelectionModel().clearSelection();
397- Datastore.saveSignatures(lvSignature.getItems().subList(0, lvSignature.getItems().size()));
398- }
399-
400- public void lvSignature_cell_onMousePressed(MouseEvent event) throws JpkiException, IOException, ReflectiveOperationException {
401- toast.hide();
402- @SuppressWarnings("unchecked")
403- ListCell<Signature> cell = (ListCell<Signature>)event.getSource();
404-
405- //空のセルをクリックしたときにリストビューの選択を解除します。
406- if(cell.isEmpty()) {
407- lvSignature.getSelectionModel().clearSelection();
408- return;
409- }
410-
411- Signature signature = cell.getItem();
412- if(signature != Signature.INVISIBLE) {
413- return;
414- }
415-
416- try {
417- if(!event.isPrimaryButtonDown()) {
418- return;
419- } else if(pdfView.getDocument() == null) {
420- toast.show(Toast.GREEN,
421- "はじめに",
422- "PDFファイルをこのウィンドウにドラッグ&ドロップして表示しましょう。");
423- } else if(checkJpkiAvailability()) {
424- ButtonType result = Dialogs.showConfirmation(getPrimaryStage(),
425- APPLICATION_NAME + " " + APPLICATION_VERSION,
426- "印影を使わずに電子署名しますか?");
427- if(result == ButtonType.YES) {
428- PDDocument document = pdfView.getDocument();
429- int pageIndex = pdfView.getPageIndex();
430- SignatureOptions options = null;
431-
432- busyProperty.set(true);
433- Async.execute(() -> sign(document, null, APPLICATION_NAME, APPLICATION_VERSION))
434- .onSucceeded(tmpFile -> {
435- if(tmpFile != null) {
436- signedTemporaryFileProperty.set(tmpFile);
437- pdfView.load(tmpFile, pageIndex);
438- busyProperty.set(false);
439-
440- if(ButtonType.YES == Dialogs.showConfirmation(getPrimaryStage(),
441- APPLICATION_NAME + " " + APPLICATION_VERSION,
442- "署名が完了しました。\nファイルに名前を付けて保存しますか?")) {
443- menuFileSave.fire();
444- }
445- }
446- })
447- .onCompleted(state -> busyProperty.set(false));
448- }
449- }
450- } finally {
451- lvSignature.getSelectionModel().clearSelection();
452- }
453- }
454-
455- public void lvSignature_cell_onMouseClicked(MouseEvent event) {
456- // 左ダブルクリックでない場合は何もしない。
457- if(event.getButton() != MouseButton.PRIMARY || event.getClickCount() != 2) {
458- return;
459- }
460-
461- @SuppressWarnings("unchecked")
462- ListCell<Signature> cell = (ListCell<Signature>)event.getSource();
463-
464- // 不可視署名では何もしない。
465- Signature signature = cell.getItem();
466- if(signature == Signature.INVISIBLE) {
467- return;
468- }
469-
470- if(cell.isEmpty()) {
471- // 空のセルがダブルクリックされた場合は「新規」操作を発動します。
472- btnAddSignature.fire();
473- } else {
474- // 可視署名がダブルクリックされた場合は「編集」操作を発動します。
475- btnEditSignature.fire();
476- }
477- }
478-
479- void pdfView_onMouseMoved(MouseEvent event) {
480- Image image = ivCursor.getImage();
481- if(image != null) {
482- ivCursor.setLayoutX(event.getX() - (int)(image.getWidth() / 2.0));
483- ivCursor.setLayoutY(event.getY() - (int)(image.getHeight() / 2.0));
484- }
485- }
486-
487- void pdfView_onMouseClicked(MouseEvent event) throws JpkiException, IOException, ReflectiveOperationException {
488- toast.hide();
489-
490- // 必要な条件を満たしている場合、可視署名を実行します。
491-
492- if(event.getButton() != MouseButton.PRIMARY) {
493- return;
494- }
495-
496- Rectangle2D renderBounds = pdfView.getRenderBounds();
497- double x = event.getX() - renderBounds.getMinX();
498- double y = event.getY() - renderBounds.getMinY();
499- if(x < 0.0 || y < 0.0 || x > renderBounds.getWidth() || y > renderBounds.getHeight()) {
500- return;
501- }
502-
503- Signature signature = signatureBinding.get();
504- if(!signature.isVisible()) {
505- return;
506- }
507-
508- if(!checkJpkiAvailability()) {
509- return;
510- }
511-
512- PDDocument document = pdfView.getDocument();
513- int pageIndex = pdfView.getPageIndex();
514- PDRectangle pageMediaBox = document.getPage(pageIndex).getMediaBox();
515- double xPt = x * pageMediaBox.getWidth() / renderBounds.getWidth();
516- double yPt = y * pageMediaBox.getHeight() / renderBounds.getHeight();
517-
518- PDVisibleSignDesigner designer;
519- try(InputStream is = new FileInputStream(signature.getFile())) {
520- designer = new PDVisibleSignDesigner(is);
521- designer.width((float)mm2px(signature.getWidthMillis()));
522- designer.height((float)mm2px(signature.getHeightMillis()));
523- designer.xAxis((float)xPt - designer.getWidth() / 2);
524- designer.yAxis((float)yPt - designer.getHeight() / 2 - pageMediaBox.getHeight());
525- }
526- PDVisibleSigProperties props = new PDVisibleSigProperties();
527- props.setPdVisibleSignature(designer);
528- props.visualSignEnabled(true);
529- props.page(pageIndex + 1);
530- props.buildSignature();
531-
532- SignatureOptions options = new SignatureOptions();
533- options.setPage(pageIndex);
534- options.setVisualSignature(props);
535-
536- lvSignature.getSelectionModel().clearSelection();
537- busyProperty.set(true);
538- Async.execute(() -> sign(document, options, APPLICATION_NAME, APPLICATION_VERSION))
539- .onSucceeded(tmpFile -> {
540- if(tmpFile != null) {
541- signedTemporaryFileProperty.set(tmpFile);
542- pdfView.load(tmpFile, pageIndex);
543- busyProperty.set(false);
544-
545- if(ButtonType.YES == Dialogs.showConfirmation(getPrimaryStage(), APPLICATION_NAME + " " + APPLICATION_VERSION,
546- "署名が完了しました。\nファイルに名前を付けて保存しますか?")) {
547- menuFileSave.fire();
548- }
549- lvSignature.getSelectionModel().clearSelection();
550- }
551- })
552- .onCompleted(state -> busyProperty.set(false));
553- }
554-
555- protected File getFile(DragEvent event) {
556- if(event.getDragboard().hasFiles()) {
557- List<File> files = event.getDragboard().getFiles();
558- if(files.size() == 1) {
559- return files.get(0);
560- }
561- }
562- return null;
563- }
564-
565- protected boolean isAcceptable(File file) {
566- return file != null && file.getName().matches("(?i).+(\\.pdf)");
567- }
568-
569- protected boolean checkJpkiAvailability() {
570- boolean isAvailable = JpkiWrapper.isAvailable();
571- if(!isAvailable) {
572- // JPKI 利用者クライアントソフトがインストールされていない場合、
573- // インストールを促すために、クリックで公的個人認証サービスのウェブサイトが開くようにします。
574- // ただし、Microsoft Storeアプリではストア以外でのアプリインストールを促すことが禁止されているため
575- // UWPとして実行されている場合にはウェブサイトを開く機能を提供せずメッセージ表示のみに留めています。
576- if(Datastore.isRunningAsUWP()) {
577- toast.show(Toast.GREEN, "構成", "JPKI 利用者クライアントソフトが見つかりません。");
578- } else {
579- Runnable actionOnClick = wrap(() -> {
580- toast.hide();
581- Desktop.getDesktop().browse(URI.create("https://www.jpki.go.jp/download/win.html"));
582- });
583- toast.show(Toast.GREEN, "事前準備", ""
584- + "JPKI 利用者クライアントソフトをインストールしてください。\n"
585- + "ここをクリックするとブラウザーでダウンロードサイトを開きます。",
586- null,
587- actionOnClick);
588- }
589- }
590- return isAvailable;
591- }
592-
593- protected File sign(PDDocument document, SignatureOptions options, String applicationName, String applicationVersion) throws JpkiException, IOException, ReflectiveOperationException {
594- File tmpFile = Datastore.getMyDataDirectory(true).resolve("output.tmp").toFile();
595- try (OutputStream output = new FileOutputStream(tmpFile)) {
596- output.flush();
597- JpkiWrapper jpki = new JpkiWrapper();
598- jpki.setApplicationName(applicationName);
599- jpki.setApplicationVersion(applicationVersion);
600- jpki.addSignature(output, document, options);
601- return tmpFile;
602- } catch(JpkiException e) {
603- //ユーザーがキャンセル操作をした場合はダイアログを表示しません。
604- if(e.getWinErrorCode() != JpkiException.SCARD_W_CANCELLED_BY_USER) {
605- throw e;
606- }
607- }
608- return null;
609- }
610-
611- public static double mm2px(double mm) {
612- return mm * 72.0 / 25.4;
613- }
8+ public static void main(String[] args) {
9+ Platform.setImplicitExit(false);
10+ SingletonApplication.launch(MainApp.class, args);
11+ }
61412 }
--- jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/MainApp.css (nonexistent)
+++ jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/MainApp.css (revision 57)
@@ -0,0 +1,108 @@
1+.root {
2+ -fx-font-family: "Meiryo";
3+ -fx-font-size: 12px;
4+}
5+
6+FontIcon {
7+ -fx-icon-size: 17;
8+ -fx-icon-color: -fx-selection-bar;
9+}
10+.menu-item:focused FontIcon {
11+ -fx-icon-color: -fx-selection-bar-text;
12+}
13+
14+.list-view {
15+ -fx-padding: 0;
16+ -fx-background-insets: 0;
17+}
18+.list-cell {
19+ -fx-padding: 0px;
20+}
21+.list-cell:selected {
22+ -fx-background-color: -fx-accent;
23+}
24+.list-cell:selected .label {
25+ -fx-text-fill: white;
26+}
27+
28+#buttonBox {
29+ -fx-background-color: #303030;
30+}
31+#buttonBox > .label {
32+ -fx-alignment: center;
33+ -fx-min-width: 80px;
34+ -fx-font-size: 14px;
35+ -fx-text-fill: #F2F2F2;
36+}
37+#buttonBox .button {
38+ -fx-padding: 0 10px 0 7px;
39+ -fx-min-height: 25px;
40+ -fx-pref-height: 25px;
41+ -fx-min-width: 40px;
42+ -fx-background-insets: 0;
43+ -fx-background-radius: 0;
44+ -fx-background-color: transparent;
45+ -fx-text-fill: #CCCCCC;
46+ -fx-skin: "net.osdn.util.javafx.scene.control.pdf.TransitionButtonSkin";
47+}
48+#buttonBox .button:focused {
49+ -fx-background-color: rgba(255, 255, 255, 0.2);
50+}
51+#buttonBox .button:hover {
52+ -fx-background-color: rgba(255, 255, 255, 0.2);
53+ -fx-text-fill: #FFFFFF;
54+}
55+#buttonBox .button:pressed {
56+ -fx-background-color: rgba(255, 255, 255, 0.1);
57+ -fx-text-fill: #CCCCCC;
58+}
59+#buttonBox FontIcon {
60+ -fx-icon-size: 17;
61+ -fx-icon-color: #CCCCCC;
62+}
63+#buttonBox .button:hover FontIcon {
64+ -fx-icon-color: #FFFFFF;
65+}
66+
67+.scroll-bar:horizontal{
68+ -fx-background-color: #F1F1F1;
69+ -fx-background-insets: 0;
70+ -fx-padding: 1 0 1 0;
71+}
72+.scroll-bar:vertical{
73+ -fx-background-color: #F1F1F1;
74+ -fx-background-insets: 0;
75+ -fx-padding: 0 1 0 1;
76+}
77+.scroll-bar:horizontal .track ,
78+.scroll-bar:vertical .track{
79+ -fx-background-color: #F1F1F1;
80+}
81+.scroll-bar:horizontal .thumb,
82+.scroll-bar:vertical .thumb {
83+ -fx-background-color: #C1C1C1;
84+ -fx-background-insets: 0, 0, 0;
85+ -fx-background-radius: 0;
86+}
87+.scroll-bar:horizontal .thumb:hover,
88+.scroll-bar:vertical .thumb:hover {
89+ -fx-background-color: #A8A8A8;
90+}
91+.scroll-bar:horizontal .thumb:pressed,
92+.scroll-bar:vertical .thumb:pressed {
93+ -fx-background-color: #787878;
94+}
95+.scroll-bar:vertical .increment-button,
96+.scroll-bar:vertical .decrement-button,
97+.scroll-bar:horizontal .increment-button,
98+.scroll-bar:horizontal .decrement-button {
99+ -fx-padding: 0px;
100+}
101+.scroll-bar:vertical .increment-arrow,
102+.scroll-bar:vertical .decrement-arrow {
103+ -fx-padding: 0 5 0 5;
104+}
105+.scroll-bar:horizontal .increment-arrow,
106+.scroll-bar:horizontal .decrement-arrow {
107+ -fx-padding: 5 0 5 0;
108+}
--- jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/MainApp.java (nonexistent)
+++ jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/MainApp.java (revision 57)
@@ -0,0 +1,592 @@
1+package net.osdn.jpki.pdf_signer;
2+
3+import javafx.application.Platform;
4+import javafx.beans.binding.Bindings;
5+import javafx.beans.binding.ObjectBinding;
6+import javafx.beans.binding.StringBinding;
7+import javafx.beans.property.BooleanProperty;
8+import javafx.beans.property.ObjectProperty;
9+import javafx.beans.property.SimpleBooleanProperty;
10+import javafx.beans.property.SimpleObjectProperty;
11+import javafx.event.ActionEvent;
12+import javafx.fxml.FXML;
13+import javafx.fxml.Initializable;
14+import javafx.geometry.Rectangle2D;
15+import javafx.scene.Cursor;
16+import javafx.scene.Parent;
17+import javafx.scene.Scene;
18+import javafx.scene.control.Button;
19+import javafx.scene.control.ButtonType;
20+import javafx.scene.control.ListCell;
21+import javafx.scene.control.ListView;
22+import javafx.scene.control.MenuItem;
23+import javafx.scene.control.ProgressIndicator;
24+import javafx.scene.image.Image;
25+import javafx.scene.image.ImageView;
26+import javafx.scene.input.DragEvent;
27+import javafx.scene.input.MouseButton;
28+import javafx.scene.input.MouseEvent;
29+import javafx.scene.input.TransferMode;
30+import javafx.stage.FileChooser;
31+import javafx.stage.Stage;
32+import net.osdn.jpki.pdf_signer.control.LicenseDialog;
33+import net.osdn.jpki.pdf_signer.control.Toast;
34+import net.osdn.jpki.wrapper.JpkiException;
35+import net.osdn.jpki.wrapper.JpkiWrapper;
36+import net.osdn.util.javafx.application.SingletonApplication;
37+import net.osdn.util.javafx.concurrent.Async;
38+import net.osdn.util.javafx.fxml.Fxml;
39+import net.osdn.util.javafx.scene.control.Dialogs;
40+import net.osdn.util.javafx.scene.control.pdf.Pager;
41+import net.osdn.util.javafx.scene.control.pdf.PdfView;
42+import net.osdn.util.javafx.stage.StageUtil;
43+import org.apache.pdfbox.pdmodel.PDDocument;
44+import org.apache.pdfbox.pdmodel.common.PDRectangle;
45+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
46+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties;
47+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner;
48+
49+import java.awt.Desktop;
50+import java.io.File;
51+import java.io.FileInputStream;
52+import java.io.FileOutputStream;
53+import java.io.IOException;
54+import java.io.InputStream;
55+import java.io.OutputStream;
56+import java.io.UncheckedIOException;
57+import java.net.URI;
58+import java.net.URL;
59+import java.nio.file.Files;
60+import java.nio.file.StandardCopyOption;
61+import java.util.List;
62+import java.util.ResourceBundle;
63+import java.util.prefs.Preferences;
64+
65+public class MainApp extends SingletonApplication implements Initializable {
66+
67+ public static final String APPLICATION_NAME = "JPKI PDF SIGNER";
68+ public static final String APPLICATION_VERSION;
69+
70+ static {
71+ System.setProperty(
72+ "org.apache.commons.logging.LogFactory", "net.osdn.jpki.pdf_signer.LogFilter");
73+ LogFilter.setLevel("org.apache.pdfbox", LogFilter.Level.ERROR);
74+ LogFilter.setLevel("org.apache.fontbox", LogFilter.Level.ERROR);
75+
76+ int[] version = Datastore.getApplicationVersion();
77+ if(version != null) {
78+ if (version[2] == 0) {
79+ APPLICATION_VERSION = String.format("%d.%d", version[0], version[1]);
80+ } else {
81+ APPLICATION_VERSION = String.format("%d.%d.%d", version[0], version[1], version[2]);
82+ }
83+ } else {
84+ APPLICATION_VERSION = "";
85+ }
86+ }
87+
88+ @Override
89+ public void start(Stage primaryStage) throws Exception {
90+ primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/img/app-icon-48px.png")));
91+ primaryStage.titleProperty().bind(new StringBinding() {
92+ {
93+ bind(inputFileProperty);
94+ }
95+ @Override
96+ protected String computeValue() {
97+ try {
98+ return (inputFileProperty.get() != null ? inputFileProperty.get().getCanonicalPath() + " - " : "")
99+ + APPLICATION_NAME + " " + APPLICATION_VERSION;
100+ } catch(IOException e) {
101+ throw new UncheckedIOException(e);
102+ }
103+ }
104+ });
105+
106+ primaryStage.showingProperty().addListener((observable, oldValue, newValue) -> {
107+ if(oldValue == true && newValue == false) {
108+ Platform.exit();
109+ }
110+ });
111+
112+ Parent root = Fxml.load(this);
113+
114+ Scene scene = new Scene(root);
115+ scene.setOnDragOver(wrap(this::scene_onDragOver));
116+ scene.setOnDragDropped(wrap(this::scene_onDragDropped));
117+ scene.getAccelerators().putAll(pager.createDefaultAccelerators());
118+
119+ StageUtil.setRestorable(primaryStage, Preferences.userNodeForPackage(getClass()));
120+ primaryStage.setOnShown(event -> { Platform.runLater(wrap(this::stage_onReady)); });
121+ primaryStage.setMinWidth(448.0);
122+ primaryStage.setMinHeight(396.0);
123+ primaryStage.setOpacity(0.0);
124+ primaryStage.setScene(scene);
125+ primaryStage.show();
126+
127+ Thread.currentThread().setUncaughtExceptionHandler(handler);
128+ }
129+
130+ protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
131+ @Override
132+ public void uncaughtException(Thread t, Throwable e) {
133+ showException(e);
134+ }
135+ };
136+
137+ protected void showException(Throwable exception) {
138+ exception.printStackTrace();
139+
140+ Runnable r = ()-> {
141+ String title;
142+ if(exception instanceof JpkiException) {
143+ title = "エラー";
144+ } else {
145+ title = exception.getClass().getName();
146+ }
147+ String message = exception.getLocalizedMessage();
148+ if(message != null) {
149+ message = message.trim();
150+ }
151+ toast.show(Toast.RED, title, message, null);
152+ };
153+ if(Platform.isFxApplicationThread()) {
154+ r.run();
155+ } else {
156+ Platform.runLater(r);
157+ }
158+ }
159+
160+ @FXML Toast toast;
161+ @FXML MenuItem menuFileOpen;
162+ @FXML MenuItem menuFileSave;
163+ @FXML MenuItem menuFileExit;
164+ @FXML MenuItem menuHelpAbout;
165+ @FXML Pager pager;
166+ @FXML PdfView pdfView;
167+ @FXML ImageView ivCursor;
168+ @FXML ProgressIndicator piSign;
169+ @FXML Button btnRemoveSignature;
170+ @FXML Button btnEditSignature;
171+ @FXML Button btnAddSignature;
172+ @FXML ListView<Signature> lvSignature;
173+ ObjectBinding<Signature> signatureBinding;
174+ ObjectProperty<File> inputFileProperty = new SimpleObjectProperty<File>();
175+ ObjectProperty<File> signedTemporaryFileProperty = new SimpleObjectProperty<File>();
176+ BooleanProperty busyProperty = new SimpleBooleanProperty();
177+
178+ @Override
179+ public void initialize(URL location, ResourceBundle resources) {
180+ lvSignature.setCellFactory(new SignatureListCell.Factory(this));
181+
182+ //
183+ // event handlers
184+ //
185+ menuFileOpen.setOnAction(wrap(this::menuFileOpen_onAction));
186+ menuFileSave.setOnAction(wrap(this::menuFileSave_onAction));
187+ menuFileExit.setOnAction(wrap(this::menuFileExit_onAction));
188+ menuHelpAbout.setOnAction(wrap(this::menuHelpAbout_onAction));
189+ pdfView.setOnMouseMoved(wrap(this::pdfView_onMouseMoved));
190+ pdfView.setOnMouseClicked(wrap(this::pdfView_onMouseClicked));
191+ btnRemoveSignature.setOnAction(wrap(this::btnRemoveSignature_onAction));
192+ btnEditSignature.setOnAction(wrap(this::btnEditSignature_onAction));
193+ btnAddSignature.setOnAction(wrap(this::btnAddSignature_onAction));
194+
195+ //
196+ // bindings
197+ //
198+ signatureBinding = Bindings
199+ .when(lvSignature.getSelectionModel().selectedItemProperty().isNotNull())
200+ .then(lvSignature.getSelectionModel().selectedItemProperty())
201+ .otherwise(Signature.EMPTY);
202+
203+ menuFileSave.disableProperty().bind(signedTemporaryFileProperty.isNull());
204+
205+ pager.maxPageIndexProperty().bind(pdfView.maxPageIndexProperty());
206+ pager.pageIndexProperty().bindBidirectional(pdfView.pageIndexProperty());
207+
208+ pdfView.cursorProperty().bind(Bindings
209+ .when(pdfView.documentProperty().isNotNull()
210+ .and(ivCursor.visibleProperty())
211+ .and(ivCursor.imageProperty().isNotNull()))
212+ .then(Cursor.NONE)
213+ .otherwise(Cursor.DEFAULT));
214+
215+ ivCursor.imageProperty().bind(Bindings.select(signatureBinding, "image"));
216+ ivCursor.scaleXProperty().bind(
217+ pdfView.renderScaleProperty().multiply(Bindings.selectDouble(signatureBinding, "imageScaleX")));
218+ ivCursor.scaleYProperty().bind(
219+ pdfView.renderScaleProperty().multiply(Bindings.selectDouble(signatureBinding, "imageScaleY")));
220+ ivCursor.visibleProperty().bind(Bindings
221+ .selectBoolean(signatureBinding, "visible")
222+ .and(pdfView.hoverProperty()));
223+
224+ btnRemoveSignature.disableProperty().bind(
225+ Bindings.not(Bindings.selectBoolean(signatureBinding, "visible")));
226+ btnEditSignature.disableProperty().bind(
227+ Bindings.not(Bindings.selectBoolean(signatureBinding, "visible")));
228+
229+ piSign.visibleProperty().bind(busyProperty);
230+
231+ toast.maxWidthProperty().bind(getPrimaryStage().widthProperty().subtract(32));
232+ toast.maxHeightProperty().bind(getPrimaryStage().heightProperty().subtract(32));
233+ }
234+
235+ void stage_onReady() {
236+ getPrimaryStage().setOpacity(1.0);
237+
238+ lvSignature.getItems().clear();
239+ lvSignature.getItems().add(Signature.INVISIBLE);
240+ Async.execute(() -> {
241+ return Datastore.loadSignatures();
242+ }).onSucceeded(signatures -> {
243+ for (Signature signature : signatures) {
244+ lvSignature.getItems().add(signature);
245+ }
246+ Platform.runLater(() -> {
247+ checkJpkiAvailability();
248+ });
249+ }).onFailed(exception -> {
250+ showException(exception);
251+ });
252+ }
253+
254+ void scene_onDragOver(DragEvent event) {
255+ if(isAcceptable(getFile(event))) {
256+ event.acceptTransferModes(TransferMode.COPY);
257+ } else {
258+ event.consume();
259+ }
260+ }
261+
262+ void scene_onDragDropped(DragEvent event) {
263+ File file = getFile(event);
264+ if(isAcceptable(file)) {
265+ getPrimaryStage().toFront();
266+ toast.hide();
267+ signedTemporaryFileProperty.set(null);
268+ inputFileProperty.set(file);
269+ pdfView.load(file);
270+ event.setDropCompleted(true);
271+ }
272+ event.consume();
273+ }
274+
275+ void menuFileOpen_onAction(ActionEvent event) {
276+ toast.hide();
277+ Preferences preferences = Preferences.userNodeForPackage(getClass());
278+
279+ FileChooser fc = new FileChooser();
280+ fc.setTitle("開く");
281+ fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "*.pdf"));
282+
283+ String lastOpenDirectory = preferences.get("lastOpenDirectory", null);
284+ if(lastOpenDirectory != null) {
285+ File dir = new File(lastOpenDirectory);
286+ if(dir.isDirectory()) {
287+ fc.setInitialDirectory(dir);
288+ }
289+ }
290+ File file = fc.showOpenDialog(getPrimaryStage());
291+ if(file != null) {
292+ preferences.put("lastOpenDirectory", file.getParentFile().getAbsolutePath());
293+ if(isAcceptable(file)) {
294+ signedTemporaryFileProperty.set(null);
295+ inputFileProperty.set(file);
296+ pdfView.load(file);
297+ }
298+ }
299+ }
300+
301+ void menuFileSave_onAction(ActionEvent event) throws IOException {
302+ toast.hide();
303+ String defaultName = inputFileProperty.get().getName();
304+ int i = defaultName.lastIndexOf('.');
305+ if(i > 0) {
306+ defaultName = defaultName.substring(0, i);
307+ }
308+ defaultName += "-signed.pdf";
309+
310+ FileChooser fc = new FileChooser();
311+ fc.setTitle("名前を付けて保存");
312+ fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF", "*.pdf"));
313+ fc.setInitialDirectory(inputFileProperty.get().getParentFile());
314+ fc.setInitialFileName(defaultName);
315+
316+ File file = fc.showSaveDialog(getPrimaryStage());
317+ if(file != null) {
318+ Files.copy(signedTemporaryFileProperty.get().toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
319+ toast.show(Toast.GREEN, "保存しました", file.getPath(), Toast.LONG);
320+ }
321+ }
322+
323+ void menuFileExit_onAction(ActionEvent event) {
324+ getPrimaryStage().close();
325+ }
326+
327+ void menuHelpAbout_onAction(ActionEvent event) throws IOException {
328+ String license = Datastore.getLicense();
329+ LicenseDialog dialog = new LicenseDialog(getPrimaryStage(), APPLICATION_NAME + " " + APPLICATION_VERSION, license);
330+ dialog.showAndWait();
331+ }
332+
333+ void btnAddSignature_onAction(ActionEvent event) throws IOException {
334+ toast.hide();
335+ SignatureDialog dialog = new SignatureDialog(getPrimaryStage(), null);
336+ Signature newSignature = dialog.showAndWait().orElse(null);
337+ if(newSignature == null) {
338+ return;
339+ }
340+ lvSignature.getItems().add(newSignature);
341+ Datastore.saveSignatures(lvSignature.getItems().subList(0, lvSignature.getItems().size()));
342+ }
343+
344+ void btnEditSignature_onAction(ActionEvent event) throws IOException {
345+ toast.hide();
346+ Signature currentSignature = lvSignature.getSelectionModel().getSelectedItem();
347+ if(currentSignature == null || currentSignature.getImage() == null) {
348+ return;
349+ }
350+ SignatureDialog dialog = new SignatureDialog(getPrimaryStage(), currentSignature);
351+ Signature newSignature = dialog.showAndWait().orElse(null);
352+ if(newSignature == null) {
353+ return;
354+ }
355+ lvSignature.getItems().add(lvSignature.getSelectionModel().getSelectedIndex(), newSignature);
356+ lvSignature.getItems().remove(lvSignature.getSelectionModel().getSelectedIndex());
357+ Datastore.saveSignatures(lvSignature.getItems().subList(0, lvSignature.getItems().size()));
358+ }
359+
360+ void btnRemoveSignature_onAction(ActionEvent event) throws IOException {
361+ toast.hide();
362+ Signature currentSignature = lvSignature.getSelectionModel().getSelectedItem();
363+ if(currentSignature == null || currentSignature.getImage() == null) {
364+ return;
365+ }
366+ ButtonType result = Dialogs.showConfirmation(getPrimaryStage(),
367+ "印影の削除",
368+ currentSignature.getTitle() + " を削除しますか?");
369+ if(result != ButtonType.YES) {
370+ return;
371+ }
372+
373+ lvSignature.getItems().remove(lvSignature.getSelectionModel().getSelectedIndex());
374+ lvSignature.getSelectionModel().clearSelection();
375+ Datastore.saveSignatures(lvSignature.getItems().subList(0, lvSignature.getItems().size()));
376+ }
377+
378+ public void lvSignature_cell_onMousePressed(MouseEvent event) throws JpkiException, IOException, ReflectiveOperationException {
379+ toast.hide();
380+ @SuppressWarnings("unchecked")
381+ ListCell<Signature> cell = (ListCell<Signature>)event.getSource();
382+
383+ //空のセルをクリックしたときにリストビューの選択を解除します。
384+ if(cell.isEmpty()) {
385+ lvSignature.getSelectionModel().clearSelection();
386+ return;
387+ }
388+
389+ Signature signature = cell.getItem();
390+ if(signature != Signature.INVISIBLE) {
391+ return;
392+ }
393+
394+ try {
395+ if(!event.isPrimaryButtonDown()) {
396+ return;
397+ } else if(pdfView.getDocument() == null) {
398+ toast.show(Toast.GREEN,
399+ "はじめに",
400+ "PDFファイルをこのウィンドウにドラッグ&ドロップして表示しましょう。");
401+ } else if(checkJpkiAvailability()) {
402+ ButtonType result = Dialogs.showConfirmation(getPrimaryStage(),
403+ APPLICATION_NAME + " " + APPLICATION_VERSION,
404+ "印影を使わずに電子署名しますか?");
405+ if(result == ButtonType.YES) {
406+ PDDocument document = pdfView.getDocument();
407+ int pageIndex = pdfView.getPageIndex();
408+ SignatureOptions options = null;
409+
410+ busyProperty.set(true);
411+ Async.execute(() -> sign(document, null, APPLICATION_NAME, APPLICATION_VERSION))
412+ .onSucceeded(tmpFile -> {
413+ if(tmpFile != null) {
414+ signedTemporaryFileProperty.set(tmpFile);
415+ pdfView.load(tmpFile, pageIndex);
416+ busyProperty.set(false);
417+
418+ if(ButtonType.YES == Dialogs.showConfirmation(getPrimaryStage(),
419+ APPLICATION_NAME + " " + APPLICATION_VERSION,
420+ "署名が完了しました。\nファイルに名前を付けて保存しますか?")) {
421+ menuFileSave.fire();
422+ }
423+ }
424+ })
425+ .onCompleted(state -> busyProperty.set(false));
426+ }
427+ }
428+ } finally {
429+ lvSignature.getSelectionModel().clearSelection();
430+ }
431+ }
432+
433+ public void lvSignature_cell_onMouseClicked(MouseEvent event) {
434+ // 左ダブルクリックでない場合は何もしない。
435+ if(event.getButton() != MouseButton.PRIMARY || event.getClickCount() != 2) {
436+ return;
437+ }
438+
439+ @SuppressWarnings("unchecked")
440+ ListCell<Signature> cell = (ListCell<Signature>)event.getSource();
441+
442+ // 不可視署名では何もしない。
443+ Signature signature = cell.getItem();
444+ if(signature == Signature.INVISIBLE) {
445+ return;
446+ }
447+
448+ if(cell.isEmpty()) {
449+ // 空のセルがダブルクリックされた場合は「新規」操作を発動します。
450+ btnAddSignature.fire();
451+ } else {
452+ // 可視署名がダブルクリックされた場合は「編集」操作を発動します。
453+ btnEditSignature.fire();
454+ }
455+ }
456+
457+ void pdfView_onMouseMoved(MouseEvent event) {
458+ Image image = ivCursor.getImage();
459+ if(image != null) {
460+ ivCursor.setLayoutX(event.getX() - (int)(image.getWidth() / 2.0));
461+ ivCursor.setLayoutY(event.getY() - (int)(image.getHeight() / 2.0));
462+ }
463+ }
464+
465+ void pdfView_onMouseClicked(MouseEvent event) throws JpkiException, IOException, ReflectiveOperationException {
466+ toast.hide();
467+
468+ // 必要な条件を満たしている場合、可視署名を実行します。
469+
470+ if(event.getButton() != MouseButton.PRIMARY) {
471+ return;
472+ }
473+
474+ Rectangle2D renderBounds = pdfView.getRenderBounds();
475+ double x = event.getX() - renderBounds.getMinX();
476+ double y = event.getY() - renderBounds.getMinY();
477+ if(x < 0.0 || y < 0.0 || x > renderBounds.getWidth() || y > renderBounds.getHeight()) {
478+ return;
479+ }
480+
481+ Signature signature = signatureBinding.get();
482+ if(!signature.isVisible()) {
483+ return;
484+ }
485+
486+ if(!checkJpkiAvailability()) {
487+ return;
488+ }
489+
490+ PDDocument document = pdfView.getDocument();
491+ int pageIndex = pdfView.getPageIndex();
492+ PDRectangle pageMediaBox = document.getPage(pageIndex).getMediaBox();
493+ double xPt = x * pageMediaBox.getWidth() / renderBounds.getWidth();
494+ double yPt = y * pageMediaBox.getHeight() / renderBounds.getHeight();
495+
496+ PDVisibleSignDesigner designer;
497+ try(InputStream is = new FileInputStream(signature.getFile())) {
498+ designer = new PDVisibleSignDesigner(is);
499+ designer.width((float)mm2px(signature.getWidthMillis()));
500+ designer.height((float)mm2px(signature.getHeightMillis()));
501+ designer.xAxis((float)xPt - designer.getWidth() / 2);
502+ designer.yAxis((float)yPt - designer.getHeight() / 2 - pageMediaBox.getHeight());
503+ }
504+ PDVisibleSigProperties props = new PDVisibleSigProperties();
505+ props.setPdVisibleSignature(designer);
506+ props.visualSignEnabled(true);
507+ props.page(pageIndex + 1);
508+ props.buildSignature();
509+
510+ SignatureOptions options = new SignatureOptions();
511+ options.setPage(pageIndex);
512+ options.setVisualSignature(props);
513+
514+ lvSignature.getSelectionModel().clearSelection();
515+ busyProperty.set(true);
516+ Async.execute(() -> sign(document, options, APPLICATION_NAME, APPLICATION_VERSION))
517+ .onSucceeded(tmpFile -> {
518+ if(tmpFile != null) {
519+ signedTemporaryFileProperty.set(tmpFile);
520+ pdfView.load(tmpFile, pageIndex);
521+ busyProperty.set(false);
522+
523+ if(ButtonType.YES == Dialogs.showConfirmation(getPrimaryStage(), APPLICATION_NAME + " " + APPLICATION_VERSION,
524+ "署名が完了しました。\nファイルに名前を付けて保存しますか?")) {
525+ menuFileSave.fire();
526+ }
527+ lvSignature.getSelectionModel().clearSelection();
528+ }
529+ })
530+ .onCompleted(state -> busyProperty.set(false));
531+ }
532+
533+ protected File getFile(DragEvent event) {
534+ if(event.getDragboard().hasFiles()) {
535+ List<File> files = event.getDragboard().getFiles();
536+ if(files.size() == 1) {
537+ return files.get(0);
538+ }
539+ }
540+ return null;
541+ }
542+
543+ protected boolean isAcceptable(File file) {
544+ return file != null && file.getName().matches("(?i).+(\\.pdf)");
545+ }
546+
547+ protected boolean checkJpkiAvailability() {
548+ boolean isAvailable = JpkiWrapper.isAvailable();
549+ if(!isAvailable) {
550+ // JPKI 利用者クライアントソフトがインストールされていない場合、
551+ // インストールを促すために、クリックで公的個人認証サービスのウェブサイトが開くようにします。
552+ // ただし、Microsoft Storeアプリではストア以外でのアプリインストールを促すことが禁止されているため
553+ // UWPとして実行されている場合にはウェブサイトを開く機能を提供せずメッセージ表示のみに留めています。
554+ if(Datastore.isRunningAsUWP()) {
555+ toast.show(Toast.GREEN, "構成", "JPKI 利用者クライアントソフトが見つかりません。");
556+ } else {
557+ Runnable actionOnClick = wrap(() -> {
558+ toast.hide();
559+ Desktop.getDesktop().browse(URI.create("https://www.jpki.go.jp/download/win.html"));
560+ });
561+ toast.show(Toast.GREEN, "事前準備", ""
562+ + "JPKI 利用者クライアントソフトをインストールしてください。\n"
563+ + "ここをクリックするとブラウザーでダウンロードサイトを開きます。",
564+ null,
565+ actionOnClick);
566+ }
567+ }
568+ return isAvailable;
569+ }
570+
571+ protected File sign(PDDocument document, SignatureOptions options, String applicationName, String applicationVersion) throws JpkiException, IOException, ReflectiveOperationException {
572+ File tmpFile = Datastore.getMyDataDirectory(true).resolve("output.tmp").toFile();
573+ try (OutputStream output = new FileOutputStream(tmpFile)) {
574+ output.flush();
575+ JpkiWrapper jpki = new JpkiWrapper();
576+ jpki.setApplicationName(applicationName);
577+ jpki.setApplicationVersion(applicationVersion);
578+ jpki.addSignature(output, document, options);
579+ return tmpFile;
580+ } catch(JpkiException e) {
581+ //ユーザーがキャンセル操作をした場合はダイアログを表示しません。
582+ if(e.getWinErrorCode() != JpkiException.SCARD_W_CANCELLED_BY_USER) {
583+ throw e;
584+ }
585+ }
586+ return null;
587+ }
588+
589+ public static double mm2px(double mm) {
590+ return mm * 72.0 / 25.4;
591+ }
592+}
--- jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/SignatureListCell.java (revision 56)
+++ jpki-pdf-signer/trunk/src/main/java/net/osdn/jpki/pdf_signer/SignatureListCell.java (revision 57)
@@ -27,8 +27,8 @@
2727 Node node = Fxml.load(this);
2828 setText(null);
2929 setGraphic(node);
30- setOnMousePressed(SilentEventHandler.wrap(factory.main::lvSignature_cell_onMousePressed));
31- setOnMouseClicked(SilentEventHandler.wrap(factory.main::lvSignature_cell_onMouseClicked));
30+ setOnMousePressed(SilentEventHandler.wrap(factory.mainApp::lvSignature_cell_onMousePressed));
31+ setOnMouseClicked(SilentEventHandler.wrap(factory.mainApp::lvSignature_cell_onMouseClicked));
3232 }
3333
3434 @Override
@@ -62,7 +62,7 @@
6262
6363 public static class Factory implements Callback<ListView<Signature>, ListCell<Signature>> {
6464
65- Main main;
65+ MainApp mainApp;
6666 ContextMenu contextMenu1 = Fxml.load(this, "SignatureListCellContextMenu1.fxml");
6767 ContextMenu contextMenu2 = Fxml.load(this, "SignatureListCellContextMenu2.fxml");
6868
@@ -70,11 +70,11 @@
7070 @FXML MenuItem menuEditSignature;
7171 @FXML MenuItem menuRemoveSignature;
7272
73- public Factory(Main main) {
74- this.main = main;
75- menuAddSignature.setOnAction(SilentEventHandler.wrap(main::btnAddSignature_onAction));
76- menuEditSignature.setOnAction(SilentEventHandler.wrap(main::btnEditSignature_onAction));
77- menuRemoveSignature.setOnAction(SilentEventHandler.wrap(main::btnRemoveSignature_onAction));
73+ public Factory(MainApp mainApp) {
74+ this.mainApp = mainApp;
75+ menuAddSignature.setOnAction(SilentEventHandler.wrap(mainApp::btnAddSignature_onAction));
76+ menuEditSignature.setOnAction(SilentEventHandler.wrap(mainApp::btnEditSignature_onAction));
77+ menuRemoveSignature.setOnAction(SilentEventHandler.wrap(mainApp::btnRemoveSignature_onAction));
7878 }
7979
8080 @Override
旧リポジトリブラウザで表示