OSDN Git Service

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