リビジョン | 57 (tree) |
---|---|
日時 | 2020-11-18 16:18:58 |
作者 | ![]() |
* jpki-pdf-signer 1.2.5
依存ライブラリ fx-util 0.3.8 -> 0.4.0
SingletonApplicationを継承するMainクラスをMainAppクラスにリネームして、エントリーメソッドmainを持つMainクラスを追加しました。
@@ -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 | -} |
@@ -1,614 +1,12 @@ | ||
1 | 1 | package net.osdn.jpki.pdf_signer; |
2 | 2 | |
3 | 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 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; | |
37 | 4 | 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; | |
49 | 5 | |
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 { | |
65 | 7 | |
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 | + } | |
614 | 12 | } |
@@ -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 | +} |
@@ -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 | +} |
@@ -27,8 +27,8 @@ | ||
27 | 27 | Node node = Fxml.load(this); |
28 | 28 | setText(null); |
29 | 29 | 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)); | |
32 | 32 | } |
33 | 33 | |
34 | 34 | @Override |
@@ -62,7 +62,7 @@ | ||
62 | 62 | |
63 | 63 | public static class Factory implements Callback<ListView<Signature>, ListCell<Signature>> { |
64 | 64 | |
65 | - Main main; | |
65 | + MainApp mainApp; | |
66 | 66 | ContextMenu contextMenu1 = Fxml.load(this, "SignatureListCellContextMenu1.fxml"); |
67 | 67 | ContextMenu contextMenu2 = Fxml.load(this, "SignatureListCellContextMenu2.fxml"); |
68 | 68 |
@@ -70,11 +70,11 @@ | ||
70 | 70 | @FXML MenuItem menuEditSignature; |
71 | 71 | @FXML MenuItem menuRemoveSignature; |
72 | 72 | |
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)); | |
78 | 78 | } |
79 | 79 | |
80 | 80 | @Override |