1 package osm.jp.gpx.matchtime.gui;
3 import java.awt.event.ActionEvent;
5 import java.io.FileNotFoundException;
6 import java.io.IOException;
7 import java.text.SimpleDateFormat;
13 import org.apache.commons.imaging.ImageReadException;
14 import org.apache.commons.imaging.ImageWriteException;
15 import org.apache.commons.imaging.Imaging;
16 import org.apache.commons.imaging.common.ImageMetadata;
17 import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
18 import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
19 import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
24 @SuppressWarnings("serial")
25 public class AdjustTime extends JFrame
27 public static final String PROGRAM_NAME = "AdjustTime for JOSM";
28 public static final String PROGRAM_VARSION = "3.0";
29 public static final String PROGRAM_UPDATE = "2015/07/26";
31 // Used for addNotify check.
32 boolean fComponentsAdjusted = false;
35 JPanel cardPanel; // ウィザード形式パネル(カード型)
38 JPanel argsPanel; // パラメータ設定パネル (上部)
39 JScrollPane imageSPane; // スクロールパネル
40 JLabel imageLabel; // 基準時刻画像表示
41 ButtonGroup baseTimeGroup; // 基準時刻の指定グループ
42 JRadioButton exifBase; // EXIF日時を基準にする/ !(ファイル更新日時を基準にする)
43 JRadioButton fupdateBase; // File更新日時を基準にする/ !(XIF日時を基準にする)
44 JCheckBox noFirstNode; // GPX: <trkseg>セグメントの最初の1ノードは無視する。 {ON | OFF}
45 JCheckBox gpxReuse; // 生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も対象にする。 {ON | OFF}
46 JTextArea textArea; // 実行結果表示領域
47 //ImagePreview imagePane; // 基準時刻画像表示
48 ParameterPanelFolder arg1_srcFolder;
49 ParameterPanelImageFile arg2_baseTimeImg;
50 ParameterPanel arg3_basetiome;
51 JCheckBox exifON; // EXIF 書き出しモード / !(EXIFの書き換えはしない)
52 ParameterPanelFolder arg5_outputFolder; // EXIF 書き出しフォルダ
53 ParameterPanelFolder arg4_gpxFolder; // GPXファイル・フォルダ
55 JPanel buttonPanel; // ボタンパネル (下部)
56 JButton openButton; // [Fit]ボタン
57 JButton zoomInButton; // [Zoom in]ボタン
58 JButton zoomOutButton; // [Zoom out]ボタン
59 JButton nextButton; // [次へ]ボタン
60 JButton backButton; // [戻る]ボタン
61 JButton doButton; // [処理実行]ボタン
65 java.awt.MenuBar mainMenuBar;
67 java.awt.MenuItem miDoNewFileList;
68 java.awt.MenuItem miDoDirSize;
69 java.awt.MenuItem miDoReadXML;
70 java.awt.MenuItem miExit;
72 java.awt.MenuItem miAbout;
75 class SymWindow extends java.awt.event.WindowAdapter {
78 * このパネルが閉じられたら、このアプリケーションも終了させる。
81 public void windowClosing(java.awt.event.WindowEvent event) {
82 Object object = event.getSource();
83 if (object == AdjustTime.this) {
84 DbMang_WindowClosing(event);
89 class SymAction implements java.awt.event.ActionListener {
91 public void actionPerformed(java.awt.event.ActionEvent event) {
92 Object object = event.getSource();
93 if (object == miAbout) {
94 miAbout_Action(event);
96 else if (object == miExit) {
99 else if (object == openButton) {
100 imageView_Action(event);
102 else if (object == zoomInButton) {
103 zoomin_Action(event);
105 else if (object == zoomOutButton) {
106 zoomout_Action(event);
108 else if (object == arg2_baseTimeImg.argField) {
109 imageView_Action(event);
111 else if (object == arg2_baseTimeImg.openButton) {
112 selectImage_Action(event);
113 imageView_Action(event);
115 else if (object == exifON) {
116 exifON_Action(event);
118 else if (object == doButton) {
119 doButton_Action(event);
121 else if (object == nextButton) {
122 nextButton_Action(event);
124 else if (object == backButton) {
125 backButton_Action(event);
130 AppParameters params;
133 * データベース内のテーブルを一覧で表示するFrame
134 * @throws IOException
136 public AdjustTime() throws IOException
139 Container container = getContentPane();
140 container.setLayout(new BorderLayout());
141 setSize(getInsets().left + getInsets().right + 960,getInsets().top + getInsets().bottom + 480);
142 setTitle(AdjustTime.PROGRAM_NAME +" v"+ AdjustTime.PROGRAM_VARSION);
144 params = new AppParameters();
146 //---------------------------------------------------------------------
147 cardPanel = new JPanel();
148 cardPanel.setLayout(new CardLayout());
149 container.add(cardPanel, BorderLayout.CENTER);
151 nextButton = new JButton("次へ");
152 container.add(nextButton, BorderLayout.EAST);
154 backButton = new JButton("戻る");
155 container.add(backButton, BorderLayout.WEST);
157 cards = new JPanel[5];
158 for (int i=0; i < 5; i++) {
159 cards[i] = new JPanel();
160 cardPanel.add(cards[i], String.valueOf(i));
164 //---------------------------------------------------------------------
167 cards[cardNo].setLayout(new BorderLayout());
168 JLabel label1 = new JLabel();
169 label1.setText("<html><p>位置情報を付加したい画像ファイルが格納されているフォルダを選択してください。</p><ul><li>コピー動作を行うと、ファイル更新時刻がコピーを実行した時刻に書き換わってしまうことがあります。オリジナルのカメラUSB内のフォルダを直接指定することをおすすめします。</li></ul>");
170 cards[cardNo].add(label1, BorderLayout.NORTH);
172 argsPanel = new JPanel();
173 argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.Y_AXIS));
174 arg1_srcFolder = new ParameterPanelFolder("対象フォルダ: ", params.getProperty(AppParameters.IMG_SOURCE_FOLDER));
175 argsPanel.add(arg1_srcFolder);
176 cards[cardNo].add(argsPanel, BorderLayout.CENTER);
178 //---------------------------------------------------------------------
181 cards[cardNo].setLayout(new BorderLayout());
182 JLabel label2 = new JLabel();
183 label2.setText("<html><p>正確な撮影時刻が判明できる画像を選んでください。</p><ul><li>スマートフォンの時計画面(秒が判別できること)を撮影した画像</li><li>カメラの時計が正確ならば、どの画像を選んでも構いません。</li></ul>");
184 cards[cardNo].add(label2, BorderLayout.NORTH);
186 argsPanel = new JPanel();
187 argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.Y_AXIS));
188 arg2_baseTimeImg = new ParameterPanelImageFile("基準時刻画像: ", params.getProperty(AppParameters.IMG_BASE_FILE), arg1_srcFolder);
189 argsPanel.add(arg2_baseTimeImg);
191 baseTimeGroup = new ButtonGroup();
192 exifBase = new JRadioButton("EXIFの日時を基準にする");
193 fupdateBase = new JRadioButton("File更新日時を基準にする");
194 baseTimeGroup.add(exifBase);
195 baseTimeGroup.add(fupdateBase);
196 argsPanel.add(exifBase);
197 argsPanel.add(fupdateBase);
198 if (params.getProperty(AppParameters.GPX_BASETIME).equals("EXIF_TIME")) {
199 fupdateBase.setSelected(false);
200 exifBase.setSelected(true);
203 fupdateBase.setSelected(true);
204 exifBase.setSelected(false);
206 cards[cardNo].add(argsPanel, BorderLayout.CENTER);
208 //---------------------------------------------------------------------
211 cards[cardNo].setLayout(new BorderLayout());
212 argsPanel = new JPanel();
213 argsPanel.setLayout(new GridLayout(2, 1));
215 JLabel label3 = new JLabel();
216 label3.setText("<html><p>正確な撮影時刻を入力してください。</p><ul><li>カメラの時計が正確ならば、設定を変更する必要はありません。</li></ul>");
217 argsPanel.add(label3);
218 arg3_basetiome = new ParameterPanel(" 基準時刻: ", ImportPicture.TIME_FORMAT_STRING);
219 argsPanel.add(arg3_basetiome);
220 cards[cardNo].add(argsPanel, BorderLayout.NORTH);
223 imageLabel = new JLabel();
224 imageSPane = new JScrollPane(imageLabel);
225 cards[cardNo].add(imageSPane, BorderLayout.CENTER);
227 // 画像ファイル選択ダイアログを起動するボタン
228 buttonPanel = new JPanel();
229 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
230 openButton = new JButton(createImageIcon("images/Fit16.gif"));
231 buttonPanel.add(openButton);
232 zoomInButton = new JButton(createImageIcon("images/ZoomIn16.gif"));
233 buttonPanel.add(zoomInButton);
234 zoomOutButton = new JButton(createImageIcon("images/ZoomOut16.gif"));
235 buttonPanel.add(zoomOutButton);
236 cards[cardNo].add(buttonPanel, BorderLayout.SOUTH);
238 //---------------------------------------------------------------------
241 cards[cardNo].setLayout(new BorderLayout());
242 JLabel label4 = new JLabel();
243 label4.setText("<html><p>ヒモ付を行うGPXファイルを選択してください。</p><ul><li>フォルダを指定すると、フォルダ内にあるすべてのGPXファイルを対象とします。</li><li>カメラの時計が正確ならば、どの画像を選んでも構いません。</li></ul>");
244 cards[cardNo].add(label4, BorderLayout.NORTH);
246 JPanel tmpPanel4a = new JPanel();
247 tmpPanel4a.setLayout(new BoxLayout(tmpPanel4a, BoxLayout.Y_AXIS));
248 File gpxDir = new File(".");
249 arg4_gpxFolder = new ParameterPanelFolder("GPXフォルダ: ", gpxDir.getAbsolutePath(), JFileChooser.FILES_AND_DIRECTORIES);
250 tmpPanel4a.add(arg4_gpxFolder);
251 noFirstNode = new JCheckBox("<trkseg>セグメントの最初の1ノードは無視する。", params.getProperty(AppParameters.GPX_NO_FIRST_NODE).equals("ON"));
252 tmpPanel4a.add(noFirstNode);
253 gpxReuse = new JCheckBox("生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も対象にする。", params.getProperty(AppParameters.GPX_REUSE).equals("ON"));
254 gpxReuse.setEnabled(true);
255 tmpPanel4a.add(gpxReuse);
256 cards[cardNo].add(tmpPanel4a, BorderLayout.CENTER);
258 //---------------------------------------------------------------------
261 cards[cardNo].setLayout(new BorderLayout());
262 JLabel label5 = new JLabel();
263 label5.setText("<html><p>EXIF変換を行うかどうかを選択してください。</p><ul><li>EXIF変換を行う場合には、変換ファイルを出力するフォルダも指定する必要があります。</li><li>出力フォルダには、書き込み権限と、十分な空き容量が必要です。</li></ul>");
264 cards[cardNo].add(label5, BorderLayout.NORTH);
266 JPanel tmpPanel5 = new JPanel();
267 tmpPanel5.setLayout(new BoxLayout(tmpPanel5, BoxLayout.Y_AXIS));
269 exifON = new JCheckBox("EXIFの変換をする", false);
270 tmpPanel5.add(exifON);
272 File outputDir = new File(".");
273 arg5_outputFolder = new ParameterPanelFolder("書き出しフォルダ: ", outputDir.getAbsolutePath());
274 tmpPanel5.add(arg5_outputFolder);
275 cards[cardNo].add(tmpPanel5, BorderLayout.CENTER);
277 doButton = new JButton("処理実行", AdjustTime.createImageIcon("images/media_playback_start.png"));
278 cards[cardNo].add(doButton, BorderLayout.SOUTH);
281 //---------------------------------------------------------------------
283 menu1 = new java.awt.Menu("File");
284 miExit = new java.awt.MenuItem(QuitDialog.TITLE);
285 miExit.setFont(new Font("Dialog", Font.PLAIN, 12));
288 miAbout = new java.awt.MenuItem("About...");
289 miAbout.setFont(new Font("Dialog", Font.PLAIN, 12));
291 menu3 = new java.awt.Menu("Help");
292 menu3.setFont(new Font("Dialog", Font.PLAIN, 12));
295 mainMenuBar = new java.awt.MenuBar();
296 mainMenuBar.setHelpMenu(menu3);
297 mainMenuBar.add(menu1);
298 mainMenuBar.add(menu3);
299 setMenuBar(mainMenuBar);
302 //{{REGISTER_LISTENERS
303 SymWindow aSymWindow = new SymWindow();
304 this.addWindowListener(aSymWindow);
305 SymAction lSymAction = new SymAction();
306 miAbout.addActionListener(lSymAction);
307 miExit.addActionListener(lSymAction);
308 openButton.addActionListener(lSymAction);
309 zoomOutButton.addActionListener(lSymAction);
310 zoomInButton.addActionListener(lSymAction);
311 arg2_baseTimeImg.argField.addActionListener(lSymAction);
312 arg2_baseTimeImg.openButton.addActionListener(lSymAction);
313 doButton.addActionListener(lSymAction);
314 exifON.addActionListener(lSymAction);
315 noFirstNode.addActionListener(lSymAction);
316 gpxReuse.addActionListener(lSymAction);
317 //arg1Panel_4.field.addActionListener(lSymAction);
318 nextButton.addActionListener(lSymAction);
319 backButton.addActionListener(lSymAction);
324 * Shows or hides the component depending on the boolean flag b.
325 * @param b trueのときコンポーネントを表示; その他のとき, componentを隠す.
326 * @see java.awt.Component#isVisible
329 public void setVisible(boolean b) {
337 * このクラスをインスタンスを生成して表示する。
341 static public void main(String args[]) {
342 SwingUtilities.invokeLater(() -> {
345 } catch (Exception e) {
350 private static void createAndShowGUI() throws IOException {
351 (new AdjustTime()).setVisible(true);
355 public void addNotify() {
356 // Record the size of the window prior to calling parents addNotify.
357 Dimension d = getSize();
361 if (fComponentsAdjusted)
364 // Adjust components according to the insets
365 setSize(getInsets().left + getInsets().right + d.width, getInsets().top + getInsets().bottom + d.height);
366 Component components[] = getComponents();
367 for (Component component : components) {
368 Point p = component.getLocation();
369 p.translate(getInsets().left, getInsets().top);
370 component.setLocation(p);
372 fComponentsAdjusted = true;
375 void DbMang_WindowClosing(java.awt.event.WindowEvent event) {
376 setVisible(false); // hide the Manager
377 dispose(); // free the system resources
378 System.exit(0); // close the application
381 void miAbout_Action(java.awt.event.ActionEvent event) {
382 // Action from About Create and show as modal
383 (new AboutDialog(this, true)).setVisible(true);
386 void miExit_Action(java.awt.event.ActionEvent event) {
387 // Action from Exit Create and show as modal
388 //(new hayashi.yuu.tools.gui.QuitDialog(this, true)).setVisible(true);
389 (new QuitDialog(this, true)).setVisible(true);
396 * 基準画像ボタンがクリックされた時に、基準時刻フィールドに基準画像の作成日時を設定する。
398 * @throws ImageReadException
399 * @throws ImageWriteException
401 public void imageView_Action(ActionEvent ev) {
402 String path = (new File(arg1_srcFolder.getText(), arg2_baseTimeImg.getText())).getPath();
404 File timeFile = new File(path);
405 long lastModifyTime = timeFile.lastModified();
406 arg3_basetiome.argField.setText(ImportPicture.dfjp.format(new Date(lastModifyTime)));
408 if (exifBase.isSelected()) {
410 ImageMetadata meta = Imaging.getMetadata(timeFile);
411 arg3_basetiome.argField.setText("imageView_Action : 412");
413 JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
414 if (jpegMetadata != null) {
415 TiffImageMetadata exif = jpegMetadata.getExif();
417 String dateTimeOriginal = exif.getFieldValue(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL)[0];
418 lastModifyTime = (new SimpleDateFormat("yyyy:MM:dd HH:mm:ss")).parse(dateTimeOriginal).getTime();
419 arg3_basetiome.argField.setText(ImportPicture.dfjp.format(new Date(lastModifyTime)));
422 arg3_basetiome.argField.setText("exif == null");
426 catch (Exception e) {}
429 int size_x = imageSPane.getWidth() - 8;
430 ImageIcon tmpIcon = new ImageIcon(path);
432 if (tmpIcon.getIconWidth() > size_x) {
433 refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x, -1, Image.SCALE_DEFAULT));
435 imageLabel.setIcon(refImage);
439 public void zoomin_Action(ActionEvent ev) {
440 if (refImage != null) {
441 int size_x = imageLabel.getWidth();
442 String path = (new File(arg1_srcFolder.getText(), arg2_baseTimeImg.getText())).getPath();
443 ImageIcon tmpIcon = new ImageIcon(path);
444 refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x * 2, -1, Image.SCALE_DEFAULT));
445 imageLabel.setIcon(refImage);
450 public void zoomout_Action(ActionEvent ev) {
451 if (refImage != null) {
452 int size_x = imageLabel.getWidth();
453 ImageIcon tmpIcon = refImage;
454 refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x / 2, -1, Image.SCALE_DEFAULT));
455 imageLabel.setIcon(refImage);
461 * ソースディレクトリが選択された時のアクション
462 * → AdjustTime.ini に書き込む
465 public void selectSource_Action(ActionEvent ev) {
468 public void selectImage_Action(ActionEvent ev) {
471 //Set up the file chooser.
472 File sdir = new File(arg1_srcFolder.getText());
473 System.out.println(sdir.getPath());
474 if (sdir.isDirectory()) {
475 fc = new JFileChooser(sdir);
478 fc = new JFileChooser();
481 //Add a custom file filter and disable the default
482 //(Accept All) file filter.
483 fc.addChoosableFileFilter(new ImageFilter());
484 fc.setAcceptAllFileFilterUsed(false);
486 //Add custom icons for file types.
487 fc.setFileView(new ImageFileView());
489 //Add the preview pane.
490 fc.setAccessory(new ImagePreview(fc));
493 int returnVal = fc.showDialog(this, "選択");
495 //Process the results.
496 if (returnVal == JFileChooser.APPROVE_OPTION) {
497 File file = fc.getSelectedFile();
498 arg2_baseTimeImg.argField.setText(file.getName());
501 //Reset the file chooser for the next time it's shown.
502 fc.setSelectedFile(null);
505 /** Returns an ImageIcon, or null if the path was invalid.
508 public static ImageIcon createImageIcon(String path) {
509 java.net.URL imgURL = AdjustTime.class.getResource(path);
510 if (imgURL != null) {
511 return new ImageIcon(imgURL);
513 System.err.println("Couldn't find file: " + path);
519 * checkbox[EXIF書き出し]を変更した場合のアクション
520 * ON ー> EXIF変換先フォルダのフィールドを有効にする
521 * OFF -> EXIF変換先フォルダのフィールドを無効にする
524 void exifON_Action (ActionEvent event) {
525 arg5_outputFolder.setEnabled(exifON.isEnabled());
529 * [実行]ボタンをクリックしたときの動作
531 * @throws IOException
532 * @throws FileNotFoundException
534 void doButton_Action(java.awt.event.ActionEvent event) {
535 doButton.setEnabled(false);
537 String[] argv = new String[5];
538 argv[0] = arg1_srcFolder.getText();
539 argv[1] = arg2_baseTimeImg.getText();
540 argv[2] = arg3_basetiome.getText();
541 argv[3] = (exifON.isSelected() ? arg5_outputFolder.getText() : "noEXIF");
542 argv[4] = arg4_gpxFolder.getText();
544 this.params.setProperty(AppParameters.GPX_NO_FIRST_NODE, (noFirstNode.isSelected() ? "ON" : "OFF"));
545 this.params.setProperty(AppParameters.GPX_REUSE, (gpxReuse.isSelected() ? "ON" : "OFF"));
546 this.params.setProperty(AppParameters.IMG_SOURCE_FOLDER, arg1_srcFolder.getText());
547 this.params.setProperty(AppParameters.IMG_BASE_FILE, arg2_baseTimeImg.getText());
548 this.params.setProperty(AppParameters.GPX_BASETIME, arg3_basetiome.getText());
549 this.params.setProperty(AppParameters.IMG_OUTPUT_FOLDER, arg4_gpxFolder.getText());
553 catch(Exception e) {}
555 (new DoDialog(this, argv)).setVisible(true);
557 doButton.setEnabled(true);
564 void nextButton_Action(ActionEvent event) {
565 if ((cardPanelNo + 1) < cards.length) {
567 CardLayout cl = (CardLayout)(cardPanel.getLayout());
568 cl.show(cardPanel, String.valueOf(cardPanelNo));
576 void backButton_Action(ActionEvent event) {
577 if ((cardPanelNo - 1) >= 0) {
579 CardLayout cl = (CardLayout)(cardPanel.getLayout());
580 cl.show(cardPanel, String.valueOf(cardPanelNo));