OSDN Git Service

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