OSDN Git Service

適用ボタン周辺部見直し
[coroid/inqubus.git] / frontend / src / yukihane / inqubus / gui / MainFrame.java
1 /*
2  * MainFrame.java
3  *
4  * Created on 2011/05/28, 18:14:51
5  */
6 package yukihane.inqubus.gui;
7
8 import java.awt.Image;
9 import java.awt.ItemSelectable;
10 import java.awt.Toolkit;
11 import java.awt.datatransfer.DataFlavor;
12 import java.awt.datatransfer.Transferable;
13 import java.awt.event.ActionEvent;
14 import java.awt.event.ActionListener;
15 import java.awt.event.ItemEvent;
16 import java.awt.event.ItemListener;
17 import java.awt.event.KeyEvent;
18 import java.beans.PropertyChangeEvent;
19 import java.beans.PropertyChangeListener;
20 import java.io.File;
21 import java.io.FilenameFilter;
22 import java.io.IOException;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import javax.swing.BorderFactory;
32 import javax.swing.DropMode;
33 import javax.swing.GroupLayout;
34 import javax.swing.GroupLayout.Alignment;
35 import javax.swing.JButton;
36 import javax.swing.JCheckBox;
37 import javax.swing.JFrame;
38 import javax.swing.JLabel;
39 import javax.swing.JMenu;
40 import javax.swing.JMenuBar;
41 import javax.swing.JMenuItem;
42 import javax.swing.JOptionPane;
43 import javax.swing.JPanel;
44 import javax.swing.JScrollPane;
45 import javax.swing.JTabbedPane;
46 import javax.swing.JTable;
47 import javax.swing.JTextField;
48 import javax.swing.KeyStroke;
49 import javax.swing.LayoutStyle.ComponentPlacement;
50 import javax.swing.SwingUtilities;
51 import javax.swing.TransferHandler;
52 import javax.swing.WindowConstants;
53 import javax.swing.border.BevelBorder;
54 import org.apache.commons.lang.StringUtils;
55 import org.apache.commons.lang.builder.ToStringBuilder;
56 import saccubus.FfmpegOption;
57 import saccubus.MainFrame_AboutBox;
58 import saccubus.util.WayBackTimeParser;
59 import saccubus.worker.impl.convert.ConvertProgress;
60 import saccubus.worker.impl.download.DownloadProgress;
61 import saccubus.worker.WorkerListener;
62 import saccubus.worker.impl.convert.ConvertResult;
63 import saccubus.worker.impl.download.DownloadResult;
64 import saccubus.worker.profile.CommentProfile;
65 import saccubus.worker.profile.ConvertProfile;
66 import saccubus.worker.profile.DownloadProfile;
67 import saccubus.worker.profile.FfmpegProfile;
68 import saccubus.worker.profile.GeneralProfile;
69 import saccubus.worker.profile.LoginProfile;
70 import saccubus.worker.profile.OutputProfile;
71 import saccubus.worker.profile.ProxyProfile;
72 import saccubus.worker.profile.VideoProfile;
73 import yukihane.Util;
74 import yukihane.inqubus.Config;
75 import yukihane.inqubus.manager.RequestProcess;
76 import yukihane.inqubus.manager.TaskKind;
77 import yukihane.inqubus.manager.TaskManage;
78 import yukihane.inqubus.manager.TaskManageListener;
79 import yukihane.inqubus.manager.TaskStatus;
80 import yukihane.inqubus.model.Target;
81 import yukihane.inqubus.model.TargetsTableModel;
82
83 /**
84  *
85  * @author yuki
86  */
87 public class MainFrame extends JFrame {
88
89     private static final long serialVersionUID = 1L;
90     private static final Logger logger = Logger.getLogger(MainFrame.class.getName());
91     private static final String ID_FIELD_TOOLTIP = "動画のIDまたはURLを入力します。";
92     private static final String FILE_LOCALBUTTON_TOOLTIP
93             = "ダウンロードする場合はチェックを外します。ローカルファイルを使用する場合はチェックを入れます。";
94     private static final String FILE_INPUTFIELD_TOOLTIP
95             = "ダウンロードする場合はファイル命名規則を入力します。"
96             + "ローカルファイルを使用する場合はパスを含むファイル名を入力します。";
97     private static final String FILE_OUTPUTFIELD_TOOLTIP
98             = "ファイル命名規則入力します。";
99     private final TargetsTableModel targetModel = new TargetsTableModel();
100     private final TaskManage taskManager;
101
102     /** Creates new form MainFrame */
103     public MainFrame() {
104         final URL url = MainFrame_AboutBox.class.getResource("icon.png");
105         final Image icon1 = Toolkit.getDefaultToolkit().createImage(url);
106         final URL url32 = MainFrame_AboutBox.class.getResource("icon32.png");
107         final Image icon2 = Toolkit.getDefaultToolkit().createImage(url32);
108         final List<Image> images = new ArrayList<Image>(2);
109         images.add(icon1);
110         images.add(icon2);
111         setIconImages(images);
112
113         final JPanel pnlMain = new JPanel();
114         final JScrollPane scrDisplay = new JScrollPane();
115         tblDisplay = new JTable(targetModel, new TargetsColumnModel());
116         tblDisplay.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
117         final JPanel pnlButton = new JPanel();
118         final JTabbedPane tbpInput = new JTabbedPane();
119         final JPanel pnlInputMain = new JPanel();
120         final FfmpegParamPanel pnlInputFfmpeg = new FfmpegParamPanel();
121         final JLabel lblId = new JLabel();
122         fldId.setToolTipText(ID_FIELD_TOOLTIP);
123         final JLabel lblVideo = new JLabel();
124         cbVideoLocal = new JCheckBox();
125         cbVideoLocal.setToolTipText(FILE_LOCALBUTTON_TOOLTIP);
126         fldVideo = new JTextField();
127         fldVideo.setToolTipText(FILE_INPUTFIELD_TOOLTIP);
128         btnVideo = new JButton();
129         final JLabel lblComment = new JLabel();
130
131         fldBackLog.setToolTipText("YYYY/MM/DD hh:mm:ss形式、あるいは1970/01/01からの経過秒を入力します。");
132         cbBackLog.addItemListener(new ItemListener() {
133
134             @Override
135             public void itemStateChanged(ItemEvent e) {
136                 final boolean selected = (e.getStateChange() == ItemEvent.SELECTED);
137                 fldBackLog.setEnabled(selected);
138             }
139         });
140         cbBackLog.addPropertyChangeListener("enabled", new PropertyChangeListener() {
141
142             @Override
143             public void propertyChange(PropertyChangeEvent evt) {
144                 final boolean enabled = ((Boolean) evt.getNewValue()).booleanValue();
145                 final boolean fldEnabled = enabled ? cbBackLog.isSelected() : false;
146                 fldBackLog.setEnabled(fldEnabled);
147             }
148         });
149         cbBackLogReduce.setToolTipText("「コメントの量を減らす」場合はチェックを付けます。");
150
151         cbCommentLocal = new JCheckBox();
152         cbCommentLocal.setToolTipText(FILE_LOCALBUTTON_TOOLTIP);
153         cbCommentLocal.addItemListener(new ItemListener() {
154
155             @Override
156             public void itemStateChanged(ItemEvent e) {
157                 final boolean selected = (e.getStateChange() == ItemEvent.SELECTED);
158                 cbBackLogReduce.setEnabled(!selected);
159                 cbBackLog.setEnabled(!selected);
160             }
161         });
162         fldComment = new JTextField();
163         fldComment.setToolTipText(FILE_INPUTFIELD_TOOLTIP);
164         btnComment = new JButton();
165         final JLabel lblOutput = new JLabel();
166         cbOutputEnable = new JCheckBox();
167         fldOutput = new JTextField();
168         fldOutput.setToolTipText(FILE_OUTPUTFIELD_TOOLTIP);
169
170         setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
171
172         btnStop.addActionListener(new StopActionListener());
173         btnApply.addActionListener(new ApplyActionListener());
174
175         pnlMain.setBorder(BorderFactory.createEtchedBorder());
176
177         tblDisplay.setDropMode(DropMode.INSERT_ROWS);
178         scrDisplay.setViewportView(tblDisplay);
179
180         pnlButton.setBorder(BorderFactory.createEtchedBorder());
181
182         GroupLayout gl_pnlButton = new GroupLayout(pnlButton);
183         pnlButton.setLayout(gl_pnlButton);
184         gl_pnlButton.setHorizontalGroup(
185             gl_pnlButton.createParallelGroup(Alignment.LEADING)
186             .addGroup(gl_pnlButton.createSequentialGroup()
187                 .addContainerGap()
188                 .addComponent(btnStart)
189                 .addPreferredGap(ComponentPlacement.RELATED)
190                 .addComponent(btnStop)
191                 .addPreferredGap(ComponentPlacement.RELATED, 250, Short.MAX_VALUE)
192                 .addComponent(btnDeselect)
193                 .addContainerGap())
194         );
195         gl_pnlButton.setVerticalGroup(
196             gl_pnlButton.createParallelGroup(Alignment.LEADING)
197             .addGroup(gl_pnlButton.createSequentialGroup()
198                 .addContainerGap()
199                 .addGroup(gl_pnlButton.createParallelGroup(Alignment.BASELINE)
200                     .addComponent(btnStart)
201                     .addComponent(btnStop)
202                     .addComponent(btnDeselect))
203                 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
204         );
205
206         lblId.setText("ID");
207
208         fldId.addFocusListener(new java.awt.event.FocusAdapter() {
209
210             public void focusLost(java.awt.event.FocusEvent evt) {
211                 idFieldFocusLost(evt);
212             }
213         });
214
215         lblVideo.setText("動画");
216
217         cbVideoLocal.setText("local");
218         cbVideoLocal.addItemListener(new java.awt.event.ItemListener() {
219
220             public void itemStateChanged(java.awt.event.ItemEvent evt) {
221                 useMovieLocalCheckBoxItemStateChanged(evt);
222             }
223         });
224
225         btnVideo.setText("...");
226
227         lblComment.setText("コメント");
228
229         cbCommentLocal.setText("local");
230         cbCommentLocal.addItemListener(new java.awt.event.ItemListener() {
231
232             public void itemStateChanged(java.awt.event.ItemEvent evt) {
233                 useMovieLocalCheckBoxItemStateChanged(evt);
234             }
235         });
236
237         btnComment.setText("...");
238
239         lblOutput.setText("出力");
240
241         cbOutputEnable.setText("変換");
242         cbOutputEnable.addItemListener(new java.awt.event.ItemListener() {
243
244             public void itemStateChanged(java.awt.event.ItemEvent evt) {
245                 outputConvertCheckBoxItemStateChanged(evt);
246             }
247         });
248
249
250         final GroupLayout glInputMain = new GroupLayout(pnlInputMain);
251         pnlInputMain.setLayout(glInputMain);
252         glInputMain.setHorizontalGroup(
253             glInputMain.createParallelGroup(Alignment.LEADING)
254             .addGroup(glInputMain.createSequentialGroup()
255                 .addContainerGap()
256                 .addComponent(lblId)
257                 .addPreferredGap(ComponentPlacement.RELATED)
258                 .addComponent(fldId, GroupLayout.PREFERRED_SIZE, 100, Short.MAX_VALUE)
259                 .addPreferredGap(ComponentPlacement.UNRELATED)
260                 .addComponent(cbBackLogReduce)
261                 .addPreferredGap(ComponentPlacement.UNRELATED)
262                 .addComponent(cbBackLog)
263                 .addPreferredGap(ComponentPlacement.RELATED)
264                 .addComponent(fldBackLog, GroupLayout.PREFERRED_SIZE, 150, GroupLayout.PREFERRED_SIZE)
265             )
266             .addGroup(glInputMain.createSequentialGroup()
267                 .addContainerGap()
268                 .addGroup(glInputMain.createParallelGroup(Alignment.LEADING)
269                     .addGroup(glInputMain.createSequentialGroup()
270                         .addGroup(glInputMain.createParallelGroup(Alignment.LEADING)
271                             .addComponent(lblVideo)
272                             .addComponent(lblComment)
273                             .addComponent(lblOutput))
274                         .addPreferredGap(ComponentPlacement.RELATED)
275                         .addGroup(glInputMain.createParallelGroup(Alignment.LEADING)
276                             .addGroup(glInputMain.createSequentialGroup()
277                                 .addComponent(cbVideoLocal)
278                                 .addPreferredGap(ComponentPlacement.RELATED)
279                                 .addComponent(fldVideo, GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE)
280                                 .addPreferredGap(ComponentPlacement.RELATED)
281                                 .addComponent(btnVideo))
282                             .addGroup(Alignment.TRAILING, glInputMain.createSequentialGroup()
283                                 .addGroup(glInputMain.createParallelGroup(Alignment.TRAILING)
284                                     .addGroup(Alignment.LEADING, glInputMain.createSequentialGroup()
285                                         .addComponent(cbOutputEnable)
286                                         .addPreferredGap(ComponentPlacement.RELATED)
287                                         .addComponent(fldOutput, GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE))
288                                     .addGroup(glInputMain.createSequentialGroup()
289                                         .addComponent(cbCommentLocal)
290                                         .addPreferredGap(ComponentPlacement.RELATED)
291                                         .addComponent(fldComment, GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE)))
292                                 .addPreferredGap(ComponentPlacement.RELATED)
293                                 .addComponent(btnComment))))
294                     )
295                 .addContainerGap())
296         );
297         glInputMain.setVerticalGroup(
298             glInputMain.createParallelGroup(Alignment.LEADING)
299             .addGroup(glInputMain.createSequentialGroup()
300                 .addContainerGap()
301                 .addGroup(glInputMain.createParallelGroup(Alignment.BASELINE)
302                     .addComponent(fldId, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
303                     .addComponent(lblId)
304                     .addComponent(cbBackLogReduce)
305                     .addComponent(cbBackLog)
306                     .addComponent(fldBackLog)
307                 )
308                 .addPreferredGap(ComponentPlacement.RELATED)
309                 .addGroup(glInputMain.createParallelGroup(Alignment.BASELINE)
310                     .addComponent(lblVideo)
311                     .addComponent(fldVideo, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
312                     .addComponent(btnVideo)
313                     .addComponent(cbVideoLocal))
314                 .addPreferredGap(ComponentPlacement.RELATED)
315                 .addGroup(glInputMain.createParallelGroup(Alignment.BASELINE)
316                     .addComponent(lblComment)
317                     .addComponent(fldComment, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
318                     .addComponent(btnComment)
319                     .addComponent(cbCommentLocal))
320                 .addPreferredGap(ComponentPlacement.RELATED)
321                 .addGroup(glInputMain.createParallelGroup(Alignment.BASELINE)
322                     .addComponent(lblOutput)
323                     .addComponent(fldOutput, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
324                     .addComponent(cbOutputEnable))
325             )
326         );
327
328         tbpInput.add("メイン", pnlInputMain);
329         tbpInput.add("ffmpeg", pnlInputFfmpeg);
330
331         // 入力部のボタンやメッセージ表示部
332         fldInputMessage.setEditable(false);
333         fldInputMessage.setEnabled(false);
334         fldInputMessage.setBorder(BorderFactory.createEmptyBorder());
335
336         final JPanel pnlInputButton = new JPanel();
337         final GroupLayout glInputButton = new GroupLayout(pnlInputButton);
338         pnlInputButton.setLayout(glInputButton);
339         glInputButton.setHorizontalGroup(glInputButton.createSequentialGroup()
340             .addContainerGap()
341             .addComponent(fldInputMessage, GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
342             .addPreferredGap(ComponentPlacement.UNRELATED)
343             .addComponent(btnApply)
344             .addContainerGap()
345         );
346         glInputButton.setVerticalGroup(glInputButton.createSequentialGroup()
347             .addContainerGap()
348             .addGroup(glInputButton.createParallelGroup(Alignment.BASELINE)
349                 .addComponent(fldInputMessage, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
350                 .addComponent(btnApply)
351             )
352             .addContainerGap()
353         );
354
355         // 画面下半分の入力部分
356         final JPanel pnlInputAll = new JPanel();
357         pnlInputAll.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
358         final GroupLayout glInputAll = new GroupLayout(pnlInputAll);
359         pnlInputAll.setLayout(glInputAll);
360         glInputAll.setHorizontalGroup(glInputAll.createParallelGroup()
361             .addComponent(tbpInput)
362             .addComponent(pnlInputButton)
363         );
364         glInputAll.setVerticalGroup(glInputAll.createSequentialGroup()
365             .addComponent(tbpInput)
366             .addComponent(pnlInputButton)
367         );
368
369         GroupLayout gl_pnlMain = new GroupLayout(pnlMain);
370         pnlMain.setLayout(gl_pnlMain);
371         gl_pnlMain.setHorizontalGroup(
372             gl_pnlMain.createParallelGroup(Alignment.LEADING)
373             .addGroup(Alignment.TRAILING, gl_pnlMain.createSequentialGroup()
374                 .addContainerGap()
375                 .addGroup(gl_pnlMain.createParallelGroup(Alignment.TRAILING)
376                     .addComponent(scrDisplay, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 480, Short.MAX_VALUE)
377                     .addComponent(pnlButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
378                     .addComponent(pnlInputAll, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
379                 )
380                 .addContainerGap())
381         );
382         gl_pnlMain.setVerticalGroup(
383             gl_pnlMain.createParallelGroup(Alignment.LEADING)
384             .addGroup(Alignment.TRAILING, gl_pnlMain.createSequentialGroup()
385                 .addContainerGap()
386                 .addComponent(scrDisplay, GroupLayout.DEFAULT_SIZE, 197, Short.MAX_VALUE)
387                 .addPreferredGap(ComponentPlacement.RELATED)
388                 .addComponent(pnlButton, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
389                 .addPreferredGap(ComponentPlacement.RELATED)
390                 .addComponent(pnlInputAll, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
391                 .addContainerGap()
392             )
393         );
394
395
396         JMenuBar menuBar = initMenuBar();
397         setJMenuBar(menuBar);
398
399         GroupLayout layout = new GroupLayout(getContentPane());
400         getContentPane().setLayout(layout);
401         layout.setHorizontalGroup(
402             layout.createParallelGroup(Alignment.LEADING)
403             .addComponent(pnlMain, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
404         );
405         layout.setVerticalGroup(
406             layout.createParallelGroup(Alignment.LEADING)
407             .addComponent(pnlMain, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
408         );
409
410         pack();
411         initInputPanel();
412         pnlMain.setTransferHandler(new DownloadListTransferHandler());
413         tblDisplay.setTransferHandler(new TableTransferHandler());
414
415         final Config p = Config.INSTANCE;
416         final int thDownload = p.getSystemDownloadThread();
417         final int secDownload = p.getSystemDownloadWait();
418         final int thConvert = p.getSystemConvertThread();
419         taskManager = new TaskManage(thDownload, secDownload,thConvert, new GuiTaskManageListener());
420     }
421
422     private class GuiTaskManageListener implements TaskManageListener {
423
424         @Override
425         public void process(final int id, final TaskKind kind, final TaskStatus status, final double percentage,
426                 final String message) {
427             SwingUtilities.invokeLater(new Runnable() {
428
429                 @Override
430                 public void run() {
431                     targetModel.setStatus(id, kind, status, percentage, message);
432                 }
433             });
434         }
435     }
436
437     private class StopActionListener implements ActionListener {
438
439         @Override
440         public void actionPerformed(ActionEvent e) {
441             final int row = tblDisplay.getSelectedRow();
442             final Target t = targetModel.getTarget(row);
443             final boolean res = taskManager.cancel(t.getRowId());
444             logger.log(Level.FINE, "停止: {0} {1}", new Object[]{t.getVideoId(), res});
445             if (res) {
446                 targetModel.setStatus(t.getRowId(), null, TaskStatus.CANCELLED, -1.0, "キャンセル");
447             }
448         }
449     }
450
451     private class ApplyActionListener implements ActionListener {
452
453         @Override
454         public void actionPerformed(ActionEvent e) {
455             try {
456                 final DownloadProfile downProf = new InqubusDownloadProfile();
457                 final String id = Util.getVideoId(fldId.getText());
458                 final InqubusConvertProfile convProf = new InqubusConvertProfile();
459                 logger.log(Level.INFO, downProf.toString());
460                 logger.log(Level.INFO, convProf.toString());
461                 final RequestProcess rp = new RequestProcess(downProf, id, convProf);
462                 taskManager.add(rp);
463                 targetModel.addTarget(new Target(rp));
464                 initInputPanel();
465             } catch (Throwable th) {
466                 logger.log(Level.SEVERE, null, th);
467                 JOptionPane.showMessageDialog(MainFrame.this, th.getMessage(), "中断しました", JOptionPane.ERROR_MESSAGE);
468             }
469         }
470     }
471     /** This method is called from within the constructor to
472      * initialize the form.
473      * WARNING: Do NOT modify this code. The content of this method is
474      * always regenerated by the Form Editor.
475      */
476     @SuppressWarnings("unchecked")
477     // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
478     private void initComponents() {
479     }// </editor-fold>//GEN-END:initComponents
480
481     private File searchFileMatchId(final File dir, final String id) {
482         // TODO 候補は複数返すようにして、その後の対処は呼び出しもとで行ってもらった方が良いかも
483         if (id.isEmpty()) {
484             return null;
485         }
486
487         final File[] lists = dir.listFiles(new FilenameFilter() {
488
489             final Pattern pattern = Pattern.compile(id + "\\D");
490
491             @Override
492             public boolean accept(File dir, String name) {
493                 return pattern.matcher(name).find();
494             }
495         });
496
497         if (lists.length == 1) {
498             return lists[0];
499         } else if (lists.length > 1) {
500             throw new UnsupportedOperationException();
501         } else {
502             return null;
503         }
504     }
505
506     /**
507      * 動画, コメントの"local"チェックボックス更新時の処理.
508      */
509     private void useMovieLocalCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_useMovieLocalCheckBoxItemStateChanged
510         final Config p = Config.INSTANCE;
511
512         final ItemSelectable source = evt.getItemSelectable();
513
514         JButton btn;
515         JTextField field;
516         File dir;
517         if (source == cbVideoLocal) {
518             btn = btnVideo;
519             field = fldVideo;
520             dir = new File(p.getVideoDir());
521         } else {
522             btn = btnComment;
523             field = fldComment;
524             dir = new File(p.getCommentDir());
525         }
526
527         final boolean useLocal = (evt.getStateChange() == ItemEvent.SELECTED);
528         btn.setEnabled(useLocal);
529
530         String text;
531         if (useLocal) {
532             final File f = searchFileMatchId(dir, fldId.getText());
533             if (f != null) {
534                 text = f.getPath();
535             } else {
536                 text = "";
537             }
538         } else {
539             text = p.getVideoFileNamePattern();
540         }
541         field.setText(text);
542
543     }//GEN-LAST:event_useMovieLocalCheckBoxItemStateChanged
544
545     private void outputConvertCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_outputConvertCheckBoxItemStateChanged
546         final boolean convert = (evt.getStateChange() == ItemEvent.SELECTED);
547         fldOutput.setEnabled(convert);
548     }//GEN-LAST:event_outputConvertCheckBoxItemStateChanged
549
550     private void idFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_idFieldFocusLost
551         final Config p = Config.INSTANCE;
552         final String id = fldId.getText();
553         if (id.isEmpty()) {
554             return;
555         }
556
557         if (cbVideoLocal.isSelected() && fldVideo.getText().isEmpty()) {
558             final File dir = new File(p.getVideoDir());
559             final File file = searchFileMatchId(dir, id);
560             if (file != null) {
561                 fldVideo.setText(file.getPath());
562             }
563         }
564
565         if (cbCommentLocal.isSelected() && fldComment.getText().isEmpty()) {
566             final File dir = new File(p.getCommentDir());
567             final File file = searchFileMatchId(dir, id);
568             if (file != null) {
569                 fldComment.setText(file.getPath());
570             }
571         }
572
573     }//GEN-LAST:event_idFieldFocusLost
574     // Variables declaration - do not modify//GEN-BEGIN:variables
575     private final JTable tblDisplay;
576     // ボタン領域
577     private final JButton btnStart = new JButton("開始");
578     private final JButton btnStop = new JButton("停止");
579     private final JButton btnDeselect = new JButton("選択解除");
580     //入力領域 - 標準
581     private final JTextField fldId = new JTextField();
582     private final JCheckBox cbBackLogReduce = new JCheckBox("コメ数減少");
583     private final JCheckBox cbBackLog = new JCheckBox("過去ログ");
584     private final JTextField fldBackLog = new JTextField();
585     private final JCheckBox cbVideoLocal;
586     private final JTextField fldVideo;
587     private final JButton btnVideo;
588     private final JCheckBox cbCommentLocal;
589     private final JTextField fldComment;
590     private final JButton btnComment;
591     private final JCheckBox cbOutputEnable;
592     private final JTextField fldOutput;
593     // 適用
594     private final JTextField fldInputMessage = new JTextField();
595     private final JButton btnApply = new JButton("適用");
596     // End of variables declaration//GEN-END:variables
597
598     private void initInputPanel() {
599         final Config p = Config.INSTANCE;
600
601         fldId.setText("");
602         fldBackLog.setEnabled(false);
603         cbBackLog.setEnabled(true);
604
605         final boolean movieLocal = p.getVideoUseLocal();
606         cbVideoLocal.setSelected(movieLocal);
607         btnVideo.setEnabled(movieLocal);
608         if (!movieLocal) {
609             fldVideo.setText(p.getVideoFileNamePattern());
610         }
611
612         final boolean commentLocal = p.getCommentUseLocal();
613         cbCommentLocal.setSelected(commentLocal);
614         btnComment.setEnabled(commentLocal);
615         if (!commentLocal) {
616             fldComment.setText(p.getCommentFileNamePattern());
617         }
618
619         final boolean convert = p.getOutputEnable();
620         cbOutputEnable.setSelected(convert);
621         fldOutput.setEnabled(convert);
622         fldOutput.setText(p.getOutputFileNamePattern());
623
624     }
625
626     private JMenuBar initMenuBar() {
627         final JMenuBar menuBar = new JMenuBar();
628
629         final JMenu mnFile = new JMenu("ファイル(F)");
630         menuBar.add(mnFile);
631
632         final JMenuItem itExit = new JMenuItem("終了(X)", KeyEvent.VK_X);
633         itExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
634         itExit.addActionListener(new ActionListener() {
635
636             @Override
637             public void actionPerformed(ActionEvent e) {
638                 throw new UnsupportedOperationException("Not supported yet.");
639             }
640         });
641         mnFile.add(itExit);
642
643         final JMenu mnTool = new JMenu("ツール(T)");
644         menuBar.add(mnTool);
645
646         final JMenuItem itOption = new JMenuItem("オプション(O)...", KeyEvent.VK_O);
647         // TODO ショートカットキー
648         itOption.addActionListener(new ActionListener() {
649
650             @Override
651             public void actionPerformed(ActionEvent e) {
652                 final yukihane.inqubus.gui.ConfigDialog dlg = new yukihane.inqubus.gui.ConfigDialog(MainFrame.this);
653                 dlg.setLocationRelativeTo(MainFrame.this);
654                 dlg.setModal(true);
655                 dlg.setVisible(true);
656             }
657         });
658         mnTool.add(itOption);
659
660         final JMenu mnHelp = new JMenu("ヘルプ(H)");
661         menuBar.add(mnHelp);
662
663         final JMenuItem itAbout = new JMenuItem("このソフトウェアについて(A)...", KeyEvent.VK_A);
664         itAbout.addActionListener(new ActionListener() {
665
666             @Override
667             public void actionPerformed(ActionEvent e) {
668                 MainFrame_AboutBox dlg = new MainFrame_AboutBox(MainFrame.this);
669                 dlg.pack();
670                 dlg.setLocationRelativeTo(MainFrame.this);
671                 dlg.setModal(true);
672                 dlg.setVisible(true);
673             }
674         });
675         mnHelp.add(itAbout);
676
677         return menuBar;
678     }
679
680     private class DownloadProgressListener implements WorkerListener<DownloadResult, DownloadProgress> {
681
682         @Override
683         public void process(DownloadProgress progress) {
684             throw new UnsupportedOperationException("Not supported yet.");
685         }
686
687         @Override
688         public void cancelled() {
689             throw new UnsupportedOperationException("Not supported yet.");
690         }
691
692         @Override
693         public void done(DownloadResult result) {
694             throw new UnsupportedOperationException("Not supported yet.");
695         }
696
697         @Override
698         public void error(Throwable th) {
699             throw new UnsupportedOperationException("Not supported yet.");
700         }
701     }
702
703     private class ConvertProgressListener implements WorkerListener<ConvertResult, ConvertProgress> {
704
705         @Override
706         public void process(ConvertProgress progress) {
707             throw new UnsupportedOperationException("Not supported yet.");
708         }
709
710         @Override
711         public void cancelled() {
712             throw new UnsupportedOperationException("Not supported yet.");
713         }
714
715         @Override
716         public void done(ConvertResult result) {
717             throw new UnsupportedOperationException("Not supported yet.");
718         }
719
720         @Override
721         public void error(Throwable th) {
722             throw new UnsupportedOperationException("Not supported yet.");
723         }
724     }
725
726
727
728     private class DownloadListTransferHandler extends TransferHandler {
729
730         private static final long serialVersionUID = 1L;
731         private final Pattern movieIdPattern = Pattern.compile("(\\w\\w\\d+)");
732
733         @Override
734         public boolean canImport(TransferHandler.TransferSupport support) {
735             Transferable transferable = support.getTransferable();
736             if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
737                     || transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
738                 return true;
739             }
740             return false;
741         }
742
743         @Override
744         public boolean importData(TransferHandler.TransferSupport support) {
745 //            try {
746 //                Transferable transferable = support.getTransferable();
747 //                if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
748 //                    @SuppressWarnings("unchecked")
749 //                    final List<File> data = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
750 //                    Collection<Target> targets = Target.from(data);
751 //                    targetModel.addTarget(targets);
752 //                } else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
753 //                    String data = (String) transferable.getTransferData(DataFlavor.stringFlavor);
754 //                    Matcher matcher = movieIdPattern.matcher(data);
755 //                    if (matcher.find()) {
756 //                        String movieId = matcher.group(1);
757 //                        Target target = Target.fromId(movieId);
758 //                        targetModel.addTarget(target);
759 //                    } else {
760 //                        return false;
761 //                    }
762 //
763 //                }
764 //                return false;
765 //            } catch (Exception e) {
766 //                logger.log(Level.SEVERE, null, e);
767 //                return false;
768 //            }
769             // TODO 上記実装見直し(Locationは削除された)
770             return false;
771         }
772     }
773
774     private class TableTransferHandler extends DownloadListTransferHandler {
775
776         private static final long serialVersionUID = 1L;
777
778         @Override
779         public boolean canImport(TransferHandler.TransferSupport support) {
780             return super.canImport(support);
781         }
782
783         @Override
784         public boolean importData(TransferHandler.TransferSupport support) {
785             return super.importData(support);
786         }
787     }
788
789     /*
790      * ここからDownloadProfile作成用クラスの定義
791      */
792
793     private class InqubusDownloadProfile implements DownloadProfile {
794
795         private final LoginProfile loginProfile;
796         private final ProxyProfile proxyProfile;
797         private final VideoProfile videoProfile;
798         private final CommentProfile commentProfile;
799         private final GeneralProfile generalProfile;
800
801         private InqubusDownloadProfile() {
802             this.loginProfile = new InqubusLoginProfile();
803             this.proxyProfile = new InqubusProxyProfile();
804             this.videoProfile = new InqubusVideoProfile();
805             this.commentProfile = new InqubusCommentProfile();
806             this.generalProfile = new InqubusGeneralProfile();
807         }
808
809         @Override
810         public LoginProfile getLoginInfo() {
811             return this.loginProfile;
812         }
813
814         @Override
815         public ProxyProfile getProxyProfile() {
816             return this.proxyProfile;
817         }
818
819         @Override
820         public VideoProfile getVideoProfile() {
821             return this.videoProfile;
822         }
823
824         @Override
825         public CommentProfile getCommentProfile() {
826             return this.commentProfile;
827         }
828
829         @Override
830         public GeneralProfile getGeneralProfile() {
831             return this.generalProfile;
832         }
833
834         @Override
835         public String toString(){
836             return ToStringBuilder.reflectionToString(this);
837         }
838     }
839
840     private class InqubusLoginProfile implements LoginProfile {
841         private final String mail;
842         private final String password;
843
844         private InqubusLoginProfile(){
845             final Config p = Config.INSTANCE;
846             this.mail = p.getId();
847             this.password = p.getPassword();
848         }
849
850         @Override
851         public String getMail() {
852             return this.mail;
853         }
854
855         @Override
856         public String getPassword() {
857             return this.password;
858         }
859
860         @Override
861         public String toString(){
862             return ToStringBuilder.reflectionToString(this);
863         }
864     }
865
866     private class InqubusProxyProfile implements ProxyProfile {
867         private final boolean use;
868         private final String host;
869         private final int port;
870
871         private InqubusProxyProfile(){
872             final Config p = Config.INSTANCE;
873             this.use = p.getProxyUse();
874             this.host = p.getProxyHost();
875             final String pp = p.getProxyPort();
876             this.port = StringUtils.isBlank(pp) ? -1 : Integer.parseInt(pp);
877         }
878
879         @Override
880         public boolean use() {
881             return this.use;
882         }
883
884         @Override
885         public String getHost() {
886             return this.host;
887         }
888
889         @Override
890         public int getPort() {
891             return this.port;
892         }
893
894         @Override
895         public String toString(){
896             return ToStringBuilder.reflectionToString(this);
897         }
898     }
899
900     private class InqubusVideoProfile implements VideoProfile {
901         private final boolean download;
902         private final File dir;
903         private final String fileName;
904         private final File localFile;
905
906         private InqubusVideoProfile(){
907             final Config p = Config.INSTANCE;
908             this.download = !cbVideoLocal.isSelected();
909             if (this.download) {
910                 this.dir = new File(p.getVideoDir());
911                 this.fileName = fldVideo.getText();
912                 this.localFile = null;
913             } else {
914                 this.dir = null;
915                 this.fileName = null;
916                 this.localFile = new File(fldVideo.getText());
917             }
918         }
919
920         @Override
921         public boolean isDownload() {
922             return this.download;
923         }
924
925         @Override
926         public File getDir() {
927             return this.dir;
928         }
929
930         @Override
931         public String getFileName() {
932             return this.fileName;
933         }
934
935         @Override
936         public File getLocalFile() {
937             return this.localFile;
938         }
939
940         @Override
941         public String toString(){
942             return ToStringBuilder.reflectionToString(this);
943         }
944     }
945
946     private class InqubusCommentProfile implements CommentProfile {
947         private final boolean download;
948         private final File dir;
949         private final String fileName;
950         private final File localFile;
951         private final int lengthRelatedCommentSize;
952         private final boolean disablePerMinComment;
953         private final int perMinCommentSize;
954         private final long backLogPoint;
955
956         private InqubusCommentProfile() {
957             final Config p = Config.INSTANCE;
958             this.download = !cbCommentLocal.isSelected();
959             if (this.download) {
960                 this.dir = new File(p.getCommentDir());
961                 this.fileName = fldComment.getText();
962                 this.localFile = null;
963             } else {
964                 this.dir = null;
965                 this.fileName = null;
966                 this.localFile = new File(fldComment.getText());
967             }
968
969             if(cbBackLog.isSelected()) {
970                 try {
971                     this.backLogPoint = WayBackTimeParser.parse(fldBackLog.getText());
972                 } catch (IOException ex) {
973                     throw new IllegalArgumentException("過去ログ時刻指定が誤っています。", ex);
974                 }
975             } else {
976                 this.backLogPoint = -1L;
977             }
978
979             this.disablePerMinComment = cbBackLogReduce.isSelected();
980             this.lengthRelatedCommentSize
981                     = (p.getCommentSizeAutosize()) ? -1 : Integer.parseInt(p.getCommentSizeManual());
982             this.perMinCommentSize
983                     = (p.getCommentMinSizeAutosize()) ? -1 : Integer.parseInt(p.getCommentMinSizeManual());
984         }
985
986         @Override
987         public boolean isDownload() {
988             return this.download;
989         }
990
991         @Override
992         public File getDir() {
993             return this.dir;
994         }
995
996         @Override
997         public String getFileName() {
998             return this.fileName;
999         }
1000
1001         @Override
1002         public File getLocalFile() {
1003             return this.localFile;
1004         }
1005
1006         @Override
1007         public int getLengthRelatedCommentSize() {
1008             return this.lengthRelatedCommentSize;
1009         }
1010
1011         @Override
1012         public boolean isDisablePerMinComment() {
1013             return this.disablePerMinComment;
1014         }
1015
1016         @Override
1017         public int getPerMinCommentSize() {
1018             return this.perMinCommentSize;
1019         }
1020
1021         @Override
1022         public long getBackLogPoint() {
1023             return this.backLogPoint;
1024         }
1025
1026         @Override
1027         public String toString(){
1028             return ToStringBuilder.reflectionToString(this);
1029         }
1030     }
1031
1032     private class InqubusGeneralProfile implements GeneralProfile {
1033         private final String replaceFrom;
1034         private final String replaceTo;
1035         private InqubusGeneralProfile() {
1036             final Config p = Config.INSTANCE;
1037             this.replaceFrom = p.getReplaceFrom();
1038             this.replaceTo = p.getReplaceTo();
1039         }
1040
1041         @Override
1042         public String getReplaceFrom() {
1043             return this.replaceFrom;
1044         }
1045
1046         @Override
1047         public String getReplaceTo() {
1048             return this.replaceTo;
1049         }
1050
1051         @Override
1052         public String toString(){
1053             return ToStringBuilder.reflectionToString(this);
1054         }
1055     }
1056
1057     /*
1058      * ここからConvertProfile作成用クラスの定義
1059      */
1060     private class InqubusConvertProfile implements ConvertProfile {
1061         private final OutputProfile outputProfile;
1062         private final GeneralProfile generalProfile;
1063         private final FfmpegProfile ffmpegProfile;
1064         private final boolean convert;
1065         private final File ffmpeg;
1066         private final boolean vhookDisabled;
1067         private final boolean commentOverlay;
1068         private final File vhook;
1069         private final File tmpDir;
1070         private final File font;
1071         private final int fontIndex;
1072         private final boolean commentOpaque;
1073         private final boolean disableFontSizeArrange;
1074         private final int shadowIndex;
1075         private final boolean showConvrting;
1076         private final int maxNumOfComment;
1077         private final HideCondition ngSetting;
1078
1079         private InqubusConvertProfile() throws IOException {
1080             final Config p = Config.INSTANCE;
1081             this.outputProfile = new InqubusOutputProfile();
1082             this.generalProfile = new InqubusGeneralProfile();
1083             this.ffmpegProfile = new InqubusFfmpegProfile();
1084             this.convert = cbOutputEnable.isSelected();
1085             this.ffmpeg = new File(p.getFfmpegPath());
1086             // TODO コンフィグに設定なし
1087             this.vhookDisabled = false;
1088             this.commentOverlay = p.getOutputCommentOverlay();
1089             this.vhook = new File(p.getFfmpegDllPath());
1090             this.tmpDir = new File(p.getSystemTempDir());
1091             this.font = new File(p.getFontPath());
1092             this.fontIndex = Integer.parseInt(p.getFontIndex());
1093             this.commentOpaque = p.getCommentOpaque();
1094             this.disableFontSizeArrange = p.getFontSizeArrangeDisable();
1095             this.shadowIndex = p.getFontShadow();
1096             // TODO コンフィグに設定なし
1097             this.showConvrting = true;
1098             this.maxNumOfComment = (p.getCommentDisplaySizeDefault()) ? -1 : Integer.parseInt(p.
1099                     getCommentDisplaySizeManual());
1100             this.ngSetting = new InqubusHideCondition();
1101         }
1102
1103         @Override
1104         public OutputProfile getOutputProfile() {
1105             return this.outputProfile;
1106         }
1107
1108         @Override
1109         public GeneralProfile getGeneralProfile() {
1110             return this.generalProfile;
1111         }
1112
1113         @Override
1114         public FfmpegProfile getFfmpegOption() {
1115             return this.ffmpegProfile;
1116         }
1117
1118         @Override
1119         public boolean isConvert() {
1120             return this.convert;
1121         }
1122
1123         @Override
1124         public File getFfmpeg() {
1125             return this.ffmpeg;
1126         }
1127
1128         @Override
1129         public boolean isVhookDisabled() {
1130             return this.vhookDisabled;
1131         }
1132
1133         @Override
1134         public boolean isCommentOverlay() {
1135             return this.commentOverlay;
1136         }
1137
1138         @Override
1139         public File getVhook() {
1140             return this.vhook;
1141         }
1142
1143         @Override
1144         public File getTempDir() {
1145             return this.tmpDir;
1146         }
1147
1148         @Override
1149         public File getFont() {
1150             return this.font;
1151         }
1152
1153         @Override
1154         public int getFontIndex() {
1155             return this.fontIndex;
1156         }
1157
1158         @Override
1159         public boolean isCommentOpaque() {
1160             return this.commentOpaque;
1161         }
1162
1163         @Override
1164         public boolean isDisableFontSizeArrange() {
1165             return this.disableFontSizeArrange;
1166         }
1167
1168         @Override
1169         public int getShadowIndex() {
1170             return this.shadowIndex;
1171         }
1172
1173         @Override
1174         public boolean isShowConverting() {
1175             return this.showConvrting;
1176         }
1177
1178         @Override
1179         public int getMaxNumOfComment() {
1180             return this.maxNumOfComment;
1181         }
1182
1183         @Override
1184         public HideCondition getNgSetting() {
1185             return this.ngSetting;
1186         }
1187
1188         @Override
1189         public String toString(){
1190             return ToStringBuilder.reflectionToString(this);
1191         }
1192     }
1193
1194     private class InqubusOutputProfile implements OutputProfile {
1195         private final File dir;
1196         private final String fileName;
1197         private final String videoId;
1198         private final String title;
1199
1200
1201         private InqubusOutputProfile(){
1202             final Config p = Config.INSTANCE;
1203             this.dir = new File(p.getOutputDir());
1204             this.fileName = fldOutput.getText();
1205             // TODO この時点でのID/Titleはどうするか…
1206             this.videoId = "";
1207             this.title = "";
1208         }
1209
1210         @Override
1211         public File getDir() {
1212             return this.dir;
1213         }
1214
1215         @Override
1216         public String getFileName() {
1217             return this.fileName;
1218         }
1219
1220         @Override
1221         public String getVideoId() {
1222             return this.videoId;
1223         }
1224
1225         @Override
1226         public String getTitile() {
1227             return this.title;
1228         }
1229
1230         @Override
1231         public String toString(){
1232             return ToStringBuilder.reflectionToString(this);
1233         }
1234     }
1235
1236     private class InqubusFfmpegProfile implements FfmpegProfile {
1237         private final String extOption;
1238         private final String inOption;
1239         private final String mainOption;
1240         private final String outOption;
1241         private final String avOption;
1242         private final boolean resize;
1243         private final int resizeWidth;
1244         private final int resizeHeight;
1245         private final boolean adjustRatio;
1246
1247         private InqubusFfmpegProfile() throws IOException {
1248             // TODO FFMPEGオプションは、後でメイン画面でも設定できるようにするかも
1249             final Config p = Config.INSTANCE;
1250             final String opf = p.getFfmpegOptionFile();
1251             if (StringUtils.isNotEmpty(opf)) {
1252                 final File file = new File(opf);
1253                 final FfmpegOption ffop = FfmpegOption.load(file);
1254                 this.extOption = ffop.getExtOption();
1255                 this.inOption = ffop.getInOption();
1256                 this.mainOption = ffop.getMainOption();
1257                 this.outOption = ffop.getMainOption();
1258                 this.avOption = ffop.getAvfilterOption();
1259                 this.resize = ffop.isResize();
1260                 this.resizeWidth = ffop.getResizeWidth();
1261                 this.resizeHeight = ffop.getResizeHeight();
1262                 this.adjustRatio = ffop.isAdjustRatio();
1263                 return;
1264             }
1265             this.extOption = p.getFfmpegExtension();
1266             this.inOption = p.getFfmpegInOption();
1267             this.mainOption = p.getFfmpegMainOption();
1268             this.outOption = p.getFfmpegOutOption();
1269             this.avOption = p.getFfmpegAvOption();
1270             this.resize = p.getFfmpegResizeEnable();
1271             this.resizeWidth = Integer.parseInt(p.getFfmpegResizeWidth());
1272             this.resizeHeight = Integer.parseInt(p.getFfmpegResizeHeight());
1273             this.adjustRatio = p.getFfmpegKeepAspect();
1274         }
1275
1276         @Override
1277         public String getExtOption() {
1278             return this.extOption;
1279         }
1280
1281         @Override
1282         public String getInOption() {
1283             return this.inOption;
1284         }
1285
1286         @Override
1287         public String getMainOption() {
1288             return this.mainOption;
1289         }
1290
1291         @Override
1292         public String getOutOption() {
1293             return this.outOption;
1294         }
1295
1296         @Override
1297         public String getAvfilterOption() {
1298             return this.avOption;
1299         }
1300
1301         @Override
1302         public boolean isResize() {
1303             return this.resize;
1304         }
1305
1306         @Override
1307         public int getResizeWidth() {
1308             return this.resizeWidth;
1309         }
1310
1311         @Override
1312         public int getResizeHeight() {
1313             return this.resizeHeight;
1314         }
1315
1316         @Override
1317         public boolean isAdjustRatio() {
1318             return this.adjustRatio;
1319         }
1320
1321         @Override
1322         public String toString(){
1323             return ToStringBuilder.reflectionToString(this);
1324         }
1325     }
1326
1327     private class InqubusHideCondition implements ConvertProfile.HideCondition{
1328
1329         @Override
1330         public String getWord() {
1331             // TODO
1332             return "";
1333         }
1334
1335         @Override
1336         public String getId() {
1337             // TODO
1338             return "";
1339         }
1340
1341         @Override
1342         public String toString(){
1343             return ToStringBuilder.reflectionToString(this);
1344         }
1345     }
1346 }