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>1. 位置情報を付加したい画像ファイルが格納されているフォルダを選択してください。</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>2. 正確な撮影時刻が判明できる画像を選んでください。</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>3. 正確な撮影時刻を入力してください。</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>4. ヒモ付を行う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 arg4_gpxFolder = new ParameterPanelFolder("GPXフォルダ: ", params.getProperty(AppParameters.IMG_SOURCE_FOLDER), JFileChooser.FILES_AND_DIRECTORIES);
249 tmpPanel4a.add(arg4_gpxFolder);
250 noFirstNode = new JCheckBox("<trkseg>セグメントの最初の1ノードは無視する。", params.getProperty(AppParameters.GPX_NO_FIRST_NODE).equals("ON"));
251 tmpPanel4a.add(noFirstNode);
252 gpxReuse = new JCheckBox("生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も対象にする。", params.getProperty(AppParameters.GPX_REUSE).equals("ON"));
253 gpxReuse.setEnabled(true);
254 tmpPanel4a.add(gpxReuse);
255 cards[cardNo].add(tmpPanel4a, BorderLayout.CENTER);
257 //---------------------------------------------------------------------
260 cards[cardNo].setLayout(new BorderLayout());
261 JLabel label5 = new JLabel();
262 label5.setText("<html><p>5. EXIF変換を行うかどうかを選択してください。</p><ul><li>EXIF変換を行う場合には、変換ファイルを出力するフォルダも指定する必要があります。</li><li>出力フォルダには、書き込み権限と、十分な空き容量が必要です。</li></ul>");
263 cards[cardNo].add(label5, BorderLayout.NORTH);
265 JPanel tmpPanel5 = new JPanel();
266 tmpPanel5.setLayout(new BoxLayout(tmpPanel5, BoxLayout.Y_AXIS));
268 exifON = new JCheckBox("EXIFの変換をする", false);
269 tmpPanel5.add(exifON);
271 arg5_outputFolder = new ParameterPanelFolder("書き出しフォルダ: ", params.getProperty(AppParameters.IMG_OUTPUT_FOLDER));
272 tmpPanel5.add(arg5_outputFolder);
273 cards[cardNo].add(tmpPanel5, BorderLayout.CENTER);
275 doButton = new JButton("処理実行", AdjustTime.createImageIcon("images/media_playback_start.png"));
276 cards[cardNo].add(doButton, BorderLayout.SOUTH);
279 //---------------------------------------------------------------------
281 menu1 = new java.awt.Menu("File");
282 miExit = new java.awt.MenuItem(QuitDialog.TITLE);
283 miExit.setFont(new Font("Dialog", Font.PLAIN, 12));
286 miAbout = new java.awt.MenuItem("About...");
287 miAbout.setFont(new Font("Dialog", Font.PLAIN, 12));
289 menu3 = new java.awt.Menu("Help");
290 menu3.setFont(new Font("Dialog", Font.PLAIN, 12));
293 mainMenuBar = new java.awt.MenuBar();
294 mainMenuBar.setHelpMenu(menu3);
295 mainMenuBar.add(menu1);
296 mainMenuBar.add(menu3);
297 setMenuBar(mainMenuBar);
300 //{{REGISTER_LISTENERS
301 SymWindow aSymWindow = new SymWindow();
302 this.addWindowListener(aSymWindow);
303 SymAction lSymAction = new SymAction();
304 miAbout.addActionListener(lSymAction);
305 miExit.addActionListener(lSymAction);
306 openButton.addActionListener(lSymAction);
307 zoomOutButton.addActionListener(lSymAction);
308 zoomInButton.addActionListener(lSymAction);
309 arg2_baseTimeImg.argField.addActionListener(lSymAction);
310 arg2_baseTimeImg.openButton.addActionListener(lSymAction);
311 doButton.addActionListener(lSymAction);
312 exifON.addActionListener(lSymAction);
313 noFirstNode.addActionListener(lSymAction);
314 gpxReuse.addActionListener(lSymAction);
315 //arg1Panel_4.field.addActionListener(lSymAction);
316 nextButton.addActionListener(lSymAction);
317 backButton.addActionListener(lSymAction);
322 * Shows or hides the component depending on the boolean flag b.
323 * @param b trueのときコンポーネントを表示; その他のとき, componentを隠す.
324 * @see java.awt.Component#isVisible
327 public void setVisible(boolean b) {
335 * このクラスをインスタンスを生成して表示する。
339 static public void main(String args[]) {
340 SwingUtilities.invokeLater(() -> {
343 } catch (Exception e) {
348 private static void createAndShowGUI() throws IOException {
349 (new AdjustTime()).setVisible(true);
353 public void addNotify() {
354 // Record the size of the window prior to calling parents addNotify.
355 Dimension d = getSize();
359 if (fComponentsAdjusted)
362 // Adjust components according to the insets
363 setSize(getInsets().left + getInsets().right + d.width, getInsets().top + getInsets().bottom + d.height);
364 Component components[] = getComponents();
365 for (Component component : components) {
366 Point p = component.getLocation();
367 p.translate(getInsets().left, getInsets().top);
368 component.setLocation(p);
370 fComponentsAdjusted = true;
373 void DbMang_WindowClosing(java.awt.event.WindowEvent event) {
374 setVisible(false); // hide the Manager
375 dispose(); // free the system resources
376 System.exit(0); // close the application
379 void miAbout_Action(java.awt.event.ActionEvent event) {
380 // Action from About Create and show as modal
381 (new AboutDialog(this, true)).setVisible(true);
384 void miExit_Action(java.awt.event.ActionEvent event) {
385 // Action from Exit Create and show as modal
386 //(new hayashi.yuu.tools.gui.QuitDialog(this, true)).setVisible(true);
387 (new QuitDialog(this, true)).setVisible(true);
394 * 基準画像ボタンがクリックされた時に、基準時刻フィールドに基準画像の作成日時を設定する。
396 * @throws ImageReadException
397 * @throws ImageWriteException
399 public void imageView_Action(ActionEvent ev) {
400 String path = (new File(arg1_srcFolder.getText(), arg2_baseTimeImg.getText())).getPath();
402 File timeFile = new File(path);
403 long lastModifyTime = timeFile.lastModified();
404 arg3_basetiome.argField.setText(ImportPicture.dfjp.format(new Date(lastModifyTime)));
406 if (exifBase.isSelected()) {
408 ImageMetadata meta = Imaging.getMetadata(timeFile);
409 arg3_basetiome.argField.setText("imageView_Action : 412");
411 JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
412 if (jpegMetadata != null) {
413 TiffImageMetadata exif = jpegMetadata.getExif();
415 String dateTimeOriginal = exif.getFieldValue(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL)[0];
416 lastModifyTime = (new SimpleDateFormat("yyyy:MM:dd HH:mm:ss")).parse(dateTimeOriginal).getTime();
417 arg3_basetiome.argField.setText(ImportPicture.dfjp.format(new Date(lastModifyTime)));
420 arg3_basetiome.argField.setText("exif == null");
424 catch (Exception e) {}
427 int size_x = imageSPane.getWidth() - 8;
428 ImageIcon tmpIcon = new ImageIcon(path);
430 if (tmpIcon.getIconWidth() > size_x) {
431 refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x, -1, Image.SCALE_DEFAULT));
433 imageLabel.setIcon(refImage);
437 public void zoomin_Action(ActionEvent ev) {
438 if (refImage != null) {
439 int size_x = imageLabel.getWidth();
440 String path = (new File(arg1_srcFolder.getText(), arg2_baseTimeImg.getText())).getPath();
441 ImageIcon tmpIcon = new ImageIcon(path);
442 refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x * 2, -1, Image.SCALE_DEFAULT));
443 imageLabel.setIcon(refImage);
448 public void zoomout_Action(ActionEvent ev) {
449 if (refImage != null) {
450 int size_x = imageLabel.getWidth();
451 ImageIcon tmpIcon = refImage;
452 refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x / 2, -1, Image.SCALE_DEFAULT));
453 imageLabel.setIcon(refImage);
459 * ソースディレクトリが選択された時のアクション
460 * → AdjustTime.ini に書き込む
463 public void selectSource_Action(ActionEvent ev) {
466 public void selectImage_Action(ActionEvent ev) {
469 //Set up the file chooser.
470 File sdir = new File(arg1_srcFolder.getText());
471 System.out.println(sdir.getPath());
472 if (sdir.isDirectory()) {
473 fc = new JFileChooser(sdir);
476 fc = new JFileChooser();
479 //Add a custom file filter and disable the default
480 //(Accept All) file filter.
481 fc.addChoosableFileFilter(new ImageFilter());
482 fc.setAcceptAllFileFilterUsed(false);
484 //Add custom icons for file types.
485 fc.setFileView(new ImageFileView());
487 //Add the preview pane.
488 fc.setAccessory(new ImagePreview(fc));
491 int returnVal = fc.showDialog(this, "選択");
493 //Process the results.
494 if (returnVal == JFileChooser.APPROVE_OPTION) {
495 File file = fc.getSelectedFile();
496 arg2_baseTimeImg.argField.setText(file.getName());
499 //Reset the file chooser for the next time it's shown.
500 fc.setSelectedFile(null);
503 /** Returns an ImageIcon, or null if the path was invalid.
506 public static ImageIcon createImageIcon(String path) {
507 java.net.URL imgURL = AdjustTime.class.getResource(path);
508 if (imgURL != null) {
509 return new ImageIcon(imgURL);
511 System.err.println("Couldn't find file: " + path);
517 * checkbox[EXIF書き出し]を変更した場合のアクション
518 * ON ー> EXIF変換先フォルダのフィールドを有効にする
519 * OFF -> EXIF変換先フォルダのフィールドを無効にする
522 void exifON_Action (ActionEvent event) {
523 arg5_outputFolder.setEnabled(exifON.isEnabled());
527 * [実行]ボタンをクリックしたときの動作
529 * @throws IOException
530 * @throws FileNotFoundException
532 void doButton_Action(java.awt.event.ActionEvent event) {
533 doButton.setEnabled(false);
535 String[] argv = new String[5];
536 argv[0] = arg1_srcFolder.getText();
537 argv[1] = arg2_baseTimeImg.getText();
538 argv[2] = arg3_basetiome.getText();
539 argv[3] = (exifON.isSelected() ? arg5_outputFolder.getText() : "noEXIF");
540 argv[4] = arg4_gpxFolder.getText();
542 this.params.setProperty(AppParameters.GPX_NO_FIRST_NODE, (noFirstNode.isSelected() ? "ON" : "OFF"));
543 this.params.setProperty(AppParameters.GPX_REUSE, (gpxReuse.isSelected() ? "ON" : "OFF"));
544 this.params.setProperty(AppParameters.GPX_SOURCE_FOLDER, arg4_gpxFolder.getText());
545 this.params.setProperty(AppParameters.IMG_SOURCE_FOLDER, arg1_srcFolder.getText());
546 this.params.setProperty(AppParameters.IMG_BASE_FILE, arg2_baseTimeImg.getText());
547 this.params.setProperty(AppParameters.GPX_BASETIME, arg3_basetiome.getText());
548 this.params.setProperty(AppParameters.IMG_OUTPUT_FOLDER, arg4_gpxFolder.getText());
552 catch(Exception e) {}
554 (new DoDialog(this, argv)).setVisible(true);
556 doButton.setEnabled(true);
563 void nextButton_Action(ActionEvent event) {
564 if ((cardPanelNo + 1) < cards.length) {
566 CardLayout cl = (CardLayout)(cardPanel.getLayout());
567 cl.show(cardPanel, String.valueOf(cardPanelNo));
575 void backButton_Action(ActionEvent event) {
576 if ((cardPanelNo - 1) >= 0) {
578 CardLayout cl = (CardLayout)(cardPanel.getLayout());
579 cl.show(cardPanel, String.valueOf(cardPanelNo));