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    //some suggestions about this class: floriaen@gmail.com
019    
020    package sears.gui;
021    
022    import java.awt.BorderLayout;
023    import java.awt.Container;
024    import java.awt.Dimension;
025    import java.awt.Font;
026    import java.awt.GridLayout;
027    import java.awt.event.ActionEvent;
028    import java.awt.event.ActionListener;
029    import java.awt.event.WindowAdapter;
030    import java.awt.event.WindowEvent;
031    import java.nio.charset.Charset;
032    import java.util.Iterator;
033    
034    import javax.swing.BorderFactory;
035    import javax.swing.BoxLayout;
036    import javax.swing.Icon;
037    import javax.swing.JButton;
038    import javax.swing.JComboBox;
039    import javax.swing.JLabel;
040    import javax.swing.JPanel;
041    import javax.swing.JScrollPane;
042    import javax.swing.JTextArea;
043    import javax.swing.SwingConstants;
044    import javax.swing.SwingUtilities;
045    import javax.swing.UIManager;
046    
047    import sears.file.SubtitleFile;
048    import sears.gui.resources.SearsResources;
049    import sears.tools.Utils;
050    
051    /**
052     * <p>
053     * <tt>ErrorDialog</tt> allows to display 3 things:
054     * <ul>
055     * <li>A title to display a general message to alert user</li>
056     * <li>A message error which describes the error</li>
057     * <li>A text area where is printed error's details</li>
058     * <li>A panel with a combo box to let user choose another encoding</li>
059     * </ul>
060     * </p>
061     * <p>
062     * <tt>ErrorDialog</tt> provides static method for access to generics error dialog:
063     * <br><tt># showOpenErrorDialog(String, String, boolean)</tt>
064     * <br><tt># showSaveErrorDialog(String, String)</tt>
065     * 
066     */
067    public class ErrorDialog extends SearsJDialog {
068            private static final long serialVersionUID = -8640938815129486410L;
069    
070            /** this font is used for all the dialog's components */
071            public static final Font DEFAULT_FONT = UIManager.getFont("Label.font");
072            /** derived font of <tt>DEFAULT_FONT</tt> */
073            public static final Font BIG_FONT = DEFAULT_FONT.deriveFont(13f);
074            /** derived font of <tt>DEFAULT_FONT</tt> */
075            public static final Font MIDDLE_FONT = DEFAULT_FONT.deriveFont(12f);
076            /** derived font of <tt>DEFAULT_FONT</tt> */
077            public static final Font SMALL_FONT = DEFAULT_FONT.deriveFont(11f);
078    
079            private static final int MINIMUM_HEIGHT = 180;
080            private static final int MINIMUM_WIDTH = 300;
081    
082            // MESSAGE PANEL
083            private JPanel messagePanel;
084            private JLabel mainTitle;
085            private JLabel errorMessageLabel;
086            private ArrowButton arrowButton;
087    
088            // DETAIL AREA
089            private JPanel detailPanel;
090            private JScrollPane scrollDetailPane;
091            private JTextArea errorDetailArea;
092    
093            // CHARSET PANEL
094            private JPanel charsetPanel;
095            private JTextArea charsetMessageArea;
096            private JComboBox charsetComboBox;
097    
098            // an instance used by the static method
099            private static ErrorDialog openErrorDialog;
100    
101            /**
102             * Constructs a new instance of <tt>ErrorDialog</tt>
103             * <br>A preferred size is set as default
104             */
105            public ErrorDialog() {
106                    super("Error");
107                    //this.setPreferredSize(new Dimension(200, 300));
108            }
109    
110            /**
111             * Redefines super method, add components to the dialog before
112             * compute the it size :D
113             */
114            protected void configureSize() {                
115                    // DEVELOPER:
116                    // the super method #configureSize() calls the pack() method if there's no saved sizes.
117                    // The problem resides in the fact that there's not all the components added when this method 
118                    // is called, so pack() method result is not good for the view...
119                    //
120                    //this.setMinimumSize(new Dimension(372,332));
121                    initComponents();
122                    setSize(MINIMUM_WIDTH, MINIMUM_HEIGHT);
123                    setLocationRelativeTo(MainWindow.instance);
124                    //super.configureSize();
125            }
126    
127            /*
128             * (non-Javadoc)
129             * @see java.awt.Component#setVisible(boolean)
130             */
131            public void setVisible(boolean aFlag) {
132                    if( aFlag == true ) {
133                            setSize(MINIMUM_WIDTH, MINIMUM_HEIGHT);
134                    }
135                    super.setVisible(aFlag);
136            }
137    
138            /**
139             * Initializes all the dialog's components
140             */
141            private void initComponents() {
142                    Container contentPane = getContentPane();
143                    contentPane.setLayout(new BorderLayout());
144                    contentPane.add(getMessagePanel(), BorderLayout.NORTH);
145                    contentPane.add(getDetailPanel(), BorderLayout.CENTER);
146                    contentPane.add(getCharsetPanel(), BorderLayout.SOUTH);
147            }
148    
149            /**
150             * Constructs if needed and returns the message panel.
151             * <br> This panel contains two <tt>JLabel</tt> object:
152             * <ul>
153             * <li>one to describe to the user what happens</li>
154             * <li>the other to display which error occurs</li>
155             * </ul>
156             * @return the message panel
157             */
158            private JPanel getMessagePanel() {
159                    if( messagePanel == null ) {
160                            messagePanel = new JPanel();
161                            messagePanel.setLayout(new GridLayout(2,0));
162                            messagePanel.setBorder(BorderFactory.createEmptyBorder(10, 4, 6, 4));
163                            // ERROR MESSAGE
164                            errorMessageLabel = new JLabel();
165                            errorMessageLabel.setFont(MIDDLE_FONT);
166                            errorMessageLabel.setHorizontalTextPosition(SwingConstants.LEFT);
167                            // MAIN TITLE
168                            mainTitle = new JLabel();
169                            Icon errorIcon = SearsResources.getIcon("ErrorIcon");
170                            mainTitle.setIconTextGap(5);
171                            mainTitle.setIcon(errorIcon);
172                            mainTitle.setFont(BIG_FONT);
173                            // ADD LABELS TO PANEL
174                            messagePanel.add(mainTitle);
175                            //messagePanel.add(errorMessageLabel);
176                            messagePanel.add(getArrowButton());
177                    }
178                    return messagePanel;
179            }
180    
181            /**
182             * Constructs if needed and returns the "detail panel"
183             * <br>It is composed by an area where is displayed text
184             * @return      the panel
185             */
186            private JPanel getDetailPanel() {
187                    if( detailPanel == null ) {
188                            detailPanel = new JPanel();
189                            detailPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
190                            scrollDetailPane = new JScrollPane(getErrorDetailArea());
191                            scrollDetailPane.setPreferredSize(new Dimension(200,100));
192                            detailPanel.setLayout(new GridLayout(1,0));
193                            detailPanel.add(scrollDetailPane);
194                    }
195                    return detailPanel;
196            }
197    
198            // get the arrow button
199            private JButton getArrowButton() {
200                    arrowButton = new ArrowButton("details");
201                    arrowButton.setHorizontalAlignment(SwingConstants.LEFT);
202                    arrowButton.setFont(MIDDLE_FONT);
203                    arrowButton.setToolTipText("click to show or hide error details");
204                    arrowButton.addActionListener(new ActionListener() {
205                            public void actionPerformed(ActionEvent e) {
206                                    setDetailPanelVisible(!arrowButton.isTurnOn());
207                            }                       
208                    });
209                    return arrowButton;
210            }
211    
212            /**
213             * Used by the arrow button to show or hide the detail panel
214             * @param visible true for show, false for hide
215             */
216            protected void setDetailPanelVisible(boolean visible) {
217                    if( visible && !detailPanel.isVisible() ) {
218                            detailPanel.setVisible(true);
219                            int detailPanelHeigth = detailPanel.getHeight();
220                            if( detailPanelHeigth == 0 ) {
221                                    detailPanelHeigth = 100;
222                            }               
223                            this.setSize(this.getWidth(), this.getHeight() + detailPanelHeigth);
224                            this.repaint();
225                    } else if( !visible && detailPanel.isVisible() ) {
226                            detailPanel.setVisible(false);
227                            this.setSize(this.getWidth(), this.getHeight() - detailPanel.getHeight());
228                            this.repaint();
229                    }
230            }
231    
232            /**
233             * Constructs if needed and returns a non-editable <tt>JTextArea</tt> object.
234             * <br>Use for display error messages
235             * @return      the error message area
236             */
237            private JTextArea getErrorDetailArea() {
238                    if( errorDetailArea == null ) {
239                            errorDetailArea = new JTextArea();
240                            errorDetailArea.setEditable(false);
241                            errorDetailArea.setBorder(BorderFactory.createEmptyBorder(2,4,2,4));
242                            errorDetailArea.setFont(SMALL_FONT);
243                    }
244                    return errorDetailArea;
245            }
246    
247            /**
248             * Constructs if needed and returns the "charset panel" composed by labels and a combo box
249             * <br>to give tu user the possibility to choose another charset encoding/decoding
250             * @return      the panel
251             */
252            private JPanel getCharsetPanel() {
253                    if( charsetPanel == null ) {
254                            charsetPanel = new JPanel();
255                            charsetPanel.setBorder(BorderFactory.createEmptyBorder(6, 4, 6, 18));
256    
257                            charsetMessageArea = new JTextArea();
258                            charsetMessageArea.setText(
259                                            "A wrong charset might be the error origin."    
260                                            + Utils.LINE_SEPARATOR
261                                            + "You can try to reopen the file with the"
262                                            + Utils.LINE_SEPARATOR
263                                            + "charset of your choice:");
264    
265                            charsetMessageArea.setFont(SMALL_FONT);
266                            charsetMessageArea.setEditable(false);
267                            charsetMessageArea.setBorder(BorderFactory.createEmptyBorder(2,4,2,4));
268                            charsetMessageArea.setBackground(charsetPanel.getBackground());
269    
270                            charsetComboBox = new JComboBox();
271                            charsetComboBox.setFont(MIDDLE_FONT);
272                            fillCharsetComboBox();          
273                            charsetComboBox.addActionListener(new ActionListener() {
274                                    public void actionPerformed(ActionEvent e) {
275                                            String selectedCharset = (String) charsetComboBox.getSelectedItem();
276                                            charset = selectedCharset;
277                                            // closed and dispose window dialog,
278                                            // call to super method from SearsJDialog
279                                            okAction();
280                                    }                       
281                            });
282    
283                            charsetMessageArea.setAlignmentX(LEFT_ALIGNMENT);
284                            charsetComboBox.setAlignmentX(LEFT_ALIGNMENT);
285    
286                            // adding components:
287                            charsetPanel.setLayout(new BoxLayout(charsetPanel, BoxLayout.Y_AXIS));
288                            charsetPanel.add(charsetMessageArea);
289                            charsetPanel.add(charsetComboBox);
290                    }
291                    return charsetPanel;
292            }
293    
294            // NullPointerException if charsetComboBox is null
295            private void fillCharsetComboBox() {
296                    String[] searsCharset = SubtitleFile.BASIC_CHARSETS;
297                    Iterator<Charset> availableCharsetsIt = Charset.availableCharsets().values().iterator();
298                    while( availableCharsetsIt.hasNext() ) {
299                            String charset = availableCharsetsIt.next().displayName();
300                            //charsetComboBox.addItem(charset);
301                            for( int i=0;i<searsCharset.length;i++ ) {
302                                    if( charset.equals(searsCharset[i])) {
303                                            charset = null;
304                                            // exit the loop for:
305                                            i = searsCharset.length;
306                                    }
307                            }
308                            if( charset != null ) {
309                                    charsetComboBox.addItem(charset);
310                            }
311                    }               
312            }
313    
314            /**
315             * Indicates if the "charset panel" must be visible or not
316             * @param aFlag if true, "charset panel" will be visible.
317             */
318            public void setCharsetSelectionVisible(boolean aFlag) {
319                    charsetPanel.setVisible(aFlag);         
320            }
321    
322            /**
323             * Sets the main title 
324             * @param title the main title to set
325             */
326            public void setMainTitle(String title) {
327                    mainTitle.setText(title);
328            }
329    
330            /**
331             * Sets a message error
332             * @param message       the message error to set
333             */
334            public void setErrorMessage(String message) {
335                    //errorMessageLabel.setText("> " + message);
336                    arrowButton.setText(message);
337            }
338    
339            /**
340             * Sets a detail error
341             * @param detail        the detail error to set
342             */
343            public void setErrorDetail(String detail) {
344                    // use line separator for a good display of the JScollBar
345                    errorDetailArea.setText(detail + Utils.LINE_SEPARATOR);
346            }
347    
348            /**
349             * Appends text to the actual message error
350             * @param detail        the text to append
351             */
352            public void appendErrorDetail(String detail) {
353                    String textArea = errorDetailArea.getText();
354                    errorDetailArea.setText(textArea + detail);
355            }
356    
357            /**
358             * Clears the error detail
359             */
360            public void clearErrorDetail() {
361                    errorDetailArea.setText(null);
362            }
363    
364            /*
365             * (non-Javadoc)
366             * @see sears.gui.SearsJDialog#getDialogName()
367             */
368            protected String getDialogName() {
369                    return "error";
370            }
371    
372            public void windowClosed(WindowEvent e) {
373                    //super.windowClosed(e);
374                    arrowButton.turnOff();
375            }
376    
377            //
378            // STATIC FACTORY METHODS:
379            //
380    
381            // used to return result of user action on error dialog
382            private static String charset = SubtitleFile.DEFAULT_CHARSET;
383    
384            /**
385             * Shows a generic error dialog when file is opened
386             * @param message               the message
387             * @param detail                the error detail
388             * @param reOpenChoice  if there's a way to resolve the error with a different charset
389             * @return                              the charset choosed by the user or null if not
390             */
391            public static String showOpenErrorDialog(String message, String detail, boolean reOpenChoice) {
392                    if( openErrorDialog == null ) {
393                            openErrorDialog = new ErrorDialog();
394                    }
395                    openErrorDialog.setMainTitle("Sears could'nt open file");
396                    openErrorDialog.setErrorMessage(message);
397                    openErrorDialog.setErrorDetail(detail);
398                    openErrorDialog.setCharsetSelectionVisible(reOpenChoice);
399                    openErrorDialog.setDetailPanelVisible(false);
400    
401                    if( SwingUtilities.isEventDispatchThread() ) {
402                            openErrorDialog.addWindowListener(new WindowAdapter() {
403                                    public void windowClosing(WindowEvent e) {
404                                            charset = null;
405                                    }
406                            });
407                            openErrorDialog.setVisible(true);
408                    } else {
409                            try {
410                                    SwingUtilities.invokeAndWait(new Runnable() {
411                                            public void run() {
412                                                    openErrorDialog.addWindowListener(new WindowAdapter() {
413                                                            public void windowClosing(WindowEvent e) {
414                                                                    charset = null;
415                                                            }
416                                                    });
417                                                    openErrorDialog.setVisible(true);
418                                            }
419                                    });
420                            } catch (java.lang.reflect.InvocationTargetException e) {
421                                    e.printStackTrace();
422                            } catch (java.lang.InterruptedException e) {
423                                    e.printStackTrace();
424                            }
425                    }
426                    return charset; 
427            }
428    
429            /**
430             * Shows a generic error dialog when file is saved
431             * @param message               the message
432             * @param detail                the error detail
433             */
434            public static void showSaveErrorDialog(String message, String detail) {
435                    if( openErrorDialog == null ) {
436                            openErrorDialog = new ErrorDialog();
437                    }
438    
439                    openErrorDialog.setMainTitle("Sears could'nt save the file");
440                    openErrorDialog.setErrorMessage(message);
441                    openErrorDialog.setErrorDetail(detail);
442                    openErrorDialog.setCharsetSelectionVisible(false);
443                    openErrorDialog.setDetailPanelVisible(false);
444    
445                    if( SwingUtilities.isEventDispatchThread() ) {
446                            openErrorDialog.setVisible(true);
447                    } else {
448                            try {
449                                    SwingUtilities.invokeAndWait(new Runnable() {
450                                            public void run() {
451                                                    openErrorDialog.setVisible(true);
452                                            }
453                                    });
454                            } catch (java.lang.reflect.InvocationTargetException e) {
455                                    e.printStackTrace();
456                            } catch (java.lang.InterruptedException e) {
457                                    e.printStackTrace();
458                            }
459                    }
460            }
461    }