001    /////////////////////////////////////////////////
002    //This file is part of Sears project.
003    //Subtitle Editor And Re-Synch
004    //A tool to easily modify and resynch movies subtitles.
005    /////////////////////////////////////////////////
006    //This program is free software; 
007    //you can redistribute it and/or modify it under the terms 
008    //of the GNU General Public License 
009    //as published by the Free Software Foundation; 
010    //either version 2 of the License, or (at your option) any later version.
011    /////////////////////////////////////////////////
012    //Sears project is available under sourceforge
013    //at adress: http://sourceforge.net/projects/sears/
014    //Copyright (C) 2005 Booba Skaya
015    //Mail: booba.skaya@gmail.com
016    /////////////////////////////////////////////////
017    
018    package sears.gui;
019    
020    import java.awt.BorderLayout;
021    import java.awt.Component;
022    import java.awt.Dimension;
023    import java.awt.GridLayout;
024    import java.awt.Point;
025    import java.awt.Toolkit;
026    import java.awt.Window;
027    import java.awt.datatransfer.DataFlavor;
028    import java.awt.datatransfer.Transferable;
029    import java.awt.datatransfer.UnsupportedFlavorException;
030    import java.awt.event.ActionEvent;
031    import java.awt.event.ActionListener;
032    import java.awt.event.MouseAdapter;
033    import java.awt.event.MouseEvent;
034    import java.awt.event.MouseListener;
035    import java.awt.event.WindowEvent;
036    import java.awt.event.WindowListener;
037    import java.io.File;
038    import java.io.IOException;
039    import java.util.ArrayList;
040    import java.util.Date;
041    import java.util.HashMap;
042    import java.util.List;
043    import java.util.Locale;
044    import java.util.StringTokenizer;
045    
046    import javax.swing.BorderFactory;
047    import javax.swing.ImageIcon;
048    import javax.swing.JButton;
049    import javax.swing.JComponent;
050    import javax.swing.JFrame;
051    import javax.swing.JMenu;
052    import javax.swing.JMenuBar;
053    import javax.swing.JMenuItem;
054    import javax.swing.JOptionPane;
055    import javax.swing.JPanel;
056    import javax.swing.JPopupMenu;
057    import javax.swing.JScrollPane;
058    import javax.swing.JSlider;
059    import javax.swing.JSplitPane;
060    import javax.swing.JTable;
061    import javax.swing.JToolBar;
062    import javax.swing.SwingUtilities;
063    import javax.swing.TransferHandler;
064    import javax.swing.UIManager;
065    import javax.swing.UnsupportedLookAndFeelException;
066    import javax.swing.border.EmptyBorder;
067    import javax.swing.event.ChangeEvent;
068    import javax.swing.event.ChangeListener;
069    import javax.swing.filechooser.FileFilter;
070    import javax.swing.table.TableColumn;
071    
072    import sears.Version;
073    import sears.file.FileSystemAccess;
074    import sears.file.SrtFile;
075    import sears.file.Subtitle;
076    import sears.file.SubtitleFile;
077    import sears.file.exception.io.FileConversionException;
078    import sears.gui.resources.SearsResources;
079    import sears.gui.search.FindBox;
080    import sears.gui.search.FindDialog;
081    import sears.gui.undo.SearsUndoAction;
082    import sears.gui.undo.SearsUndoManager;
083    import sears.search.core.FindModule;
084    import sears.tools.DialogUtils;
085    import sears.tools.SearsAction;
086    import sears.tools.SearsProperties;
087    import sears.tools.SearsResourceBundle;
088    import sears.tools.Trace;
089    import sears.tools.Updater;
090    import sears.tools.Utils;
091    import sears.tools.player.DefaultPlayer;
092    import sears.tools.player.PlayerException;
093    import sears.tools.player.PlayerInterface;
094    import sears.tools.player.VLCPlayer;
095    
096    /**
097     * Class MainWindow.
098     * <br><b>Summary:</b><br>
099     * This is the main Interface of the sears project.
100     * It contains a JTable to displays the subtitles, and menus to interact and launch actions.
101     */
102    public class MainWindow extends JFrame implements MouseListener, WindowListener {
103            private static final long serialVersionUID = -6575979899788187615L;
104    
105            /** The instance of the mainWindow */
106            public static MainWindow instance;
107    
108            /**The menuBar*/
109            private JMenuBar jMenuBar;
110    
111            /**The subtitle file that is currently opened by Sears*/
112            private SubtitleFile subtitleFile;
113    
114            /**The main panel.*/
115            private JPanel mainPanel;
116    
117            /**The JTable that contains the subtitles.*/
118            private JTable table;
119    
120            /**The JTableModel that is used by the JTable.*/
121            private SubtitleTableModel tableModel;
122    
123            /**The list of subtitles actually in the subtitle file opened*/
124            private ArrayList<Subtitle> subtitleList;
125    
126            /**The application icon*/
127            private ImageIcon searsIcon;
128    
129            /** The application 'favicon', use in frame and dialog title bar (Windows and Linux only) */
130            private ImageIcon searsFavicon;
131    
132            /**The Actions hashMap where key is (String) actionId and Value is corresponding SearsAction.*/
133            private HashMap<String, SearsAction> actions;
134    
135            /**The JToolBar*/
136            private JToolBar jToolBar;
137    
138            /**The JPopupMenu*/
139            private JPopupMenu jPopupMenu;
140    
141            /**The Player*/
142            protected PlayerInterface player = null;
143    
144            /**The JPanelVideoControler that controls the video.*/
145            private JPanelVideoControler jPanelVideoControler;
146    
147            /**The full path to the player executable.*/
148            private static String playerVideoFile = null;
149    
150            /** (<b>ArrayList<File></b>) recentFiles: The recentFiles */
151            private ArrayList<File> recentFiles = null;
152    
153            /** (<b>JMenu</b>) recentFileMenu: The recentFileMenu */
154            private JMenu recentFileMenu;
155            
156            /** (<b>int</b>) RECENT_FILE_NUMBER: The max number of recent files to be displayed.*/
157            private static final int RECENT_FILE_NUMBER = 10;
158    
159            /** (<b>SearsUndoManager</b>) undoManager: The undoManager */
160            private final SearsUndoManager undoManager;
161            
162            /**The Actions Keys*/
163            /**Open action key, to open a subtitle file*/
164            protected static String ACTION_KEY_OPEN = "open";
165    
166            /**Save action key, to save current file.*/
167            protected static String ACTION_KEY_SAVE = "save";
168    
169            /**SaveAs action key, to save current file.*/
170            protected static String ACTION_KEY_SAVE_AS = "saveAs";
171    
172            /**Quit action key, to save current file.*/
173            protected static String ACTION_KEY_QUIT = "quit";
174    
175            /**Delay action key, to delay ST.*/
176            protected static String ACTION_KEY_DELAY = "delay";
177    
178            /**Resynch action key, to resynch ST.*/
179            protected static String ACTION_KEY_RESYNCH = "resynch";
180    
181            /**Split action key, to split ST.*/
182            protected static String ACTION_KEY_SPLIT = "split";
183    
184            /**Append action key, to append ST.*/
185            protected static String ACTION_KEY_APPEND = "append";
186    
187            /**Accent action key, to Accent repair.*/
188            protected static String ACTION_KEY_ACCENT_REPAIR = "accentRepair";
189    
190            /**Chain action key, to chain repair.*/
191            protected static String ACTION_KEY_CHAIN_REPAIR = "chainRepair";
192    
193            /**HTML action key, to html repair.*/
194            protected static String ACTION_KEY_HTML_REPAIR = "htmlRepair";
195    
196            /**Order action key, to order repair.*/
197            protected static String ACTION_KEY_ORDER_REPAIR = "orderRepair";
198    
199            /**Time action key, to time repair.*/
200            protected static String ACTION_KEY_TIME_REPAIR = "timeRepair";
201    
202            /**NormalizeDuration action key, to time repair.*/
203            protected static String ACTION_KEY_NORMALIZE_DURATION = "normalizeDuration";
204    
205            /**Select video action key.*/
206            protected static String ACTION_KEY_SELECT_VIDEO = "selectVideo";
207    
208            /**Options action key.*/
209            protected static String ACTION_KEY_OPTIONS = "options";
210    
211            /**Play action key.*/
212            protected static String ACTION_KEY_PLAY = "play";
213    
214            /**Stop action key.*/
215            protected static String ACTION_KEY_STOP = "stop";
216    
217            /**Pause action key.*/
218            protected static String ACTION_KEY_PAUSE = "pause";
219    
220            /**Previous action key.*/
221            protected static String ACTION_KEY_PREVIOUS = "previous";
222    
223            /**Next action key.*/
224            protected static String ACTION_KEY_NEXT = "next";
225    
226            /**TrackTrigger action key.*/
227            protected static String ACTION_KEY_TRACKER_TRIGGER = "trackTrigger";
228    
229            /**gotoSub action key.*/
230            protected static String ACTION_KEY_GOTO_SUB = "gotoSub";
231    
232            /**magicResynchro action key.*/
233            protected static String ACTION_KEY_MAGIC_RESYNCHRO = "magicResynchro";
234    
235            /**remoaveAllAnchors action key.*/
236            protected static String ACTION_KEY_REMOVE_ALL_ANCHORS = "removeAllAnchors";
237    
238            /**about action key.*/
239            protected static String ACTION_KEY_ABOUT = "about";
240    
241            /**find action key.*/
242            protected static String ACTION_KEY_FIND = "find";
243    
244            /**find action key.*/
245            protected static String ACTION_KEY_MIX = "mix";
246    
247            /** (<b>String[]</b>) FILE_OPEN_DEPENDANT_ACTIONS: The Actions that depend on a file open status*/
248            private static final String[] FILE_OPEN_DEPENDANT_ACTIONS = new String[] {
249                    ACTION_KEY_ACCENT_REPAIR
250                    , ACTION_KEY_APPEND
251                    , ACTION_KEY_CHAIN_REPAIR
252                    , ACTION_KEY_DELAY
253                    , ACTION_KEY_HTML_REPAIR
254                    , ACTION_KEY_MAGIC_RESYNCHRO
255                    , ACTION_KEY_NORMALIZE_DURATION
256                    , ACTION_KEY_ORDER_REPAIR
257                    , ACTION_KEY_RESYNCH, ACTION_KEY_SAVE
258                    , ACTION_KEY_SAVE_AS
259                    , ACTION_KEY_SPLIT
260                    , ACTION_KEY_TIME_REPAIR 
261                    , ACTION_KEY_FIND
262                    , ACTION_KEY_MIX};
263    
264            /** (<b>String[]</b>) FILE_CHANGED_DEPENDANT_ACTIONS: The actions that depend on a file change status*/
265            private static final String[] FILE_CHANGED_DEPENDANT_ACTIONS = new String[]{
266                    ACTION_KEY_SAVE};
267    
268            private static boolean resetFlag = false;
269    
270            private JScrollPane tableJScrollPane;
271            // FindModule object
272            private FindModule fm;
273            private JSplitPane toolbarSplitPane;
274            private FindBox findBox;
275            
276            private PlayerTimeHandler playerTimeHandler;
277    
278            /**
279             * 
280             * Constructor MainWindow.
281             * <br><b>Summary:</b><br>
282             * Constructor of the class.
283             * construct the GUI.
284             */
285            public MainWindow() {
286                    super();
287                    instance = this;
288                    //create undo manager.
289                    undoManager = new SearsUndoManager();
290                    //restore recent files
291                    recentFiles = new ArrayList<File>();
292                    String recentFileProperty = SearsProperties.getProperty(SearsProperties.RECENT_FILES);
293                    StringTokenizer stk = new StringTokenizer(recentFileProperty, ";");
294                    while(stk.hasMoreTokens()){
295                            String recentFile = stk.nextToken();
296                            File aFile = new File(recentFile);
297                            // we add to the menu only the existing files
298                            // if a file was moved it does not appear at all
299                            if(aFile.exists()) {
300                                    recentFiles.add(aFile);
301                            }
302                    }
303                    //try to get the locale from properties
304                    String lang = SearsProperties.getProperty(SearsProperties.LANGUAGE, "en");
305                    String count = SearsProperties.getProperty(SearsProperties.COUNTRY, "US");
306                    Locale locale = new Locale(lang, count);
307                    new SearsResourceBundle();
308                    setSelectedLocale(locale);
309                    //set the look and feel on the current OS.
310                    try {
311                            String selectedLookAndFeel = SearsProperties.getProperty(SearsProperties.LOOK_AND_FEEL, UIManager
312                                            .getSystemLookAndFeelClassName());
313                            UIManager.setLookAndFeel(selectedLookAndFeel);
314                            //SwingUtilities.updateComponentTreeUI(instance);
315                            SearsProperties.setProperty(SearsProperties.LOOK_AND_FEEL, selectedLookAndFeel);
316                            //MainWindow.instance.repaint();
317                    } catch (ClassNotFoundException e1) {
318                            // TODO Auto-generated catch block
319                            e1.printStackTrace();
320                    } catch (InstantiationException e1) {
321                            // TODO Auto-generated catch block
322                            e1.printStackTrace();
323                    } catch (IllegalAccessException e1) {
324                            // TODO Auto-generated catch block
325                            e1.printStackTrace();
326                    } catch (UnsupportedLookAndFeelException e1) {
327                            // TODO Auto-generated catch block
328                            e1.printStackTrace();
329                    }
330                    //Set the size.
331                    int width = Integer.parseInt(SearsProperties.getProperty("MainWindow" + SearsProperties.SUFFIX_WIDTH, "600"));
332                    int heigth = Integer.parseInt(SearsProperties.getProperty("MainWindow" + SearsProperties.SUFFIX_HEIGTH, "400"));
333                    setSize(width, heigth);
334                    searsFavicon = SearsResources.getIcon("SearsGUIFavicon");
335                    searsIcon = SearsResources.getIcon("SearsGUIIcon");
336                    setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
337                    //Set the frame's icon
338                    setIconImage(searsFavicon.getImage());
339                    updateWindowsName();
340                    centerFrame(instance);
341                    addWindowListener(this);
342                    //Construct the actions.
343                    constructActions();
344                    mainPanel = new JPanel();
345                    mainPanel.setLayout(new GridLayout(1, 1));
346                    getContentPane().setLayout(new BorderLayout());
347                    getContentPane().add(mainPanel, BorderLayout.CENTER);
348                    constructJMenuBar();
349                    setJMenuBar(jMenuBar);
350                    constructJToolBar();
351                    
352                    // -------------
353                    // search field:
354                    findBox = new FindBox(this);
355                    findBox.setMinimumSize(new Dimension(150,0));
356                    
357                    toolbarSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jToolBar, findBox);
358                    toolbarSplitPane.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));           
359                    toolbarSplitPane.setContinuousLayout(true);
360                    // get back the divider pixel location:
361                    int dividerLocation;
362                    try {
363                            dividerLocation = Integer.parseInt(SearsProperties.getProperty(SearsProperties.TOOLBAR_DIVIDER_LOCATION,"T"));
364                            if( dividerLocation == 0 ) {
365                                    // percent location:
366                                    // width --> windows calculate width
367                                    dividerLocation = 3*width/4;
368                            }
369                    } catch(NumberFormatException e) {
370                            dividerLocation = 3*width/4;
371                    }
372                    toolbarSplitPane.setDividerLocation(dividerLocation);
373                    // -------------
374                    
375                    getContentPane().add(toolbarSplitPane, BorderLayout.NORTH);
376                    getContentPane().add(getJPanelVideoControler(), BorderLayout.SOUTH);
377                    subtitleList = new ArrayList<Subtitle>();
378                    tableModel = new SubtitleTableModel(subtitleList);
379                    table = new JTable(tableModel);
380                    table.setShowHorizontalLines(false);
381                    table.setShowVerticalLines(false);
382                    // froze bug [ 1669501 ] by disable the reorder by user:
383                    table.getTableHeader().setReorderingAllowed(false);
384                    // -----------------------------------------------------
385                    table.setRowSelectionAllowed(true);
386                    table.addMouseListener(this);
387                    table.addMouseListener(new MouseAdapter() {
388                            public void mousePressed(MouseEvent e) {
389                                    fireFindStop();
390    
391                            }
392                    });
393                    //  Add the Transfer handler, which will handle the files Drag and drops.
394                    SubtitleFileTransfertHandler searsTransferHandler = new SubtitleFileTransfertHandler();
395                    getRootPane().setTransferHandler(searsTransferHandler);
396                    table.setTransferHandler(searsTransferHandler);
397                    //create table renderer.
398                    //setting the table column sizes and renderer.
399                    TableColumn column = null;
400                    for (int i = 0; i < SubtitleTableModel.COLUMN_NUMBER; i++) {
401                            column = table.getColumnModel().getColumn(i);
402                            column.setCellRenderer(new SubtitleTableCellRenderer(subtitleList));
403                            column.setHeaderRenderer(new SubtitleAnchorHeaderTableCellRenderer(
404                                            table.getTableHeader().getDefaultRenderer()));
405                            switch (i) {
406                            case SubtitleTableModel.ANCHOR_COLUMN: {
407                                    column.setPreferredWidth(18);
408                                    column.setMaxWidth(18);
409                                    break;
410                            }
411                            case SubtitleTableModel.ID_COLUMN: {
412                                    column.setPreferredWidth(40);
413                                    column.setMaxWidth(60);
414                                    break;
415                            }
416                            case SubtitleTableModel.START_DATE_COLUMN: {
417                                    column.setPreferredWidth(100);
418                                    column.setMaxWidth(100);
419                                    break;
420                            }
421                            case SubtitleTableModel.END_DATE_COLUMN: {
422                                    column.setPreferredWidth(100);
423                                    column.setMaxWidth(100);
424                                    break;
425                            }
426                            }
427                    }
428                    tableJScrollPane = new JScrollPane(table);      
429                    mainPanel.add(tableJScrollPane);
430    
431                    fm = new FindModule(tableJScrollPane);
432    
433                    //
434                    // Create the thread of video time handler
435                    playerTimeHandler = new PlayerTimeHandler();
436                    Thread playerTimeHandlerThread = new Thread(playerTimeHandler, "playerTimeHandler");
437                    playerTimeHandlerThread.start();
438                    jPanelVideoControler.setJSliderSearchListener(playerTimeHandler);
439    
440                    lookForUpdate();
441            }
442    
443            /**
444             * Returns the UndoManager
445             * @return the undoManager
446             */
447            public SearsUndoManager getUndoManager() {
448                    return undoManager;
449            }
450    
451            /**
452             * Method getJPanelVideoControler.
453             * <br><b>Summary:</b><br>
454             * Return the JPanel that control the video.
455             * @return The JPanel video controler.
456             */
457            private JPanel getJPanelVideoControler() {
458                    if (jPanelVideoControler == null) {
459                            jPanelVideoControler = new JPanelVideoControler();
460                    }
461                    return jPanelVideoControler;
462            }
463    
464            /**
465             * Method lookForUpdate.
466             * <br><b>Summary:</b><br>
467             * This method look for an update, if the SearsProperties.UPDATE_ADDRESS is not equals to "".
468             * and will display a message to the user if a new Sears version is available.
469             */
470            private void lookForUpdate() {
471                    //If an address update has been set, then update.
472                    String updateAddress = SearsProperties.getProperty(SearsProperties.UPDATE_ADDRESS, "firstLaunch");
473                    if (!updateAddress.equals("")) {
474                            //If it is the first launch
475                            if (updateAddress.equals("firstLaunch")) {
476                                    setUpdateAddress(Updater.DEFAULT_UPDATE_ADDRESS);
477                                    updateAddress = Updater.DEFAULT_UPDATE_ADDRESS;
478                            }
479                            //look for update.
480                            new Updater(this, updateAddress, Double.parseDouble(Version.VERSION));
481                    }
482            }
483    
484            /**
485             * Method setUpdateAddress.
486             * <br><b>Summary:</b><br>
487             * This method set the new update address.
488             * @param address   The new update address to set.
489             */
490            private void setUpdateAddress(String address) {
491                    SearsProperties.setProperty(SearsProperties.UPDATE_ADDRESS, address);
492            }
493    
494            /**
495             * Method constructActions.
496             * <br><b>Summary:</b><br>
497             * This method construct the actions.
498             */
499            private void constructActions() {
500                    actions = new HashMap<String, SearsAction>();
501                    //construct Open action
502                    actions.put(ACTION_KEY_OPEN, new OpenAction());
503                    //construct Save action
504                    actions.put(ACTION_KEY_SAVE, new SaveAction());
505                    //construct SaveAs action
506                    actions.put(ACTION_KEY_SAVE_AS, new SaveAsAction());
507                    //construct quit action
508                    actions.put(ACTION_KEY_QUIT, new QuitAction());
509                    //construct Delay action
510                    actions.put(ACTION_KEY_DELAY, new DelayAction());
511                    //construct Resynch action
512                    actions.put(ACTION_KEY_RESYNCH, new ResynchAction());
513                    //construct Split action
514                    actions.put(ACTION_KEY_SPLIT, new SplitAction());
515                    //construct Append action
516                    actions.put(ACTION_KEY_APPEND, new AppendAction());
517                    //construct AccentRepairAction
518                    actions.put(ACTION_KEY_ACCENT_REPAIR, new AccentRepairAction());
519                    //construct ChainRepairAction
520                    actions.put(ACTION_KEY_CHAIN_REPAIR, new ChainRepairAction());
521                    //construct HtmlRepairAction
522                    actions.put(ACTION_KEY_HTML_REPAIR, new HtmlRepairAction());
523                    //construct OrderRepairAction
524                    actions.put(ACTION_KEY_ORDER_REPAIR, new OrderRepairAction());
525                    //construct TimeRepairAction
526                    actions.put(ACTION_KEY_TIME_REPAIR, new TimeRepairAction());
527                    //construct NormalizeDurationAction
528                    actions.put(ACTION_KEY_NORMALIZE_DURATION, new NormalizeDurationAction());
529                    //construct SelectVideoAction
530                    actions.put(ACTION_KEY_SELECT_VIDEO, new SelectVideoAction());
531                    //construct optionsAction
532                    actions.put(ACTION_KEY_OPTIONS, new OptionsAction());
533                    //construct playAction
534                    actions.put(ACTION_KEY_PLAY, new PlayAction());
535                    //construct stopAction
536                    actions.put(ACTION_KEY_STOP, new StopAction());
537                    //construct pauseAction
538                    actions.put(ACTION_KEY_PAUSE, new PauseAction());
539                    //construct previousAction
540                    actions.put(ACTION_KEY_PREVIOUS, new PreviousAction());
541                    //construct nextAction
542                    actions.put(ACTION_KEY_NEXT, new NextAction());
543                    //construct trackerTriggerAction
544                    actions.put(ACTION_KEY_TRACKER_TRIGGER, new TrackerTriggerAction());
545                    //construct gotoSubAction
546                    actions.put(ACTION_KEY_GOTO_SUB, new GotoSubAction());
547                    //construct magicResynchroAction
548                    actions.put(ACTION_KEY_MAGIC_RESYNCHRO, new MagicResynchroAction());
549                    //Construct removeAllAnchorsAction
550                    actions.put(ACTION_KEY_REMOVE_ALL_ANCHORS, new RemoveAllAnchorsAction());
551                    //Construct aboutAction
552                    actions.put(ACTION_KEY_ABOUT, new AboutAction());
553                    //Construct findAction
554                    actions.put(ACTION_KEY_FIND, new FindAction());
555                    //construct mixAction
556                    actions.put(ACTION_KEY_MIX, new MixAction());           
557                    //Add undo Action
558                    actions.put(SearsUndoManager.ACTION_KEY_UNDO, undoManager.getUndoAction());
559                    //Add redo Action
560                    actions.put(SearsUndoManager.ACTION_KEY_REDO, undoManager.getRedoAction());
561                    //update the actions status
562                    updateActionStatus();
563            }
564    
565            /**
566             * Method constructJMenuBar.
567             * <br><b>Summary:</b><br>
568             * This method construct the JMenuBar
569             */
570            private void constructJMenuBar() {
571                    jMenuBar = new JMenuBar();
572                    /////////////
573                    //FILE MENU
574                    /////////////
575                    JMenu jMenuFile = new JMenu(SearsResourceBundle.getResource("menuBar_file"));
576                    jMenuBar.add(jMenuFile);
577                    //OPEN ITEM
578                    jMenuFile.add(new JMenuItem(getAction(ACTION_KEY_OPEN)));
579                    //OPEN RECENT FILE
580                    recentFileMenu = new JMenu(SearsResourceBundle.getResource("RecentFileName"));
581                    recentFileMenu.setToolTipText(SearsResourceBundle.getResource("RecentFileTip"));
582                    recentFileMenu.setIcon(SearsResources.getIcon("RecentFileIcon"));
583                    // menu is updated each time the user access to it,
584                    // if user delete a recent subtitle, it does not appear on the menu:
585                    recentFileMenu.addMouseListener(new MouseAdapter() {
586                            public void mouseEntered(MouseEvent arg0) {
587                                    instance.updateRecentFileMenu();
588                            }
589                    });
590                    jMenuFile.add(recentFileMenu);
591                    //updateRecentFileMenu();
592                    //SAVE ITEM
593                    jMenuFile.add(new JMenuItem(getAction(ACTION_KEY_SAVE)));
594                    //SAVE AS ITEM
595                    jMenuFile.add(new JMenuItem(getAction(ACTION_KEY_SAVE_AS)));
596                    //QUIT ITEM
597                    //if platform isn't a Mac platform:
598                    if(!Utils.isMacPlatform){
599                            jMenuFile.add(new JMenuItem(getAction(ACTION_KEY_QUIT)));
600                    }               
601                    //////////////////
602                    //EDIT MENU
603                    /////////////////
604                    JMenu jMenuEdit = new JMenu(SearsResourceBundle.getResource("menuBar_edit"));
605                    jMenuBar.add(jMenuEdit);
606                    //DELAY ITEM
607                    jMenuEdit.add(new JMenuItem(getAction(ACTION_KEY_FIND)));       
608                    //UNDO REDO
609                    jMenuEdit.add(new JMenuItem(getAction(SearsUndoManager.ACTION_KEY_UNDO)));
610                    jMenuEdit.add(new JMenuItem(getAction(SearsUndoManager.ACTION_KEY_REDO)));
611    
612                    //////////////////
613                    //ACTION MENU
614                    /////////////////
615                    JMenu jMenuAction = new JMenu(SearsResourceBundle.getResource("menuBar_action"));
616                    jMenuBar.add(jMenuAction);
617                    //DELAY ITEM
618                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_DELAY)));
619                    //RESYNCHRO ITEM
620                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_RESYNCH)));
621                    //MAGIC RESYNCHRO ITEM
622                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_MAGIC_RESYNCHRO)));
623                    //REMOVE ALL ANCHORS ITEM
624                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_REMOVE_ALL_ANCHORS)));
625                    //Add a separator
626                    jMenuAction.addSeparator();
627                    //SPLIT FILE ITEM
628                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_SPLIT)));
629                    //APPEND FILE ITEM
630                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_APPEND)));
631                    //MIX FILE ITEM
632                    jMenuAction.add(new JMenuItem(getAction(ACTION_KEY_MIX)));
633    
634                    //////////////////
635                    //REPAIR MENU
636                    /////////////////
637                    JMenu jMenuRepair = new JMenu(SearsResourceBundle.getResource("menuBar_repair"));
638                    jMenuBar.add(jMenuRepair);
639                    //CHAIN REPAIR
640                    jMenuRepair.add(new JMenuItem(getAction(ACTION_KEY_CHAIN_REPAIR)));
641                    //Add a separator
642                    jMenuRepair.addSeparator();
643                    //ACCENT REPAIR
644                    jMenuRepair.add(new JMenuItem(getAction(ACTION_KEY_ACCENT_REPAIR)));
645                    //HTML REPAIR
646                    jMenuRepair.add(new JMenuItem(getAction(ACTION_KEY_HTML_REPAIR)));
647                    //Add a separator
648                    jMenuRepair.addSeparator();
649                    //ORDER REPAIR
650                    jMenuRepair.add(new JMenuItem(getAction(ACTION_KEY_ORDER_REPAIR)));
651                    //TIME REPAIR
652                    jMenuRepair.add(new JMenuItem(getAction(ACTION_KEY_TIME_REPAIR)));
653                    //TIME REPAIR
654                    jMenuRepair.add(new JMenuItem(getAction(ACTION_KEY_NORMALIZE_DURATION)));
655                    //////////////////
656                    //PLAYER MENU
657                    /////////////////
658                    JMenu jMenuPlayer = new JMenu(SearsResourceBundle.getResource("menuBar_player"));
659                    jMenuBar.add(jMenuPlayer);
660                    //SELECT VIDEO
661                    jMenuPlayer.add(new JMenuItem(getAction(ACTION_KEY_SELECT_VIDEO)));
662                    //LAUNCH PLAYER
663                    jMenuPlayer.add(new JMenuItem(getAction(ACTION_KEY_PLAY)));
664                    //PAUSE PLAYER
665                    jMenuPlayer.add(new JMenuItem(getAction(ACTION_KEY_PAUSE)));
666                    //STOP PLAYER
667                    jMenuPlayer.add(new JMenuItem(getAction(ACTION_KEY_STOP)));
668                    //PREVIOUS PLAYER
669                    jMenuPlayer.add(new JMenuItem(getAction(ACTION_KEY_PREVIOUS)));
670                    //NEXT PLAYER
671                    jMenuPlayer.add(new JMenuItem(getAction(ACTION_KEY_NEXT)));
672                    //////////////////
673                    //OPTIONS MENU
674                    /////////////////
675                    JMenu jMenuOptions = new JMenu(SearsResourceBundle.getResource("menuBar_options"));
676                    jMenuBar.add(jMenuOptions);
677                    //OPTIONS
678                    jMenuOptions.add(new JMenuItem(getAction(ACTION_KEY_OPTIONS)));
679                    //////////////////
680                    //HELP MENU
681                    /////////////////
682                    JMenu helpMenu = new JMenu(SearsResourceBundle.getResource("menuBar_help"));
683                    jMenuBar.add(helpMenu);
684                    JMenuItem helpItem = new JMenuItem(SearsResourceBundle.getResource("menuBar_help"));
685                    helpItem.setIcon(SearsResources.getIcon("HelpIcon"));
686                    helpItem.addActionListener(new ActionListener() {
687                            public void actionPerformed(ActionEvent arg0) {
688                                    //default title and icon
689                                    JOptionPane
690                                    .showMessageDialog(
691                                                    instance,
692                                                    "You can find help on Sears\nby going to sears sourceforge page:\nhttp://sears.sourceforge.net/\n ",
693                                                    "Sears Help", JOptionPane.INFORMATION_MESSAGE, searsIcon);
694                            }
695                    });
696                    helpMenu.add(helpItem);
697                    helpMenu.add(new JMenuItem(getAction(ACTION_KEY_ABOUT)));
698            }
699    
700            /**
701             * Method constructJMenuBar.
702             * <br><b>Summary:</b><br>
703             * This method construct the JMenuBar
704             */
705            private void constructJToolBar() {
706                    jToolBar = new JToolBar();
707                    jToolBar.setFloatable(false);
708                    /////////////
709                    //FILE TOOLS
710                    /////////////
711                    //OPEN ITEM
712                    JButton button = new JButton(getAction(ACTION_KEY_OPEN));
713                    button.setText(null);
714                    button.addMouseListener(new MouseAdapter() {
715                            //
716                            // DEVELOPER: the idea is to show popup menu when
717                            // user pressed key more than 2 seconds.
718                            // The action on the open button calls a non thread-safe method
719                            // the showSRTBrowser do not released the button before begin showing the open dialog
720                            // and so it's a problem...
721                            public void mousePressed(MouseEvent e) {
722                                    JButton button = (JButton) e.getSource();
723                                    if( e.isMetaDown() ) {
724                                            JPopupMenu popupMenu = new JPopupMenu();
725                                            updateRecentFileMenu();
726                                            Component[] components = recentFileMenu.getMenuComponents();
727                                            for( Component cmp : components) {
728                                                    popupMenu.add(cmp);                                                     
729                                            } 
730                                            popupMenu.show(button, button.getX(), button.getY());
731    
732                                    }
733                            }
734                    });
735                    jToolBar.add(button);
736                    //SAVE  ITEM
737                    button = new JButton(getAction(ACTION_KEY_SAVE));
738                    button.setText(null);
739                    jToolBar.add(button);
740                    //add separator
741                    jToolBar.addSeparator();
742                    //UNDO REDO
743                    button = new JButton(getAction(SearsUndoManager.ACTION_KEY_UNDO));
744                    button.setText(null);
745                    jToolBar.add(button);
746                    button = new JButton(getAction(SearsUndoManager.ACTION_KEY_REDO));
747                    button.setText(null);
748                    jToolBar.add(button);
749                    
750                    //////////////////
751                    //ACTION MENU
752                    /////////////////
753                    //DELAY ITEM
754                    button = new JButton(getAction(ACTION_KEY_DELAY));
755                    button.setText(null);
756                    jToolBar.add(button);
757                    //MAGIC RESYNCHRO ITEM
758                    button = new JButton(getAction(ACTION_KEY_MAGIC_RESYNCHRO));
759                    button.setText(null);
760                    jToolBar.add(button);
761                    //REMOVE ALL ANCHORS ITEM
762                    button = new JButton(getAction(ACTION_KEY_REMOVE_ALL_ANCHORS));
763                    button.setText(null);
764                    jToolBar.add(button);
765                    //SPLIT ITEM
766                    button = new JButton(getAction(ACTION_KEY_SPLIT));
767                    button.setText(null);
768                    jToolBar.add(button);
769                    //APPEND ITEM
770                    button = new JButton(getAction(ACTION_KEY_APPEND));
771                    button.setText(null);
772                    jToolBar.add(button);
773                    //add separator
774                    jToolBar.addSeparator();
775                    //////////////////
776                    //REPAIR MENU
777                    /////////////////
778                    //CHAIN REPAIR ITEM
779                    button = new JButton(getAction(ACTION_KEY_CHAIN_REPAIR));
780                    button.setText(null);
781                    jToolBar.add(button);
782    
783                    //ACCENT REPAIR ITEM
784                    button = new JButton(getAction(ACTION_KEY_ACCENT_REPAIR));
785                    button.setText(null);
786                    jToolBar.add(button);
787                    //HTML REPAIR ITEM
788                    button = new JButton(getAction(ACTION_KEY_HTML_REPAIR));
789                    button.setText(null);
790                    jToolBar.add(button);
791                    jToolBar.addSeparator();
792                    //////////////////
793                    //TOOLS MENU
794                    //FIND
795                    /*button = new JButton(getAction(ACTION_KEY_FIND));
796                    button.setText(null);           
797                    jToolBar.add(button);*/
798    
799                    /////////////////
800                    //OPTIONS BUTTON
801                    button = new JButton(getAction(ACTION_KEY_OPTIONS));
802                    button.setText(null);
803                    jToolBar.add(button);
804                    
805                    jToolBar.addSeparator(new Dimension(10,0));
806            }
807    
808            /**
809             * Method getAction.
810             * <br><b>Summary:</b><br>
811             * Return the SearsActions that correspond to the given key.
812             * @param   key             The <b>String</b> Action key to search for.
813             * @return  SearsAction     The <b>SearsAction</b> that correspond to the key, or null if not found.
814             */
815            protected SearsAction getAction(String key) {
816                    //Search and return the action in the actions map.
817                    return (SearsAction) actions.get(key);
818            }
819    
820            /**
821             * Method delayAction.
822             * <br><b>Summary:</b><br>
823             * Use this method to set a delay on subtitles.
824             * It will show a dialog to enter a delay to the selected subtitles.
825             * @return  (<b>boolean</b>)   True if action succeeds.
826             */
827            protected boolean delayAction() {
828                    //The result
829                    boolean result = false;
830                    //Must have opened a subtitle file to apply a delay.
831                    if (subtitleFile != null) {
832                            //get the selected rows to delay.
833                            int[] indexToDelay = table.getSelectedRows();
834                            //ask the user for delay to apply
835                            DelayDialog dd = new DelayDialog();
836                            dd.setVisible(true);
837                            //if user choose a delay.
838                            if (dd.hasBeenValidated()) {
839                                    double delay = dd.getDelay();
840                                    //delay the subtitle with the given delay.
841                                    //multiply by 1000, because delay is indicated in second, with possible milliseconds.
842                                    subtitleFile.delay(indexToDelay, (int) (delay * 1000));
843                                    result = true;
844                                    updateTableAndActions();
845                            }
846                    } else {
847                            //Must have opened a subtitle file to apply a delay.
848                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeDelay"), Trace.WARNING_PRIORITY);
849                    }
850                    return result;
851            }
852            
853            private FindDialog _fd;
854            public void addFB(FindDialog fd) {
855                    _fd = fd;
856                    
857            }
858            
859            /**
860             * Method updateWholeTable.
861             * <br><b>Summary:</b><br>
862             * Updates the whole table.
863             */
864            public void updateWholeTable(){
865                    //indicate table that data have changed
866                    tableModel.fireTableDataChanged();
867            }
868    
869            /**
870             * Method resynchroAction.
871             * <br><b>Summary:</b><br>
872             * Use this method to resynchro subtitles.
873             * If user has selected the Two source point, the dialog shown will be pre-filled with good values.
874             * @return boolean true if action succeed.
875             */
876            protected boolean resynchroAction() {
877                    boolean result = false;
878                    //Must have opened a subtitle file to apply a resynchro.
879                    if (subtitleFile != null) {
880                            //check wether user choose two source subtitles.
881                            int[] selectedIndex = table.getSelectedRows();
882                            ResynchroDialog rd;
883                            //If user choose sources, use them in shown dialog.
884                            if (selectedIndex.length > 1) {
885                                    //Keep only 2 subtitles. interpolation of level n will come after :)
886                                    int[] selectedIndex2 = new int[2];
887                                    selectedIndex2[0] = selectedIndex[0];
888                                    selectedIndex2[1] = selectedIndex[1];
889                                    //TODO: create n-level interpolation in SubtitleFile!
890                                    //construct the dialog.
891                                    rd = new ResynchroDialog(subtitleList, selectedIndex2);
892                            } else {
893                                    //If no subtitles has been selected, construct empty resynchro dialog.
894                                    rd = new ResynchroDialog(subtitleList);
895                            }
896                            //show the constructed dialog.
897                            rd.setVisible(true);
898                            //check user's response.
899                            if (rd.hasBeenValidated()) {
900                                    //if user has validated the dialog, get the result.
901                                    int[] dialogResult = rd.getResult();
902                                    //and resynchro the subtitles.
903                                    subtitleFile.resynchro(dialogResult);
904                                    result = true;
905                                    //indicate the table that data have changed.
906                                    updateWholeTable();
907                            }
908                            updateWindowsName();
909                            updateActionStatus();
910                    } else {
911                            //Must have opened a subtitle file to apply a resynchro.
912                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeResynch"), Trace.WARNING_PRIORITY);
913                    }
914                    return result;
915            }
916    
917            /**
918             * Method magicResynchroAction.
919             * <br><b>Summary:</b><br>
920             * Apply a magic resynchro
921             */
922            protected boolean magicResynchroAction(){
923                    boolean result = false;
924                    if (subtitleFile != null) {
925                            subtitleFile.magicResynchro();
926                            result = true;
927                            //After magic resynchro, remove all anchors.
928                            removeAllAnchorsAction();
929                            updateWindowsName();
930                            updateActionStatus();
931                    } else {
932                            //Must have opened a subtitle file to apply a resynchro.
933                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeResynch"), Trace.WARNING_PRIORITY);
934                    }
935                    return result;
936            }
937    
938            /**
939             * Method saveAsAction.
940             * <br><b>Summary:</b><br>
941             * Method called when want to save the subtitle file.
942             * open a JFolderChooser to chooser where to save the file.
943             */
944            protected void saveAsAction() {
945                    Trace.trace("SaveAsAction", Trace.ALGO_PRIORITY);
946                    DialogUtils du = new DialogUtils();
947                    // set options:
948                    du.setConditions(false, true);
949                    du.setApproveButtonText(SearsResourceBundle.getResource("button_saveAs"));
950                    du.setOverwriteText(SearsResourceBundle.getResource("various_overwrite"));
951                    du.setFileFilter(new FileFilter() {
952                            public String getDescription() {
953                                    return SearsResourceBundle.getResource("various_subtitleFileDescription");
954                            }
955    
956                            public boolean accept(File f) {
957                                    boolean result = f.isDirectory() 
958                                    || Utils.getExtension(f).equals("srt")
959                                    || Utils.getExtension(f).equals("ssa")
960                                    || Utils.getExtension(f).equals("sub");
961                                    return result;
962                            }
963                    });
964    
965                    // we get back the file choosen
966                    File file = du.showSaveAsDialog(instance, SearsResourceBundle.getResource("SaveAsName"), subtitleFile.getFile());
967                    // if user cancel action, file is null:
968                    if (file != null){
969                            if( fsa.saveFile(file, subtitleFile, null) ) {
970                                    openFile(file);
971                            }
972                            //updateWindowsName();
973                            //updateActionStatus();
974                    }
975            }
976    
977            /**
978             * Method saveAction.
979             * <br><b>Summary:</b><br>
980             * Method called when want to save the subtitle file.
981             */
982            protected void saveAction() {
983                    Trace.trace("SaveAction", Trace.ALGO_PRIORITY);
984                    if ( fsa.saveFile(subtitleFile.getFile(), subtitleFile, null) ) {
985                            updateWindowsName();
986                            updateActionStatus();
987                    }
988            }
989    
990            /**
991             * Method quitAction.
992             * <br><b>Summary:</b><br>
993             * Use this method to quit Sears.
994             */
995            public void quitAction() {
996                    dispose();
997                    System.exit(0);
998            }
999    
1000            public void dispose() {
1001                    //check current file before quit.
1002                    if (checkUnsavedFileBeforeAction()) {
1003                            Trace.trace(SearsResourceBundle.getResource("various_quittingSears"), Trace.ALGO_PRIORITY);
1004                            if (player != null) {
1005                                    player.quit();
1006                            }
1007    
1008                            // if user doesn't have press the reset button:
1009                            if(!resetFlag) {
1010                                    // we save properties:
1011                                    saveMainWindowProperties();
1012                                    SearsProperties.saveProperties();
1013                            } else {
1014                                    try {
1015                                            // reset operation:
1016                                            SearsProperties.resetProperty();
1017                                            Trace.trace("Deleting property file", Trace.MESSAGE_PRIORITY);
1018                                    } catch (IOException e) {
1019                                            // DEVELOPER: try to write some flag in property file, like delete=1 
1020                                            // or something like that. When Sears is reopened, check this mark and try
1021                                            // to do the job at this time. 
1022                                            Trace.trace("Cannot delete property file, I/O exception occurs", Trace.ERROR_PRIORITY);
1023                                    }
1024                            }
1025                    }
1026                    super.dispose();
1027            }
1028    
1029            /**
1030             * Method saveMainWindowProperties.
1031             * <br><b>Summary:</b><br>
1032             * This method saves the MainWindow properties of Sears.
1033             */
1034            private void saveMainWindowProperties() {
1035                    //save windows properties.
1036                    SearsProperties.setProperty("MainWindow" + SearsProperties.SUFFIX_WIDTH, "" + this.getWidth());
1037                    SearsProperties.setProperty("MainWindow" + SearsProperties.SUFFIX_HEIGTH, "" + this.getHeight());
1038                    //save the folder property.
1039                    if (subtitleFile != null) {
1040                            SearsProperties.setProperty(SearsProperties.LAST_FOLDER, "" + subtitleFile.getContentDirectory());
1041                    }
1042                    //Save the locale.
1043                    SearsProperties.setProperty(SearsProperties.LANGUAGE, getLocale().getLanguage().toString());
1044                    SearsProperties.setProperty(SearsProperties.COUNTRY, getLocale().getCountry().toString());
1045                    //Save the recent files list.
1046                    String recentFilesProperty = "";
1047                    for(File file: recentFiles){
1048                            recentFilesProperty+=file.getAbsolutePath()+";";
1049                    }
1050                    SearsProperties.setProperty(SearsProperties.RECENT_FILES, recentFilesProperty);
1051                    SearsProperties.setProperty(SearsProperties.TOOLBAR_DIVIDER_LOCATION, String.valueOf(toolbarSplitPane.getDividerLocation()));
1052    
1053            }
1054    
1055            /**
1056             * Method openAction.
1057             * <br><b>Summary:</b><br>
1058             * Use this method to open a subtitle file.
1059             * Displays a dialog to choose the file to open.
1060             */
1061            protected void openAction() {
1062                    Trace.trace("OpenAction", Trace.ALGO_PRIORITY);
1063                    File file = showSRTBrowser();
1064                    if (file != null) {
1065                            //If user has choose a file to open, open it.
1066                            Trace.trace(SearsResourceBundle.getResource("various_opening") + " " + file.getName() + ".");
1067                            // Close the video file
1068                            if (subtitleFile != null) {
1069                                    playerVideoFile = null;
1070                            }
1071                            openFile(file);
1072                            // Check if the player is running
1073                            if(player != null) {
1074                                    stopAction();
1075                                    playAction();
1076                            }
1077                    } else {
1078                            //if user canceled opening action, do nothing.
1079                            Trace.trace(SearsResourceBundle.getResource("various_openCommandCanceled"));
1080                    }
1081            }
1082    
1083            /**
1084             * Method showBrowser.
1085             * <br><b>Summary:</b><br>
1086             * This method shows a browser, to permits subtitle file selection.
1087             * Open the browser at the current subtitle file location, if any.
1088             * @param filter      The file filter
1089             * @return  <b>File</b>  
1090             */
1091            public File showBrowser(FileFilter filter) {
1092                    //The result of the method.
1093                    File result = null;
1094                    if(subtitleFile != null){
1095                            result = showBrowser(filter, subtitleFile.getFile());
1096                    }else{
1097                            result = showBrowser(filter, null);
1098                    }
1099                    //return the result.
1100                    return result;
1101            }
1102    
1103            /**
1104             * Method showBrowser.
1105             * <br><b>Summary:</b><br>
1106             * This method shows a browser, to permits subtitle file selection.
1107             * Open the browser at the given file location.
1108             * @param filter       The file filter.
1109             * @param sourceFile   Open the browser in the file's parent path.
1110             * @return  <b>File</b>  
1111             */
1112            public File showBrowser(FileFilter filter, File sourceFile) {
1113                    File result = null;
1114                    DialogUtils du = new DialogUtils();
1115                    du.setConditions(true, true);
1116                    du.setFileFilter(filter);
1117                    if (sourceFile != null) {
1118                            result = du.showOpenDialog(instance, "open", sourceFile);
1119                    } else {
1120                            result = du.showOpenDialog(instance, "open", new File(SearsProperties.getProperty(SearsProperties.LAST_FOLDER, "")));
1121                    }
1122                    return result;
1123            }
1124    
1125            /**
1126             * Method showSRTBrowser.
1127             * <br><b>Summary:</b><br>
1128             * This method shows a browser, to permits subtitle file selection.
1129             * @return  <b>File</b>  
1130             */
1131            public File showSRTBrowser() {
1132                    return showBrowser(new FileFilter() {
1133                            public String getDescription() {
1134                                    return SearsResourceBundle.getResource("various_subtitleFileDescription");
1135                            }
1136    
1137                            public boolean accept(File f) {
1138                                    boolean result = f.isDirectory() 
1139                                    || Utils.getExtension(f).equals("srt")
1140                                    || Utils.getExtension(f).equals("ssa")
1141                                    || Utils.getExtension(f).equals("sub");
1142                                    return result;
1143                            }
1144                    });
1145            }
1146    
1147            /**
1148             * Method showVideoBrowser.
1149             * <br><b>Summary:</b><br>
1150             * This method shows a browser, to permits video selection.
1151             * @return  <b>File</b>  
1152             */
1153            public File showVideoBrowser() {
1154                    return showBrowser(new FileFilter() {
1155                            public String getDescription() {
1156                                    return SearsResourceBundle.getResource("various_videoFileDescription");
1157                            }
1158    
1159                            public boolean accept(File f) {
1160                                    return f.isDirectory() || Utils.getExtension(f).equals("avi") || Utils.getExtension(f).equals("mkv")
1161                                    || Utils.getExtension(f).equals("divx") || Utils.getExtension(f).equals("mov")
1162                                    || Utils.getExtension(f).equals("ogm") || Utils.getExtension(f).equals("wmv")
1163                                    || Utils.getExtension(f).equals("mpg") || Utils.getExtension(f).equals("mpeg");
1164                            }
1165                    });
1166            }
1167    
1168            private FileSystemAccess fsa;
1169    
1170            /**
1171             * Opens a file with the specific charset
1172             * @param file          the file to open
1173             * @param charset       the charset name
1174             */
1175            public void openFile(File file, String charset) {
1176                    fsa = FileSystemAccess.getInstance();
1177                    SubtitleFile openedSubtitleFile = fsa.openFile(file, subtitleList, charset);
1178                    // if opening file succeeded,
1179                    // check current file before open.
1180                    if( openedSubtitleFile != null ) {
1181                            if ( checkUnsavedFileBeforeAction() ) {
1182                                    setSubtitleFile(openedSubtitleFile);
1183                            } // else do nothing user cancel the open operation
1184                    }
1185                    //discard all edits, since we open a new file.
1186                    undoManager.discardAllEdits();
1187            }
1188    
1189            public String showOpenErrorDialog(String message, String detail, boolean reOpenChoice) {
1190                    return ErrorDialog.showOpenErrorDialog(message, detail, reOpenChoice);
1191            }
1192    
1193            public void showSaveErrorDialog(String message, String detail) {
1194                    ErrorDialog.showSaveErrorDialog(message, detail);
1195            }
1196    
1197            /**
1198             * Sets a new subtitle file
1199             * @param newSubtitleFile the new subtitle file, if it's null this method is no op
1200             */
1201            public void setSubtitleFile(SubtitleFile newSubtitleFile) {
1202                    if( newSubtitleFile != null ) {
1203                            //Empty the UndoManager here, not to apply old edit to new file.
1204                            undoManager.discardAllEdits();
1205                            subtitleFile = newSubtitleFile;
1206                            //indicate table that data have changed.                                        
1207                            updateTableAndActions();
1208                            addToRecentFileList(newSubtitleFile.getFile());
1209                    }
1210                    
1211                    _fd.setFindEnabled(true);
1212            }
1213    
1214            /**
1215             * Method openFile. <br>
1216             * <b>Summary:</b><br>
1217             * This method open a subtitle file, and put it in the table.
1218             * 
1219             * @param file
1220             *            The subtitle file to open
1221             */
1222            public void openFile(File file) {
1223                    openFile(file, null);
1224            }
1225    
1226            /**
1227             * Method addToRecentFileList.
1228             * <br><b>Summary:</b><br>
1229             * This method permits to add a new file to recent file list.
1230             * @param file          The file that has been opened.
1231             */
1232            private void addToRecentFileList(File file) {
1233                    //remove if previously added.
1234                    recentFiles.remove(file);
1235                    //save the newly opened file under recent file list.
1236                    recentFiles.add(file);
1237                    //Remove oldest one, if list is too long.
1238                    if(recentFiles.size() > RECENT_FILE_NUMBER){
1239                            recentFiles.remove(0);
1240                    }
1241                    //Update the recentFile menu.
1242                    updateRecentFileMenu();
1243            }
1244    
1245            /**
1246             * Method updateRecentFileMenu.
1247             * <br><b>Summary:</b><br>
1248             * Permit to update the recent file menu.
1249             */
1250            private void updateRecentFileMenu() {
1251                    //Remove all previous menu items.
1252                    recentFileMenu.removeAll();
1253                    //And parse all the recent file adding a menuItem for each file.
1254                    //parse in reverse order, to put older opened files at the end.
1255                    for(int i= recentFiles.size()-1; i >= 0 ;i --){
1256                            final File finalFile = recentFiles.get(i);
1257                            JMenuItem jMenuItem = new JMenuItem(finalFile.getName());
1258                            if(finalFile.exists()) {                        
1259                                    jMenuItem.addActionListener(new ActionListener() {
1260                                            public void actionPerformed(ActionEvent e) {                            
1261                                                    openFile(finalFile);
1262                                            }
1263                                    });
1264                                    recentFileMenu.add(jMenuItem);
1265                            }
1266                    }
1267            }
1268    
1269            /**
1270             * Method centerFrame.
1271             * <br><b>Summary:</b><br>
1272             * center frame on screen.
1273             * @param window    The window to center.
1274             */
1275            public static void centerFrame(Window window) {
1276                    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
1277                    int w = window.getSize().width;
1278                    int h = window.getSize().height;
1279                    int x = (dim.width - w) / 2;
1280                    int y = (dim.height - h) / 2;
1281                    window.setLocation(x, y);
1282            }
1283    
1284            /**
1285             * Class DelayAction.
1286             * <br><b>Summary:</b><br>
1287             * The delay action.
1288             */
1289            class DelayAction extends SearsUndoAction {
1290                    private static final long serialVersionUID = -1004418974524773725L;
1291    
1292                    public DelayAction() {
1293                            super("Delay", undoManager);
1294                    }
1295    
1296                    @Override
1297                    public boolean doUndoableAction(ActionEvent e) {
1298                            return delayAction();
1299                    }
1300            }
1301    
1302            /**
1303             * Class OpenAction.
1304             * <br><b>Summary:</b><br>
1305             * This is the open action, call when user wants to open a file.
1306             */
1307            class OpenAction extends SearsAction {
1308                    private static final long serialVersionUID = -6340643915376767725L;
1309    
1310                    public OpenAction() {
1311                            super("Open");
1312                    }
1313    
1314                    /* (non-Javadoc)
1315                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1316                     */
1317                    public void actionPerformed(ActionEvent e) {
1318                            openAction();
1319                    }
1320            }
1321    
1322            /**
1323             * Class SaveAction.
1324             * <br><b>Summary:</b><br>
1325             * The Save Action
1326             */
1327            class SaveAction extends SearsAction {
1328                    private static final long serialVersionUID = 4144500453750320579L;
1329    
1330                    public SaveAction() {
1331                            super("Save");
1332                    }
1333    
1334                    /* (non-Javadoc)
1335                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1336                     */
1337                    public void actionPerformed(ActionEvent e) {
1338                            saveAction();
1339                    }
1340            }
1341    
1342            /**
1343             * Class SaveAsAction.
1344             * <br><b>Summary:</b><br>
1345             * The SaveAs Action
1346             */
1347            class SaveAsAction extends SearsAction {
1348                    private static final long serialVersionUID = 8310536551059661660L;
1349    
1350                    public SaveAsAction() {
1351                            super("SaveAs");
1352                    }
1353    
1354                    /* (non-Javadoc)
1355                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1356                     */
1357                    public void actionPerformed(ActionEvent e) {
1358                            saveAsAction();
1359                    }
1360            }
1361    
1362            /**
1363             * Class ResynchAction.
1364             * <br><b>Summary:</b><br>
1365             * The Resynch Action
1366             */
1367            class ResynchAction extends SearsUndoAction {
1368                    private static final long serialVersionUID = -3381309933952740831L;
1369    
1370                    public ResynchAction() {
1371                            super("Resynch", undoManager);
1372                    }
1373    
1374                    @Override
1375                    public boolean doUndoableAction(ActionEvent e) {
1376                            return resynchroAction();
1377                    }
1378            }
1379    
1380            /**
1381             * Class SplitAction.
1382             * <br><b>Summary:</b><br>
1383             * The Split Action
1384             */
1385            class SplitAction extends SearsUndoAction {
1386                    private static final long serialVersionUID = 4890494133812108855L;
1387    
1388                    public SplitAction() {
1389                            super("Split", undoManager);
1390                    }
1391    
1392                    @Override
1393                    public boolean doUndoableAction(ActionEvent e) {
1394                            return splitAction();
1395                    }
1396            }
1397    
1398            /**
1399             * Class AppendAction.
1400             * <br><b>Summary:</b><br>
1401             * The Append Action
1402             */
1403            class AppendAction extends SearsUndoAction {
1404                    private static final long serialVersionUID = 307293671858946138L;
1405    
1406                    public AppendAction() {
1407                            super("Append", undoManager);
1408                    }
1409    
1410                    @Override
1411                    public boolean doUndoableAction(ActionEvent e) {
1412                            return appendAction();
1413                    }
1414            }
1415    
1416            /**
1417             * Class QuitAction.
1418             * <br><b>Summary:</b><br>
1419             * The Quit Action
1420             */
1421            class QuitAction extends SearsAction {
1422                    private static final long serialVersionUID = -7251685640412615636L;
1423    
1424                    public QuitAction() {
1425                            super("Quit");
1426                    }
1427    
1428                    /* (non-Javadoc)
1429                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1430                     */
1431                    public void actionPerformed(ActionEvent e) {
1432                            quitAction();
1433                    }
1434            }
1435    
1436            /**
1437             * Class OrderRepairAction.
1438             * <br><b>Summary:</b><br>
1439             * The OrderRepair Action
1440             */
1441            class OrderRepairAction extends SearsUndoAction {
1442                    private static final long serialVersionUID = 8302695242689945983L;
1443    
1444                    public OrderRepairAction() {
1445                            super("OrderRepair", undoManager);
1446                    }
1447    
1448                    @Override
1449                    public boolean doUndoableAction(ActionEvent e) {
1450                            return orderRepairAction();
1451                    }
1452            }
1453    
1454            /**
1455             * Class PlayAction.
1456             * <br><b>Summary:</b><br>
1457             * The Play Action
1458             */
1459            class PlayAction extends SearsAction {
1460                    private static final long serialVersionUID = -8943325412755437272L;
1461    
1462                    public PlayAction() {
1463                            super("Play");
1464                    }
1465    
1466                    /* (non-Javadoc)
1467                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1468                     */
1469                    public void actionPerformed(ActionEvent e) {
1470                            playAction();
1471                    }
1472            }
1473    
1474            /**
1475             * Class StopAction.
1476             * <br><b>Summary:</b><br>
1477             * The Stop Action
1478             */
1479            class StopAction extends SearsAction {
1480                    private static final long serialVersionUID = -5754273483777981782L;
1481    
1482                    public StopAction() {
1483                            super("Stop");
1484                    }
1485    
1486                    /* (non-Javadoc)
1487                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1488                     */
1489                    public void actionPerformed(ActionEvent e) {
1490                            stopAction();
1491                    }
1492            }
1493    
1494            /**
1495             * Class PauseAction.
1496             * <br><b>Summary:</b><br>
1497             * The Pause Action
1498             */
1499            class PauseAction extends SearsAction {
1500                    private static final long serialVersionUID = -4954880612379971754L;
1501    
1502                    public PauseAction() {
1503                            super("Pause");
1504                    }
1505    
1506                    /* (non-Javadoc)
1507                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1508                     */
1509                    public void actionPerformed(ActionEvent e) {
1510                            pauseAction();
1511                    }
1512            }
1513    
1514            /**
1515             * Class PreviousAction.
1516             * <br><b>Summary:</b><br>
1517             * The Previous Action
1518             */
1519            class PreviousAction extends SearsAction {
1520                    private static final long serialVersionUID = 7512470475148460128L;
1521    
1522                    public PreviousAction() {
1523                            super("Previous");
1524                    }
1525    
1526                    /* (non-Javadoc)
1527                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1528                     */
1529                    public void actionPerformed(ActionEvent e) {
1530                            previousAction();
1531                    }
1532            }
1533    
1534            /**
1535             * Class NextAction.
1536             * <br><b>Summary:</b><br>
1537             * The Next Action
1538             */
1539            class NextAction extends SearsAction {
1540                    private static final long serialVersionUID = -4498355509337819003L;
1541    
1542                    public NextAction() {
1543                            super("Next");
1544                    }
1545    
1546                    /* (non-Javadoc)
1547                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1548                     */
1549                    public void actionPerformed(ActionEvent e) {
1550                            nextAction();
1551                    }
1552            }
1553    
1554            /**
1555             * Class TrackerTriggerAction.
1556             * <br><b>Summary:</b><br>
1557             * The TrackerTrigger Action
1558             */
1559            class TrackerTriggerAction extends SearsAction {
1560                    private static final long serialVersionUID = 5091927415403379314L;
1561    
1562                    public TrackerTriggerAction() {
1563                            super("TrackerTrigger");
1564                    }
1565    
1566                    /* (non-Javadoc)
1567                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1568                     */
1569                    public void actionPerformed(ActionEvent e) {
1570                            trackerTriggerAction();
1571                    }
1572            }
1573    
1574            /**
1575             * Method orderRepairAction.
1576             * <br><b>Summary:</b><br>
1577             * This method is called when user wants to repair the order of the subtitle file.
1578             * It will check chronology, order ST's with their start time, and finally fix ST's numbers.
1579             * @return boolean true if action succeeded
1580             */
1581            public boolean orderRepairAction() {
1582                    boolean result = false;
1583                    //Must have opened a subtitle file to repair.
1584                    if (subtitleFile != null) {
1585                            //Apply order repair
1586                            subtitleFile.orderRepair();
1587                            result = true;
1588                            updateTableAndActions();
1589                    } else {
1590                            //Must have opened a subtitle file to apply a delay.
1591                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeRepair"), Trace.WARNING_PRIORITY);
1592                    }
1593                    return result;
1594            }
1595    
1596            /**
1597             * Method updateTableAndActions.
1598             * <br><b>Summary:</b><br>
1599             * update the whole table, the actions and the window name.
1600             */
1601            public void updateTableAndActions() {
1602                    //indicate table that data have changed
1603                    updateWholeTable();
1604                    tableChangedSinceTheLastSearch();
1605                    updateWindowsName();
1606                    updateActionStatus();
1607            }
1608    
1609            /**
1610             * Method pauseAction.
1611             * <br><b>Summary:</b><br>
1612             * This method when user wants to pause the video
1613             */
1614            public void pauseAction() {
1615                    try {
1616                            if(player != null) {
1617                                    //Pause the video player
1618                                    player.pause();
1619                            }
1620                    } catch (PlayerException e) {
1621                            //display an error message.
1622                            Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
1623                    }
1624            }
1625    
1626            /**
1627             * Method trackerTriggerAction.
1628             * <br><b>Summary:</b><br>
1629             * This method is called when user wants to change the tracker mode.
1630             */
1631            public void trackerTriggerAction() {
1632                    // TODO change the tracker mode.
1633                    Trace.trace("TriggerAction");
1634            }
1635    
1636            /**
1637             * Method nextAction.
1638             * <br><b>Summary:</b><br>
1639             * This method is called when user wants to move forward in the video.
1640             */
1641            public void nextAction() {
1642                    try {
1643                            if(player != null) {
1644                                    int currentOffset = player.getPosition();
1645                                    if(currentOffset != -1) {
1646                                            //Go forward using the time step.
1647                                            int timeStep = jPanelVideoControler.getTimeStep();
1648                                            player.setPosition(currentOffset + timeStep);
1649                                    }
1650                            }
1651                    } catch (PlayerException e) {
1652                            //display an error message.
1653                            Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
1654                    }
1655            }
1656    
1657            /**
1658             * Method previousAction.
1659             * <br><b>Summary:</b><br>
1660             * This method is called when user wants to go backward in the video.
1661             */
1662            public void previousAction() {
1663                    try {
1664                            if(player != null) {
1665                                    int currentOffset = player.getPosition();
1666                                    if(currentOffset != -1) {                       
1667                                            //Go forward using the time step.
1668                                            int timeStep = jPanelVideoControler.getTimeStep();
1669                                            player.setPosition(currentOffset - timeStep);
1670                                    }
1671                            }
1672                    } catch (PlayerException e) {
1673                            //display an error message.
1674                            Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
1675                    }
1676            }
1677    
1678            /**
1679             * Method stopAction.
1680             * <br><b>Summary:</b><br>
1681             * This method is called when user wants to stop the video.
1682             */
1683            public void stopAction() {
1684                    if(player != null) {
1685                            try {
1686                                    player.stop();
1687                            } catch (PlayerException e) {
1688                                    //display an error message.
1689                                    Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
1690                            }
1691                    }
1692            }
1693    
1694            /**
1695             * Method playAction.
1696             * <br><b>Summary:</b><br>
1697             * This method is called when user wants to play the video.
1698             */
1699            public void playAction() {
1700                    String playerFile = SearsProperties.getProperty(SearsProperties.PLAYER_FULL_PATH);
1701                    if (playerFile != "") {
1702                            // Save the subtitle in tempory file
1703                            SubtitleFile subFile = subtitleFile;
1704                            if (subFile == null) {
1705                                    // Create a fake subtitle file if no file exists
1706                                    subFile = new SrtFile();
1707                                    subFile.addFakeSub();
1708                            }
1709                            subFile.writeToTemporaryFile();
1710                            String subtitleFileName = subFile.getTemporaryFile().getAbsolutePath();
1711    
1712                            // Launch VLC player
1713                            try {
1714                                    if (player == null) {
1715                                            player = constructPlayer();
1716                                    }
1717                                    // Select the video file
1718                                    if (playerVideoFile == null) {
1719                                            selectVideoAction();
1720                                    } else {
1721                                            //Verify that user has chosen a file.
1722                                            if(playerVideoFile != null) {
1723                                                    //Launch the video player
1724                                                    player.play(playerVideoFile, subtitleFileName);
1725                                            }
1726                                    }
1727                            } catch (PlayerException e) {
1728                                    //display an error message.
1729                                    Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
1730                            }
1731                    } else {
1732                            JOptionPane.showOptionDialog(instance, 
1733                                            SearsResourceBundle.getResource("error_playerNotDefined"), 
1734                                            SearsResourceBundle.getResource("error_message"), 
1735                                            JOptionPane.CLOSED_OPTION,
1736                                            JOptionPane.ERROR_MESSAGE, null, null, null);
1737                    }
1738            }
1739    
1740            /**
1741             * Class AccentRepairAction.
1742             * <br><b>Summary:</b><br>
1743             * The AccentRepair Action
1744             */
1745            class AccentRepairAction extends SearsUndoAction {
1746                    private static final long serialVersionUID = 1881880058709461107L;
1747    
1748                    public AccentRepairAction() {
1749                            super("AccentRepair", undoManager);
1750                    }
1751    
1752                    @Override
1753                    public boolean doUndoableAction(ActionEvent e) {
1754                            return accentRepairAction();
1755                    }
1756                    
1757                    
1758            }
1759    
1760            /**
1761             * Method accentRepairAction.
1762             * <br><b>Summary:</b><br>
1763             * This method is called when user wants to remove 
1764             * the accents and special characters from the selected subtitle file.
1765             * @return true if action succeeded.
1766             */
1767            public boolean accentRepairAction() {
1768                    boolean result = false;
1769                    //Must have opened a subtitle file to repair accents.
1770                    if (subtitleFile != null) {
1771                            //get the selected rows.
1772                            int[] selectedIndex = table.getSelectedRows();
1773                            //Remove accent from them.
1774                            subtitleFile.accentRepair(selectedIndex);
1775                            result = true;
1776                            updateTableAndActions();
1777                    } else {
1778                            //Must have opened a subtitle file to apply a delay.
1779                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeAccentRepair"), Trace.WARNING_PRIORITY);
1780                    }
1781                    return result;
1782            }
1783    
1784            /**
1785             * Class HtmlRepairAction.
1786             * <br><b>Summary:</b><br>
1787             * The HtmlRepair Action
1788             */
1789            class HtmlRepairAction extends SearsUndoAction {
1790                    private static final long serialVersionUID = 2595376205493266568L;
1791    
1792                    public HtmlRepairAction() {
1793                            super("HtmlRepair", undoManager);
1794                    }
1795    
1796                    @Override
1797                    public boolean doUndoableAction(ActionEvent e) {
1798                            return htmlRepairAction();
1799                    }
1800            }
1801    
1802            /**
1803             * Method htmlRepairAction.
1804             * <br><b>Summary:</b><br>
1805             * This method is called when user wants to remove 
1806             * the htmls.
1807             * @return true if action succeeded
1808             */
1809            public boolean htmlRepairAction() {
1810                    boolean result = false;
1811                    //Must have opened a subtitle file to repair.
1812                    if (subtitleFile != null) {
1813                            //get the selected rows.
1814                            int[] selectedIndex = table.getSelectedRows();
1815                            //Remove html tags from them.
1816                            subtitleFile.htmlRepair(selectedIndex);
1817                            result = true;
1818                            updateTableAndActions();
1819                    } else {
1820                            //Must have opened a subtitle file to apply a delay.
1821                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeHTMLRepair"), Trace.WARNING_PRIORITY);
1822                    }
1823                    return result;
1824            }
1825    
1826            /**
1827             * Class SelectVideoAction.
1828             * <br><b>Summary:</b><br>
1829             * The SelectVideo Action
1830             */
1831            class SelectVideoAction extends SearsAction {
1832                    private static final long serialVersionUID = 3081640943800843335L;
1833    
1834                    public SelectVideoAction() {
1835                            super("SelectVideo");
1836                    }
1837    
1838                    /* (non-Javadoc)
1839                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
1840                     */
1841                    public void actionPerformed(ActionEvent e) {
1842                            selectVideoAction();
1843                    }
1844            }
1845    
1846            /**
1847             * Method selectVideoAction.
1848             * <br><b>Summary:</b><br>
1849             * This method is called when user wants to select the video file. 
1850             */
1851            public void selectVideoAction() {
1852                    File file = showVideoBrowser();
1853                    if (file != null) {
1854                            playerVideoFile = file.getAbsolutePath();
1855                            SearsProperties.setProperty(SearsProperties.LAST_FOLDER, file.getParent());
1856                            stopAction();
1857                            playAction();
1858                    }
1859            }
1860    
1861            /**
1862             * Method constructPlayer.
1863             * <br><b>Summary:</b><br>
1864             * This method construct a new player, in function of the one selected in the properties.
1865             * @return
1866             */
1867            private PlayerInterface constructPlayer() throws PlayerException {
1868                    //The result of the method.
1869                    PlayerInterface result = null;
1870                    //First is to get the selected player.
1871                    try {
1872                            int selectedPlayer = Integer.parseInt(SearsProperties.getProperty(SearsProperties.PLAYER_SELECTED));
1873                            //Construct the player that correspond to the new selected one.
1874                            switch (selectedPlayer) {
1875                            case SearsProperties.KEY_VLC:
1876                                    result = new VLCPlayer();
1877                                    break;
1878                            case SearsProperties.KEY_OTHER:
1879                                    result = new DefaultPlayer();
1880                                    break;
1881                            }
1882                    } catch (NumberFormatException e) {
1883                            //There is an error in the player selected.
1884                            throw new PlayerException("Selected player type is invalid:"
1885                                            + SearsProperties.getProperty(SearsProperties.PLAYER_SELECTED));
1886                    }
1887                    //return the result.
1888                    return result;
1889            }
1890    
1891            /**
1892             * Class ChainRepairAction. <br>
1893             * <b>Summary:</b><br>
1894             * The ChainRepair Action
1895             */
1896            class ChainRepairAction extends SearsUndoAction {
1897                    private static final long serialVersionUID = 1973498050830247049L;
1898    
1899                    public ChainRepairAction() {
1900                            super("ChainRepair", undoManager);
1901                    }
1902    
1903                    @Override
1904                    public boolean doUndoableAction(ActionEvent e) {
1905                            return chainRepairAction();
1906                    }
1907            }
1908    
1909            /**
1910             * Method chainRepairAction.
1911             * <br><b>Summary:</b><br>
1912             * This method is called when user wants to chain repair.
1913             * It will apply several repair action.
1914             * @return boolean true if action succeeded
1915             */
1916            public boolean chainRepairAction() {
1917                    boolean result = false;
1918                    //Must have opened a subtitle file to repair.
1919                    if (subtitleFile != null) {
1920                            //Show the chain repair dialog
1921                            JDialogChainRepair cr = new JDialogChainRepair();
1922                            cr.setVisible(true);
1923                            //Get the result.
1924                            boolean[] dialogResult = cr.getResult();
1925                            //Apply repair action in function of the result.
1926                            //The accent repair
1927                            if (dialogResult[0]) {
1928                                    subtitleFile.accentRepair(null);
1929                            }
1930                            //The HTML repair
1931                            if (dialogResult[1]) {
1932                                    subtitleFile.htmlRepair(null);
1933                            }
1934                            //The orderRepair
1935                            if (dialogResult[2]) {
1936                                    subtitleFile.orderRepair();
1937                            }
1938                            //The Time Repair
1939                            if (dialogResult[3]) {
1940                                    subtitleFile.timeRepair();
1941                            }
1942                            result = true;
1943                            updateTableAndActions();
1944                    } else {
1945                            //Must have opened a subtitle file to apply a chain repair.
1946                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeChainRepair"), Trace.WARNING_PRIORITY);
1947                    }
1948                    return result;
1949            }
1950    
1951            /**
1952             * Class TimeRepairAction.
1953             * <br><b>Summary:</b><br>
1954             * The TimeRepair Action
1955             */
1956            class TimeRepairAction extends SearsUndoAction {
1957                    private static final long serialVersionUID = 1635491769577703287L;
1958    
1959                    public TimeRepairAction() {
1960                            super("TimeRepair", undoManager);
1961                    }
1962    
1963                    @Override
1964                    public boolean doUndoableAction(ActionEvent e) {
1965                            return timeRepairAction();
1966                    }
1967            }
1968    
1969            /**
1970             * Class NormalizeDurationAction.
1971             * <br><b>Summary:</b><br>
1972             * The NormalizeDuration Action
1973             */
1974            class NormalizeDurationAction extends SearsUndoAction {
1975    
1976                    /** (<b>long</b>) serialVersionUID: The serialVersionUID */
1977                    private static final long serialVersionUID = -7636895212998797145L;
1978    
1979                    public NormalizeDurationAction() {
1980                            super("NormalizeDuration", undoManager);
1981                    }
1982    
1983                    @Override
1984                    public boolean doUndoableAction(ActionEvent e) {
1985                            return normalizeDurationAction();
1986                    }
1987                    
1988            }
1989    
1990    
1991            /**
1992             * Method getSubtitleFile.
1993             * <br><b>Summary:</b><br>
1994             * Return the subtitleFile.
1995             * @return the subtitleFile
1996             */
1997            public SubtitleFile getSubtitleFile() {
1998                    return subtitleFile;
1999            }
2000    
2001            /**
2002             * Method NormalizeDurationAction.
2003             * <br><b>Summary:</b><br>
2004             * This permit to normalize the ST duration.
2005             * @return boolean      true if action succeeded
2006             */
2007            private boolean normalizeDurationAction() {
2008                    boolean result = false;
2009                    //Must have opened a subtitle file to apply a delay.
2010                    if (subtitleFile != null) {
2011                            //get the selected rows to delay.
2012                            int[] indexToNormalize = table.getSelectedRows();
2013                            //ask the user for delay to apply
2014                            JDialogNormalizeAction jd = new JDialogNormalizeAction();
2015                            jd.setVisible(true);
2016                            //if user choose a delay.
2017                            if (jd.hasBeenValidated()) {
2018                                    double[] times = jd.getTimes();
2019                                    //delay the subtitle with the given delay.
2020                                    //multiply by 1000, because time is indicated in second, with possible milliseconds.
2021                                    subtitleFile.normalizeDuration(indexToNormalize, (int) (times[0] * 1000), (int) (times[1] * 1000));
2022                                    result = true;
2023                                    updateTableAndActions();
2024                            }
2025                    } else {
2026                            //Must have opened a subtitle file to apply a delay.
2027                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeNormalize"), Trace.WARNING_PRIORITY);
2028                    }
2029                    return result;
2030            }
2031    
2032            /**
2033             * Class OptionsAction.
2034             * <br><b>Summary:</b><br>
2035             * The Options Action
2036             */
2037            class OptionsAction extends SearsAction {
2038    
2039    
2040                    /** (<b>long</b>) serialVersionUID: The serialVersionUID */
2041                    private static final long serialVersionUID = -4333319058198937067L;
2042    
2043                    public OptionsAction() {
2044                            super("Options");
2045                    }
2046    
2047                    /* (non-Javadoc)
2048                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
2049                     */
2050                    public void actionPerformed(ActionEvent e) {
2051                            optionsAction();
2052                    }
2053            }
2054    
2055            /**
2056             * Method optionsAction.
2057             * <br><b>Summary:</b><br>
2058             * This method is called when user wants to change sears options.
2059             * It will display the options dialog, and set the sears properties.
2060             */
2061            public void optionsAction() {
2062                    //Show ans options dialog.
2063                    JDialogOptions jDialogOptions = new JDialogOptions();
2064                    jDialogOptions.setVisible(true);
2065                    //If options has been validated, set the new options.
2066                    if (jDialogOptions.hasBeenValidated()) {
2067                            //A boolean to know if a message has to be displayed.
2068                            boolean displayRebootMessage = false;
2069                            //set the selected locale, if changed.
2070                            Locale selectedLocale = jDialogOptions.getSelectedLocale();
2071                            if (!SearsProperties.getProperty(SearsProperties.LANGUAGE, "").equals(
2072                                            selectedLocale.getLanguage().toString())
2073                                            || !SearsProperties.getProperty(SearsProperties.COUNTRY, "").equals(
2074                                                            selectedLocale.getCountry().toString())) {
2075                                    setSelectedLocale(selectedLocale);
2076                                    //To see changes, you have to reboot.
2077                                    displayRebootMessage = true;
2078                            }
2079                            //set the look and feel if changed.
2080                            String selectedLookAndFeel = jDialogOptions.getSelectedLookAndFeel();
2081                            if (!selectedLookAndFeel.equals(SearsProperties.getProperty(SearsProperties.LOOK_AND_FEEL, ""))) {
2082                                    setLookAndFeel(selectedLookAndFeel);
2083                                    //To see changes, you have to reboot.
2084                                    displayRebootMessage = true;
2085                            }
2086                            //set the player if changed.
2087                            String selectedPlayerPath = jDialogOptions.getConfiguredPlayerPath();
2088                            int selectedPlayer = jDialogOptions.getSelectedPlayer();
2089                            if (!selectedPlayerPath.equals(SearsProperties.getProperty(SearsProperties.PLAYER_FULL_PATH, ""))
2090                                            || !("" + selectedPlayer).equals(SearsProperties.getProperty(SearsProperties.PLAYER_SELECTED, ""))) {
2091                                    setPlayer(selectedPlayerPath, selectedPlayer);
2092                            }
2093                            //Set the DOS line separator options, if changed.
2094                            String selectedDOSLineSeparator = jDialogOptions.getSelectedDOSLineSeparator();
2095                            if (!selectedDOSLineSeparator.equals(SearsProperties.getProperty(SearsProperties.DOS_LINE_SEPARATOR, ""))) {
2096                                    SearsProperties.setProperty(SearsProperties.DOS_LINE_SEPARATOR, selectedDOSLineSeparator);
2097                            }
2098                            //Set the check for update option, if changed.
2099                            boolean selectedCheckForUpdates = jDialogOptions.getSelectedCheckForUpdates();
2100                            if (selectedCheckForUpdates
2101                                            && SearsProperties.getProperty(SearsProperties.UPDATE_ADDRESS, "firstLaunch").equals("")) {
2102                                    //If user checks, and no address has been set in properties.
2103                                    //then set the default one.
2104                                    SearsProperties.setProperty(SearsProperties.UPDATE_ADDRESS, Updater.DEFAULT_UPDATE_ADDRESS);
2105                            } else if (!selectedCheckForUpdates) {
2106                                    //Else if user does not want to check for updates, set to "".
2107                                    SearsProperties.setProperty(SearsProperties.UPDATE_ADDRESS, "");
2108                            }
2109                            //Set the VLC restart options, if changed.
2110                            String selectedVlcRestart = jDialogOptions.getSelectedVlcRestart();
2111                            if (!selectedVlcRestart.equals(SearsProperties.getProperty(SearsProperties.VLC_RESTART,
2112                                            VLCPlayer.DEFAULT_VLC_RESTART))) {
2113                                    SearsProperties.setProperty(SearsProperties.VLC_RESTART, selectedVlcRestart);
2114                            }
2115                            //Set the icon set option, if changed.
2116                            String selectedIconSet = jDialogOptions.getSelectedIconSet();
2117                            if (selectedIconSet != null && !selectedIconSet.equals(SearsProperties.getProperty(SearsProperties.ICON_SET))) {
2118                                    SearsProperties.setProperty(SearsProperties.ICON_SET, selectedIconSet);
2119                                    displayRebootMessage = true;
2120                            }
2121                            // if a reset properties operation has been called:
2122                            if (getResetFlag()) {
2123                                    displayRebootMessage = true;
2124                            }
2125                            if (displayRebootMessage) {
2126                                    // if langage change is request, the text in the dialog is
2127                                    // in the new langage choised. 
2128                                    JOptionPane.showMessageDialog (
2129                                                    instance,
2130                                                    SearsResourceBundle.getResource("warning_rebootToSeeChanges"));
2131                            }
2132    
2133                    }
2134            }
2135    
2136            /**
2137             * Class AboutAction.
2138             * <br><b>Summary:</b><br>
2139             * The About Action
2140             */
2141            class AboutAction extends SearsAction {
2142                    /** (<b>long</b>) serialVersionUID: The serialVersionUID */
2143                    private static final long serialVersionUID = 2988713944030176952L;
2144    
2145                    public AboutAction() {
2146                            super("About");
2147                    }
2148    
2149                    /* (non-Javadoc)
2150                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
2151                     */
2152                    public void actionPerformed(ActionEvent e) {
2153                            aboutAction();
2154                    }
2155            }
2156    
2157            /**
2158             * Method dialogAction.
2159             * <br><b>Summary:</b><br>
2160             * This method is called when user wants to see the about dialog window.
2161             * It will display the about dialog..
2162             */
2163            public void aboutAction() {
2164                    // we create a new JDialogAbout and set it visible:
2165                    JDialogAbout jDialogAbout = new JDialogAbout("about");
2166                    jDialogAbout.configureSize();
2167                    jDialogAbout.setVisible(true);
2168            }
2169    
2170            /**
2171             * Class GotoSubAction.
2172             * <br><b>Summary:</b><br>
2173             * The GotoSub Action
2174             */
2175            class GotoSubAction extends SearsAction {
2176                    private static final long serialVersionUID = 4897317673745710990L;
2177    
2178                    public GotoSubAction() {
2179                            super("GotoSub");
2180                    }
2181    
2182                    /* (non-Javadoc)
2183                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
2184                     */
2185                    public void actionPerformed(ActionEvent e) {
2186                            gotoSubAction();
2187                    }
2188            }
2189    
2190            /**
2191             * Class MagicResynchroAction.
2192             * <br><b>Summary:</b><br>
2193             * The MagicResynchro
2194             */
2195            class MagicResynchroAction extends SearsUndoAction {
2196    
2197                    /** (<b>long</b>) serialVersionUID: The serialVersionUID */
2198                    private static final long serialVersionUID = 7188578370344829732L;
2199    
2200                    public MagicResynchroAction() {
2201                            super("MagicResynchro", undoManager);
2202                    }
2203    
2204                    @Override
2205                    public boolean doUndoableAction(ActionEvent e) {
2206                            return magicResynchroAction();
2207                    }
2208            }
2209    
2210            /**
2211             * Class RemoveAllAnchorsAction.
2212             * <br><b>Summary:</b><br>
2213             * The RemoveAllAnchorsAction
2214             */
2215            class RemoveAllAnchorsAction extends SearsAction {
2216    
2217                    /** (<b>long</b>) serialVersionUID: The serialVersionUID */
2218                    private static final long serialVersionUID = 2484034561626602996L;
2219    
2220                    public RemoveAllAnchorsAction() {
2221                            super("RemoveAllAnchors");
2222                    }
2223    
2224                    /* (non-Javadoc)
2225                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
2226                     */
2227                    public void actionPerformed(ActionEvent e) {
2228                            removeAllAnchorsAction();
2229                    }
2230            }
2231    
2232            /**
2233             * Method removeAllAnchorsAction.
2234             * <br><b>Summary:</b><br>
2235             * Call this method to remove all the anchors from subtitles.
2236             */
2237            private void removeAllAnchorsAction() {
2238                    if(subtitleList != null){
2239                            //Parse all the subtitles, and remove anchor from them.
2240                            for(Subtitle subtitle : subtitleList){
2241                                    subtitle.unanchor();
2242                            }
2243                            updateActionStatus();
2244                            //update table.
2245                            updateWholeTable();
2246                    }
2247            }
2248    
2249            /**
2250             * Class FindAction.
2251             * <br><b>Summary:</b><br>
2252             * The search perform
2253             */
2254            class FindAction extends SearsAction {          
2255    
2256                    /** (<b>long</b>) serialVersionUID: The serialVersionUID */
2257                    private static final long serialVersionUID = 5092954733041300116L;
2258    
2259                    public FindAction() {
2260                            super("Find");
2261                    }
2262    
2263                    /* (non-Javadoc)
2264                     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
2265                     */
2266                    public void actionPerformed(ActionEvent e) {
2267                            if( table.getCellEditor() != null ) {
2268                                    table.getCellEditor().cancelCellEditing();
2269                            }
2270                            new JDialogFind(instance).setVisible(true);
2271                    }
2272            }
2273    
2274            /**
2275             * indicate to the find module that the table changed
2276             */
2277            private void tableChangedSinceTheLastSearch() {
2278                    if( fm != null ) {
2279                            fm.fireViewChange();
2280                    }
2281            }
2282    
2283            /**
2284             * find a word or a sub string in the subtitle file
2285             * @param dialog        the dialog which calls the method
2286             * @param direction     the direction of the search (<tt>false</tt>: forward, <tt>true</tt>: backward)
2287             */
2288            public void find(FindDialog dialog, boolean direction) {
2289                    //long start = System.nanoTime();
2290                    fm.fire(dialog, subtitleList, direction);
2291                    //System.out.println("Find, time of execution: " + (System.nanoTime() - start) + " ns");
2292            }
2293    
2294            public void fireFindStop() {
2295                    if( fm != null ) {
2296                            fm.cancel();
2297                    }
2298            }
2299    
2300    
2301            /**
2302             * MixAction
2303             * this permits to Mix two subtitle files, inserting the subtitle at the right place.
2304             */
2305            class MixAction extends SearsUndoAction {
2306    
2307                    private static final long serialVersionUID = 7753043190773883390L;
2308    
2309                    public MixAction() {
2310                            super("Mix", undoManager);
2311                    }
2312    
2313                    @Override
2314                    public boolean doUndoableAction(ActionEvent e) {
2315                            // TODO Auto-generated method stub
2316                            return mixAction();     
2317                    }               
2318            }
2319    
2320            /**
2321             * Method mixAction.
2322             * <br><b>Summary:</b><br>
2323             * This action mix 2 subtitle files.
2324             * @return  (<b>boolean</b>)   true if action succeeded.
2325             */
2326            private boolean mixAction() {
2327                    boolean result = false;
2328                    // we must have an opened subtitle file on Sears:
2329                    if (subtitleFile != null) {
2330                            // we display the mix dialog:
2331                            JDialogMixSubtitleFile mixDialog = new JDialogMixSubtitleFile(
2332                                            subtitleList.size());
2333                            mixDialog.setLocationRelativeTo(this);
2334                            mixDialog.setVisible(true);
2335    
2336                            // if action performed:
2337                            if(mixDialog.validationStatus) {
2338                                    SubtitleFile subForTheMix = fsa.openFile(
2339                                                    new File(mixDialog.getJTextFieldFileToMix().getText()),
2340                                                    new ArrayList<Subtitle>(), null);
2341    
2342                                    subtitleFile.mixWithAnotherSubtitleFile(
2343                                                    subForTheMix, mixDialog.keepSubtitles());
2344                                    result = true;
2345                                    updateTableAndActions();
2346                            }
2347                    }else {
2348                            //Must have opened a subtitle file to apply a delay.
2349                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeMix"), Trace.WARNING_PRIORITY);
2350                    }
2351                    return result;
2352            }
2353    
2354    
2355            /**
2356             * Method gotoSubAction.
2357             * <br><b>Summary:</b><br>
2358             * This method permits to set the video position to the selected subtitle.
2359             */
2360            public void gotoSubAction() {
2361                    //First is to get the selected subtitle.
2362                    int selectedRow = table.getSelectedRow();
2363                    //If selected and player is not null, go to this subtitle.
2364                    if(player != null && selectedRow != -1){
2365                            Subtitle selectedSubtitle = (Subtitle) subtitleList.get(selectedRow);
2366                            if(selectedSubtitle != null){
2367                                    try {
2368                                            //Divide by 1000, because start date is in milliseconds.
2369                                            player.setPosition(selectedSubtitle.getStartDate()/1000);
2370                                    } catch (PlayerException e) {
2371                                            Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
2372                                    }
2373                            }
2374                    }
2375            }
2376    
2377            /**
2378             * Method setPlayer.
2379             * <br><b>Summary:</b><br>
2380             * Set the player.
2381             * @param selectedPlayerPath    The player full path to set.
2382             * @param selectedPlayer        The player to set.
2383             */
2384            private void setPlayer(String selectedPlayerPath, int selectedPlayer) {
2385                    SearsProperties.setProperty(SearsProperties.PLAYER_FULL_PATH, selectedPlayerPath);
2386                    SearsProperties.setProperty(SearsProperties.PLAYER_SELECTED, "" + selectedPlayer);
2387                    if (player != null) {
2388                            //quit previous player if exist.
2389                            player.quit();
2390                            //disable the player
2391                            player = null;
2392                    }
2393                    //construct the new player
2394                    try {
2395                            player = constructPlayer();
2396                    } catch (PlayerException e) {
2397                            e.printStackTrace();
2398                    }
2399            }
2400    
2401    
2402            /**
2403             * Method setSelectedLocale.
2404             * <br><b>Summary:</b><br>
2405             * Use this to set the locale.
2406             * @param selectedLocale        The locale to set.
2407             */
2408            private void setSelectedLocale(Locale selectedLocale) {
2409                    SearsResourceBundle.setLocale(selectedLocale);
2410                    SearsProperties.setProperty(SearsProperties.LANGUAGE, selectedLocale.getLanguage());
2411                    SearsProperties.setProperty(SearsProperties.COUNTRY, selectedLocale.getCountry());
2412                    setLocale(selectedLocale);
2413            }
2414    
2415            /**
2416             * Method setLookAndFeel.
2417             * <br><b>Summary:</b><br>
2418             * Use this method to sets the look and feel.
2419             * @param selectedLookAndFeel       The look and feel to set.
2420             */
2421            private void setLookAndFeel(String selectedLookAndFeel) {
2422                    SearsProperties.setProperty(SearsProperties.LOOK_AND_FEEL, selectedLookAndFeel);
2423            }
2424    
2425            /**
2426             * Method timeRepairAction.
2427             * <br><b>Summary:</b><br>
2428             * This method is called when user wants to time repair.
2429             * It will correct the time superposition problem.
2430             * When subtitle ends after next ST start time.
2431             * @return boolean true if action succeeded
2432             */
2433            public boolean timeRepairAction() {
2434                    boolean result = false;
2435                    //Must have opened a subtitle file to repair.
2436                    if (subtitleFile != null) {
2437                            //Apply Time repair
2438                            subtitleFile.timeRepair();
2439                            result = true;
2440                            updateTableAndActions();
2441                    } else {
2442                            //Must have opened a subtitle file to apply a delay.
2443                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeTimeRepair"), Trace.WARNING_PRIORITY);
2444                    }
2445                    return result;
2446            }
2447    
2448            /**
2449             * Method splitAction.
2450             * <br><b>Summary:</b><br>
2451             * This method is called when user wants to split STs file in two files.
2452             * Must have selected the ST to split from.
2453             * @return boolean true if action succeed.
2454             */
2455            public boolean splitAction() {
2456                    boolean result = false;
2457                    //Must have opened a subtitle.
2458                    if (subtitleFile != null) {
2459                            int selectedIndex = table.getSelectedRow();
2460                            if (selectedIndex == -1) {
2461                                    selectedIndex = 0;
2462                            }
2463                            //show Split dialog.
2464                            JDialogSplit splitDialog = new JDialogSplit(subtitleList, selectedIndex, subtitleFile);
2465                            splitDialog.setLocationRelativeTo(this);
2466                            splitDialog.setVisible(true);
2467                            if (splitDialog.hasBeenValidated()) {
2468                                    //Get the destination files.
2469                                    File[] destinationFiles = splitDialog.getDestinationsFiles();
2470                                    //The the second part delay
2471                                    double secondPartDelay = splitDialog.getSecondPartDelay();
2472                                    //Look for open second part option.
2473                                    boolean openSecondPart = splitDialog.needToOpenSecondPart();
2474                                    //Get the choosen subtitle for split.
2475                                    int subtitleIndex = splitDialog.getChoosenSubtitle();
2476                                    //Apply spliting to subtitleFile.
2477                                    //multiply by 1000, because delay is indicated in second, with possible milliseconds.
2478                                    SubtitleFile[] parts = subtitleFile.split(destinationFiles, subtitleIndex,
2479                                                    (int) (secondPartDelay * 1000));
2480                                    //At the end save the two files.
2481                                    Trace.trace("Saving splitted files", Trace.ALGO_PRIORITY);
2482                                    try {
2483                                            parts[0].writeToFile(parts[0].getFile());
2484                                            parts[1].writeToFile(parts[1].getFile());
2485                                    } catch (FileConversionException e) {
2486                                            // TODO Auto-generated catch block
2487                                            e.printStackTrace();
2488                                    }
2489    
2490                                    //If need to open second part, open it !
2491                                    if (openSecondPart) {
2492                                            openFile(parts[1].getFile());
2493                                    }
2494                            }
2495                            result = true;
2496                            updateWindowsName();
2497                            updateActionStatus();
2498                    } else {
2499                            //Must have opened a subtitle file to apply a split.
2500                            Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeSplit"), Trace.WARNING_PRIORITY);
2501                    }
2502                    return result;
2503            }
2504    
2505            /**
2506             * Method appendAction.
2507             * <br><b>Summary:</b><br>
2508             * This method is called when user wants to make an append Action.
2509             * @return result       true if action succeeded.
2510             */
2511            public boolean appendAction() {
2512                    boolean result = false;
2513                    //Must have opened a subtitle.
2514                    if (subtitleFile != null) {
2515                            //show Append dialog.
2516                            JDialogAppend appendDialog = new JDialogAppend();
2517                            appendDialog.setVisible(true);
2518                            if (appendDialog.hasBeenValidated()) {
2519                                    File fileToAppend = appendDialog.getFileToAppend();
2520                                    double delay = appendDialog.getDelay();
2521                                    if (fileToAppend != null) {
2522                                            // instantiate subtileFile
2523                                            SubtitleFile subtitleFileToAppend = null;
2524                                            subtitleFileToAppend = fsa.openFile(fileToAppend, null);
2525                                            if( subtitleFileToAppend != null ) {
2526                                                    // Append file to current one.
2527                                                    // multiply by 1000, because delay is indicated in second, with possible milliseconds.
2528                                                    subtitleFile.append(subtitleFileToAppend, (int) (delay * 1000));
2529                                                    //indicate table that data have changed
2530                                                    result = true;
2531                                                    updateTableAndActions();
2532                                            } // else do nothing
2533                                    }
2534                            } else {
2535                                    //Must have opened a subtitle file to apply an append.
2536                                    Trace.trace(SearsResourceBundle.getResource("warning_mustOpenBeforeAppend"), Trace.WARNING_PRIORITY);
2537                            }
2538                    }
2539                    return result;
2540            }
2541    
2542            /**
2543             * Method <b>watchForUnsavedFile</b>
2544             * <br><b>Summary:</b><br>
2545             * This method check that current file (if open) does not have unsaved modifications.
2546             * If it has, ask for user to "save", "don't save" or "cancel".
2547             * If user click on save, current file will be saved, method returns true.
2548             * If user click on save, current file is not saved, method returns true.
2549             * if user click on cancel, file is not saved, method returns false.
2550             * Parameters:
2551             * @return  <b>boolean</b>      True if user wants to continue (click on "save" or "don't save")
2552             *                              , or if file has no modifications. false if user clicked on "cancel"
2553             * 
2554             */
2555            private boolean checkUnsavedFileBeforeAction() {
2556                    //The result of the method
2557                    boolean result = true;
2558                    //If there already is an unsaved open file, warn the user before opening.
2559                    if (subtitleFile != null && subtitleFile.isFileChanged()) {
2560                            result = false;
2561                            //File is not saved, ask for saving.
2562                            String message = SearsResourceBundle.getResource("warning_fileNotSaved");
2563                            //Custom button text
2564                            Object[] options = { SearsResourceBundle.getResource("button_save"),
2565                                            SearsResourceBundle.getResource("button_dontSave"),
2566                                            SearsResourceBundle.getResource("button_cancel") };
2567                            int userChoice = JOptionPane.showOptionDialog(instance, message, SearsResourceBundle
2568                                            .getResource("warning_fileNotSaved"), JOptionPane.YES_NO_CANCEL_OPTION,
2569                                            JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
2570                            //If user choose to save file, save it, and return true.
2571                            if (userChoice == 0) {
2572                                    saveAction();
2573                                    result = true;
2574                            } else if (userChoice == 1) {
2575                                    //if user choosed don't save, just return true
2576                                    result = true;
2577                            }//else return false.
2578                    }
2579                    //return the result
2580                    return result;
2581            }
2582    
2583            /**
2584             * Method updateWindowsName.
2585             * <br><b>Summary:</b><br>
2586             * Update the main windows name.
2587             */
2588            private void updateWindowsName() {
2589                    String title = SearsResources.getString("SearsGUITitle")+Version.VERSION;
2590                    if (subtitleFile != null) {
2591                            title += " - " + subtitleFile.getCharset() + " - " + subtitleFile.getFile().getName();
2592                            if (subtitleFile.isFileChanged()) {
2593                                    title += "*";
2594                            }
2595                    }
2596                    setTitle(title);
2597            }
2598    
2599            /* (non-Javadoc)
2600             * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
2601             */
2602            public void mouseClicked(MouseEvent e) {
2603                    maybeShowPopup(e);
2604                    mayBeAnchor(e);
2605            }
2606    
2607            /**
2608             * Method mayBeAnchor.
2609             * <br><b>Summary:</b><br>
2610             * This method is called whn a click is detected.
2611             * It permits to check an anchor event.
2612             * @param e             The mouseEvent that called for this mayBeAnchor.
2613             */
2614            private void mayBeAnchor(MouseEvent e) {
2615                    if (e.getButton() == MouseEvent.BUTTON1 && e.getComponent() == table) {
2616                            //click must be on 4rth column.
2617                            Point clickPoint = new Point(e.getX(), e.getY());
2618                            if(table.columnAtPoint(clickPoint) == SubtitleTableModel.ANCHOR_COLUMN){
2619                                    //retrieve the clicked row.
2620                                    int row = table.rowAtPoint(clickPoint);
2621                                    anchorSubtitle(row);
2622                            }
2623                    }
2624    
2625            }
2626    
2627            /**
2628             * Method anchorSubtitle.
2629             * <br><b>Summary:</b><br>
2630             * This method permits to anchor/un-anchor the given index subtitle.
2631             * @param subtitleIndex         The subtitle index to anchor.
2632             */
2633            private void anchorSubtitle(int row) {
2634                    //Retrieve current playing time.
2635                    if (player != null) {
2636                            try {
2637                                    // Retrieve the given subtitle.
2638                                    Subtitle subtitle = subtitleList.get(row);
2639                                    if (subtitle.isAnchored()) {
2640                                            subtitle.unanchor();
2641                                    } else {
2642                                            int currentPosition = player.getPosition();
2643                                            subtitle.anchor(currentPosition);
2644                                    }
2645                                    //Indicate to table, that data changed.
2646                                    updateWholeTable();
2647                                    //Update actions
2648                                    updateActionStatus();
2649                            } catch (PlayerException e) {
2650                                    e.printStackTrace();
2651                            }
2652                    }
2653            }
2654    
2655            /* (non-Javadoc)
2656             * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
2657             */
2658            public void mouseEntered(MouseEvent e) {
2659            }
2660    
2661            /* (non-Javadoc)
2662             * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
2663             */
2664            public void mouseExited(MouseEvent e) {
2665            }
2666    
2667            /* (non-Javadoc)
2668             * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
2669             */
2670            public void mousePressed(MouseEvent e) {
2671                    maybeShowPopup(e);
2672            }
2673    
2674            /**
2675             * Method maybeShowPopup.
2676             * <br><b>Summary:</b><br>
2677             * Show the popup menu if necessary
2678             * @param e The mouse event.
2679             */
2680            private void maybeShowPopup(MouseEvent e) {
2681                    // emulate the right click on Mac (crtl down and click):
2682                    boolean macCondition = false;
2683                    // if we are running on Mac OS X system:
2684                    if(Utils.isMacPlatform) {
2685                            macCondition = (e.getButton() == MouseEvent.BUTTON1);
2686                            macCondition = macCondition && (e.isControlDown());
2687                    }       
2688    
2689                    // Check wether it is a right button, or a system pop-up trigger, 
2690                    // or an emulation of a right click (Mac OS X only).
2691                    if (e.getButton() == MouseEvent.BUTTON3 || e.isPopupTrigger() || macCondition) {                        
2692                            getJPopUpMenu().show(e.getComponent(), e.getX(), e.getY());
2693                            table.changeSelection(table.rowAtPoint(new Point(e.getX(), e.getY())),
2694                                            0, false, false);
2695                    }
2696            }
2697    
2698            /**
2699             * Method getJPopUpMenu.
2700             * <br><b>Summary:</b><br>
2701             * Construct/return the JPopUpMenu.
2702             * @return  <b>JPopupMenu</b>   The Sears JPopupMenu.
2703             */
2704            private JPopupMenu getJPopUpMenu() {
2705                    if (jPopupMenu == null) {
2706                            jPopupMenu = new JPopupMenu("Sears");
2707                            //set border for a better display:
2708                            jPopupMenu.setBorder(new EmptyBorder(4,0,4,0));
2709                            //////////////////
2710                            //ACTION MENU
2711                            /////////////////
2712                            //DELAY ITEM
2713                            jPopupMenu.add(getAction(ACTION_KEY_DELAY));
2714                            //RESYNCHRO ITEM
2715                            jPopupMenu.add(getAction(ACTION_KEY_RESYNCH));
2716                            //Add a separator.
2717                            jPopupMenu.addSeparator();
2718                            //SPLIT ITEM
2719                            jPopupMenu.add(getAction(ACTION_KEY_SPLIT));
2720                            //APPEND ITEM
2721                            jPopupMenu.add(getAction(ACTION_KEY_APPEND));
2722                            //Add a separator.
2723                            jPopupMenu.addSeparator();
2724                            //GOTO SUB
2725                            jPopupMenu.add(getAction(ACTION_KEY_GOTO_SUB));
2726                    }
2727                    return jPopupMenu;
2728            }
2729    
2730            /**
2731             * Method <b>fileChanged</b>
2732             * <br><b>Summary:</b><br>
2733             * Set the fileChanged status flag to true.
2734             */
2735            public void fileChanged() {
2736                    if (subtitleFile != null) {
2737                            subtitleFile.fileChanged();
2738                            tableChangedSinceTheLastSearch();
2739                    }
2740                    updateWindowsName();
2741            }
2742    
2743            /* (non-Javadoc)
2744             * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
2745             */
2746            public void mouseReleased(MouseEvent e) {
2747                    updateActionStatus();
2748            }
2749    
2750            /* (non-Javadoc)
2751             * @see java.awt.event.WindowListener#windowActivated(java.awt.event.WindowEvent)
2752             */
2753            public void windowActivated(WindowEvent e) {
2754                    // TODO Auto-generated method stub
2755            }
2756    
2757            /* (non-Javadoc)
2758             * @see java.awt.event.WindowListener#windowClosed(java.awt.event.WindowEvent)
2759             */
2760            public void windowClosed(WindowEvent e) {
2761                    dispose();
2762            }
2763    
2764            /* (non-Javadoc)
2765             * @see java.awt.event.WindowListener#windowClosing(java.awt.event.WindowEvent)
2766             */
2767            public void windowClosing(WindowEvent e) {
2768                    quitAction();
2769            }
2770    
2771            /* (non-Javadoc)
2772             * @see java.awt.event.WindowListener#windowDeactivated(java.awt.event.WindowEvent)
2773             */
2774            public void windowDeactivated(WindowEvent e) {
2775                    // TODO Auto-generated method stub
2776            }
2777    
2778            /* (non-Javadoc)
2779             * @see java.awt.event.WindowListener#windowDeiconified(java.awt.event.WindowEvent)
2780             */
2781            public void windowDeiconified(WindowEvent e) {
2782                    // TODO Auto-generated method stub
2783            }
2784    
2785            /* (non-Javadoc)
2786             * @see java.awt.event.WindowListener#windowIconified(java.awt.event.WindowEvent)
2787             */
2788            public void windowIconified(WindowEvent e) {
2789                    // TODO Auto-generated method stub
2790            }
2791    
2792            /* (non-Javadoc)
2793             * @see java.awt.event.WindowListener#windowOpened(java.awt.event.WindowEvent)
2794             */
2795            public void windowOpened(WindowEvent e) {
2796                    // TODO Auto-generated method stub
2797            }
2798    
2799    
2800            /**
2801             * Method updateActionStatus.
2802             * <br><b>Summary:</b><br>
2803             * This method permits to update the action enabling status
2804             */
2805            private void updateActionStatus(){
2806                    //File dependant Actions.
2807                    boolean enableFileDependant = false;
2808                    if(subtitleFile != null){
2809                            enableFileDependant = true;             
2810                    }
2811                    for (int i = 0; i < FILE_OPEN_DEPENDANT_ACTIONS.length; i++) {
2812                            actions.get(FILE_OPEN_DEPENDANT_ACTIONS[i]).setEnabled(enableFileDependant);
2813                    }
2814                    //File changed dependant.
2815                    boolean enableFileChangedDependant = false;
2816                    if(subtitleFile != null && subtitleFile.isFileChanged()){
2817                            enableFileChangedDependant = true;
2818                    }
2819                    for (int i = 0; i < FILE_CHANGED_DEPENDANT_ACTIONS.length; i++) {
2820                            actions.get(FILE_CHANGED_DEPENDANT_ACTIONS[i]).setEnabled(enableFileChangedDependant);
2821                    }
2822                    //Then update actions, that depends on anchors number.
2823                    int anchorsNumber = getAnchorsNumber();
2824                    if(anchorsNumber == 0){
2825                            actions.get(ACTION_KEY_MAGIC_RESYNCHRO).setEnabled(false);
2826                            actions.get(ACTION_KEY_REMOVE_ALL_ANCHORS).setEnabled(false);
2827                    }else if (anchorsNumber < 2){
2828                            //need at least 2 anchors to apply a magic resynchro
2829                            actions.get(ACTION_KEY_MAGIC_RESYNCHRO).setEnabled(false);
2830                            actions.get(ACTION_KEY_REMOVE_ALL_ANCHORS).setEnabled(true);
2831                    }else {
2832                            actions.get(ACTION_KEY_MAGIC_RESYNCHRO).setEnabled(true);
2833                            actions.get(ACTION_KEY_REMOVE_ALL_ANCHORS).setEnabled(true);
2834                    }
2835            }
2836    
2837            /**
2838             * Method getAnchorsNumber.
2839             * <br><b>Summary:</b><br>
2840             * This method returns the number of defined anchors.
2841             * @return  (<b>int</b>)   The number of defined anchors.
2842             */
2843            private int getAnchorsNumber() {
2844                    //the result of the method.
2845                    int result = 0;
2846                    if(subtitleList != null){
2847                            for(Subtitle subtitle : subtitleList){
2848                                    if(subtitle.isAnchored()){
2849                                            result ++;
2850                                    }
2851                            }
2852                    }
2853                    //return the result.
2854                    return result;
2855            }
2856    
2857            protected static void setResetFlag(boolean resetOnExit) {
2858                    resetFlag = resetOnExit;
2859            }
2860    
2861            /**
2862             * Class SubtitleFileTransfertHandler.
2863             * Summary:
2864             * This TransferHanlder check for subtitle file drag and drop.
2865             * It permits to open Subtitle file that are dragged from OS's location, such as 
2866             * Navigator.
2867             */
2868            protected class SubtitleFileTransfertHandler extends TransferHandler {
2869                    private static final long serialVersionUID = 5833961095020955565L;
2870    
2871                    public SubtitleFileTransfertHandler() {
2872                            super();
2873                    }
2874    
2875                    /* (non-Javadoc)
2876                     * @see javax.swing.TransferHandler#canImport(javax.swing.JComponent, java.awt.datatransfer.DataFlavor[])
2877                     */
2878                    public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
2879                            //Check import available.
2880                            //The result of the method.
2881                            boolean result = false;
2882                            //Parse the array.
2883                            for (int i = 0; i < transferFlavors.length; i++) {
2884                                    //retrieve current element.
2885                                    DataFlavor dataFlavor = transferFlavors[i];
2886                                    //We could only import FileList flavor.
2887                                    //Which are files to be opened by Sears.
2888                                    if (dataFlavor.isFlavorJavaFileListType()) {
2889                                            result = true;
2890                                    }
2891                            }
2892                            //return the result.
2893                            return result;
2894                    }
2895    
2896                    /* (non-Javadoc)
2897                     * @see javax.swing.TransferHandler#importData(javax.swing.JComponent, java.awt.datatransfer.Transferable)
2898                     */
2899                    @SuppressWarnings("unchecked")
2900                    public boolean importData(JComponent comp, Transferable t) {
2901                            //the result of the method.
2902                            boolean result = false;
2903                            try {
2904                                    Object list = t.getTransferData(DataFlavor.javaFileListFlavor);
2905                                    if (list instanceof List && ((List<File>) list).size() > 0) {
2906                                            //Get the first file of the list, and open it.
2907                                            File fileToOpen = ((List<File>) list).get(0);
2908                                            instance.openFile(fileToOpen);
2909                                            result = true;
2910                                    }
2911                            } catch (UnsupportedFlavorException exception) {
2912                                    // TODO Auto-generated catch block
2913                                    exception.printStackTrace();
2914                            } catch (IOException exception) {
2915                                    // TODO Auto-generated catch block
2916                                    exception.printStackTrace();
2917                            }
2918                            //return the result
2919                            return result;
2920                    }
2921            }
2922    
2923            /**
2924             * Class PlayerTimeHandler.
2925             * Summary:
2926             * This PlayerTimeHandler check for video length and position.
2927             */
2928            private class PlayerTimeHandler implements Runnable, ChangeListener {
2929                    private boolean jSliderIsMoving  = false;
2930                    private boolean jSliderIsUpdating  = false;
2931                    private long updateDate = 0;
2932                    private int currentPosition = 0;
2933                    private int oldPosition = 0;
2934                    private int subtitleFocusIndex = -1;
2935                    private boolean run = true;
2936    
2937                    /* (non-Javadoc)
2938                     * @see java.lang.Runnable#run()
2939                     */
2940                    public void run() {
2941                            int previousOffset = -1;
2942                            int previousLength = -1;
2943                            while (run) {
2944                                    try {
2945                                            Thread.sleep(200);
2946                                            if(player != null) {
2947                                                    // get the position and length of video
2948                                                    int currentOffset = player.getPosition();
2949                                                    if(currentOffset == -1) 
2950                                                            currentOffset = 0;
2951                                                    int videoLength = player.getLength();
2952                                                    if(videoLength == -1) 
2953                                                            videoLength = 0;
2954                                                    // update only if it is not the same position and length
2955                                                    if(videoLength != previousLength) {
2956                                                            previousLength = videoLength;
2957                                                            //
2958                                                            // Update the JSlider bar
2959                                                            jSliderIsUpdating = true;
2960                                                            jPanelVideoControler.setJSliderSearchLength(videoLength);
2961                                                            jSliderIsUpdating = false;
2962                                                    }
2963                                                    if(currentOffset != previousOffset) {
2964                                                            previousOffset = currentOffset;
2965                                                            //
2966                                                            // Update the video position label and slider bar.
2967                                                            if(jSliderIsMoving == false) {
2968                                                                    jSliderIsUpdating = true;
2969                                                                    jPanelVideoControler.setJSliderSearchPosition(currentOffset);
2970                                                                    jSliderIsUpdating = false;
2971                                                            }
2972                                                            //
2973                                                            // Update the current subtitle focus
2974                                                            if((subtitleFile != null) && (jPanelVideoControler.jCheckBoxTrackModeIsSelected())) {
2975                                                                    Subtitle activeSubtitle = subtitleFile.getSubtitleAtDate((currentOffset+1)*1000);
2976                                                                    final int row = activeSubtitle.getNumber() - 1;
2977                                                                    if(subtitleFocusIndex !=  row) {
2978                                                                            subtitleFocusIndex = row;
2979                                                                            SwingUtilities.invokeLater(new Runnable(){
2980                                                                                    public void run() {
2981                                                                                            table.changeSelection(row, 0, false, false);
2982                                                                                            table.changeSelection(row+4, 0, true, true);
2983                                                                                    }
2984                                                                            });
2985                                                                    }
2986                                                            }
2987                                                    }
2988                                                    // Update video according to the JSlider
2989                                                    if(jSliderIsMoving == true) {
2990                                                            Date date = new Date();
2991                                                            if(((date.getTime() - updateDate) > 1000) && (oldPosition != currentPosition)) {
2992                                                                    // set the new reference date
2993                                                                    updateDate = date.getTime();
2994                                                                    oldPosition = currentPosition;
2995                                                                    // Set the new position
2996                                                                    try {
2997                                                                            player.setPosition(oldPosition);
2998                                                                    } catch (Exception e) {
2999                                                                            Trace.trace("PlayerTimeHandler failed cannot set video position:"+e.getMessage());
3000                                                                    }
3001                                                            }
3002                                                    }
3003                                            }
3004                                    } catch (Exception e) {
3005                                            Trace.trace("PlayerTimeHandler failed:"+e.getMessage());
3006                                    }
3007                            }
3008                    }
3009    
3010                    /* (non-Javadoc)
3011                     * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
3012                     */
3013                    public void stateChanged(ChangeEvent changeEvent) {
3014                            JSlider source = (JSlider)changeEvent.getSource();
3015                            // get the current slider position
3016                            currentPosition = (int)source.getValue();
3017                            jSliderIsMoving = true;
3018                            // slider stops moving
3019                            if (!source.getValueIsAdjusting()) {
3020                                    jSliderIsMoving = false;
3021                                    if((jSliderIsUpdating == false) && (oldPosition != currentPosition)) {
3022                                            // Set the new position
3023                                            try {
3024                                                    player.setPosition(currentPosition);
3025                                            } catch (Exception e) {
3026                                                    Trace.trace("PlayerTimeHandler failed cannot set video position:"+e.getMessage());
3027                                            }
3028                                    }
3029                            }
3030                            // update the video time
3031                            jPanelVideoControler.setJLabelTime(Utils.formatTime(currentPosition));
3032                    }
3033            }
3034    
3035            public static boolean getResetFlag() {
3036                    return resetFlag;
3037            }
3038    
3039            public void finalize() {
3040                    try {
3041                            super.finalize();
3042                    } catch (Throwable e) {
3043                            // TODO Auto-generated catch block
3044                            e.printStackTrace();
3045                    }
3046            }
3047    }