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.search.core;
021    
022    import java.awt.Component;
023    import java.awt.EventQueue;
024    import java.awt.Point;
025    import java.awt.Rectangle;
026    import java.awt.event.AdjustmentEvent;
027    import java.util.ArrayList;
028    
029    import javax.swing.JScrollPane;
030    import javax.swing.JTable;
031    import javax.swing.JViewport;
032    import javax.swing.SwingUtilities;
033    import javax.swing.table.TableCellRenderer;
034    import javax.swing.table.TableColumn;
035    
036    import sears.file.Subtitle;
037    import sears.gui.MainWindow;
038    import sears.gui.SubtitleCellComponent;
039    import sears.gui.SubtitleTableCellRenderer;
040    import sears.gui.SubtitleTableModel;
041    import sears.gui.glassPane.ViewportGlassPaneModule;
042    import sears.gui.search.FindDialog;
043    import sears.search.data.CharIndexIterator;
044    import sears.search.gui.JTableManipulation;
045    import sears.search.gui.SearchViewportGlassPane;
046    
047    /**
048     * BETA CLASS:
049     * Find action core, extension of <tt>MainWindow</tt> class
050     */
051    public class FindModule extends ViewportGlassPaneModule {
052    
053            private int lastSelectedRow;
054            // table on sears
055            private JTable table;
056    
057            // GLASS PANE:
058            private SearchViewportGlassPane svgp;
059            private PillManager pm;
060    
061            private JTableManipulation jtm;
062    
063            // Char index iterator needed to traverse the rows 
064            // for looking the searched text
065            private CharIndexIterator charIndexIterator;
066            
067            private boolean fireAdjustementValueChangedEnabled = true;
068    
069            /**
070             * Constructs a new <code>FindModule</code> object.
071             * @param scrollPane
072             * @throws NullPointerException if scrollPane is <tt>null</tt>
073             * @throws NullPointerException if its view port is <tt>null</tt>
074             * @throws IllegalArgumentException     if the view of the view port is not a <code>JTable</code> object
075             * 
076             * @see JScrollPane
077             * @see JViewport
078             */
079            public FindModule(JScrollPane scrollPane) {
080                    super(scrollPane);
081                    if( view instanceof JTable ) {
082                            table = (JTable) view;
083                    } else {
084                            throw new IllegalArgumentException("the view is not instance of JTable");
085                    }
086    
087                    pm = new PillManager();
088                    svgp = new SearchViewportGlassPane(viewport, pm);
089                    svgp.addAsAGlassPane(MainWindow.instance);
090                    
091                    jtm = new JTableManipulation(scrollPane);
092    
093                    // default values:
094                    lastSelectedRow = 0;
095                    super.isViewportChanged = false;
096    
097                    charIndexIterator = null;
098            }
099    
100            /**
101             * Tests if the the <code>String</code> object is valid to perform
102             * find action
103             * @param str   the string to test
104             * @return              true if the string is valid, false if not
105             */
106            private boolean isAValidStringForAFindAction(String str) {
107                    return ( str != null && str.trim().length() > 0 );
108            }
109    
110            /**
111             * Principal method
112             * @param dialog                the dialog which calls the find action
113             * @param subtitleList  the subtitle list where search occurs
114             * @param backward              direction of search
115             */
116            public void fire(FindDialog dialog, ArrayList<Subtitle> subtitleList, boolean backward) {
117                    fireAdjustementValueChangedEnabled=false;
118    
119                    if( dialog == null ) {
120                            throw new NullPointerException("Dialog object is null");
121                    }
122                    sears.search.data.SubtitleFile sf = null;
123                    // first we test the string:
124                    String str = dialog.getText();
125                    if( isAValidStringForAFindAction(str) ) {
126                            sf = new sears.search.data.SubtitleFile(subtitleList);
127                            // launch thread process to compute count of occurrences
128                            // and it in dialog
129                            setCountOfOccurrences(dialog, sf, str);
130    
131                            int rowAndChar[] = getRowAndCharIndex(str, subtitleList, backward);
132                            if( rowAndChar != null ) {                              
133                                    int row = rowAndChar[0];
134                                    SubtitleCellComponent cellComponent = getSubtitleCellAtRow(row);
135                                    if( cellComponent != null ) {
136                                            svgp.setVisible(true);
137                                            //
138                                            // highlight sub string:
139                                            highlightSubString(str, table);
140                                            //
141                                            // Scroll to visible the subtitle cell:
142                                            Rectangle cell = table.getCellRect(row, SubtitleTableModel.SUBTITLE_COLUMN, false);                             
143                                            jtm.scrollCellToVisible(row, SubtitleTableModel.SUBTITLE_COLUMN, backward);
144                                            //
145                                            // Display the pill:
146                                            Point location = this.getPillLocation(cell);
147                                            try {
148                                                    pm.firePillDataMustBeUpdated(str, cellComponent, rowAndChar[1], location);
149                                                    svgp.updatePill();
150                                            } catch(IllegalArgumentException e) {
151                                                    // !!!!!!!!!!
152                                                    // DEVELOPPER:
153                                                    // to be implement, "stop" the find action, 
154                                                    // refresh ArrayList, update the table, retry... ?
155                                                    e.printStackTrace();
156                                            }
157                                    } else {
158                                            // there's no valid cell component, abort action
159                                            // there's a problem here...
160                                            cancel();
161                                            sf = null;
162                                    }
163                            } else {
164                                    cancel();
165                                    sf = null;
166                            }
167                    } else {
168                            cancel();
169                            sf = null;
170                    }
171    
172                    if( sf == null ) {
173                            try {
174                                    setCountOfOccurrences(dialog, null, null);
175                            } catch( NullPointerException e) {
176                                    // dialog is null
177                            }
178                    }
179                    
180                    fireAdjustementValueChangedEnabled=true;
181            }
182    
183            /**
184             * Update text of the find dialog
185             * Developer: <tt>NullPointerException</tt> is throw if <tt>dialog</tt> is null
186             * @param dialog                the find dialog
187             * @param subtitleFile  the subtitle file needed to get back the count of occurrences of <tt>str</tt> string
188             * @param str                   the string to search
189             */
190            private void setCountOfOccurrences(FindDialog dialog, sears.search.data.SubtitleFile subtitleFile, String str) {
191                    final FindDialog df = dialog;
192                    if( subtitleFile == null ) {
193                            EventQueue.invokeLater(new Runnable() {
194                                    public void run() {
195                                            df.setOccurencesCount(-1);      
196                                    }
197                            });
198                    } else {
199                            final String text = str;
200                            final sears.search.data.SubtitleFile sf = subtitleFile;
201                            // put process in the request dispatcher
202                            EventQueue.invokeLater(new Runnable() {
203                                    public void run() {
204                                            // launch a thread
205                                            new Thread(new Runnable() {
206                                                    public void run() {
207                                                            df.setOccurencesCount(sf.getCountOfOccurrencesOfText(text));
208                                                    }
209                                            }).start();
210                                            
211                                    }
212                            });
213                    }
214            }
215    
216            /**
217             * Cancel the action
218             */
219            public void cancel() {
220                    highlightSubString(null, table);
221                    svgp.setVisible(false);
222            }
223    
224            /**
225             * Gets the <tt>SubtitleCellComponent</tt> object at the specified row
226             * @param row   the specified row
227             * @return              the <tt>SubtitleCellComponent</tt> object or null if there's no <tt>SubtitleCellComponent</tt> object at this row
228             */
229            private SubtitleCellComponent getSubtitleCellAtRow(int row) {
230                    SubtitleCellComponent cell = null;
231                    Component component = table.prepareRenderer(
232                                    table.getCellRenderer(
233                                                    row, SubtitleTableModel.SUBTITLE_COLUMN), 
234                                                    row, SubtitleTableModel.SUBTITLE_COLUMN);
235                    // if component isn't found the default renderer is return and so a default component is assigned
236                    if( component instanceof SubtitleCellComponent ) {
237                            cell = (SubtitleCellComponent) component;
238                    }
239                    return cell;
240            }
241    
242            /**
243             * @param cell  the cell 
244             * @return              the point in context or null if <tt>cell/tt> is null
245             */
246            public Point getPillLocation(Rectangle cell) {
247                    Point location = null;
248                    if( cell != null ) {                    
249                            cell = SwingUtilities.convertRectangle(viewport.getView(), cell.getBounds(), svgp);
250                            location = cell.getLocation();
251                    }
252                    return location;
253            }
254    
255            /*
256             * (non-Javadoc)
257             * @see sears.gui.glassPane.ViewportGlassPaneModule#fireViewChange()
258             */
259            public void fireViewChange() {
260                    super.fireViewChange();
261                    // reset iterator
262                    charIndexIterator = null;
263            }
264    
265            /**
266             * Initializes variables, depending to the search state.
267             * 
268             * @param str                   the string to find (and search)
269             * @param subtitleList  the subtitle list on which the search operation is perform
270             * @param table                 the <code>JTable</code> object ...
271             * @return                              an array of two int, one it's the row and the other the first char index of the <tt>str</tt> occurrence
272             *                                              <br><tt>null</tt> is returned if there's no occurrence of <tt>str</tt> in subtitle
273             */
274            private int[] getRowAndCharIndex(String str, ArrayList<Subtitle> subtitleList, boolean backward) {
275                    // test find action:            
276                    int selectedRow = table.getSelectedRow();
277                    if( selectedRow == -1 ) {
278                            selectedRow = 0;
279                    }
280                    // to know if the text to looking for has changed
281                    boolean isTextChanged = false;          
282                    if( charIndexIterator == null || super.isViewportChanged ) {
283                            isViewportChanged = false;
284                            charIndexIterator = new CharIndexIterator(subtitleList, str);
285                    } else {
286                            isTextChanged = charIndexIterator.setANewTextForTheSearch(str);
287                    }
288    
289                    int rowAndCharIndex[] = null;
290                    if( lastSelectedRow == selectedRow && !isTextChanged) {
291                            if( !backward ) {
292                                    rowAndCharIndex = charIndexIterator.getNextRowAndCharIndex();
293                            } else {
294                                    rowAndCharIndex = charIndexIterator.getPreviousRowAndCharIndex();
295                            }
296                    } else {
297                            if( !backward ) {
298                                    rowAndCharIndex = charIndexIterator.getNextRowAndCharIndexBeginAtRow(selectedRow);
299                            } else {
300                                    rowAndCharIndex = charIndexIterator.getPreviousRowAndCharIndexBeginAtRow(selectedRow);
301                            }
302                    }
303                    // store the selected row
304                    lastSelectedRow = selectedRow;
305                    return rowAndCharIndex;
306            }
307    
308            /**
309             * Tells <code>SubtitleTableCellRenderer</code> to highlight all accurences of visible string
310             * in <code>JTable</code> instance
311             * @param str           the string to highlight
312             * @param table         the <code>JTable</code> instance
313             */
314            private void highlightSubString(String str, JTable table) {
315                    // fire SubtitleTableCellRenderer to highlight sub string in the table
316                    TableColumn tableColumn = table.getColumnModel().getColumn(SubtitleTableModel.SUBTITLE_COLUMN);
317                    TableCellRenderer renderer = tableColumn.getCellRenderer();
318                    if( renderer instanceof SubtitleTableCellRenderer ) {
319                            ((SubtitleTableCellRenderer) renderer).highlightString(str);
320                    }
321            }
322    
323            /**
324             * Alerts that the word is not found in subtitle
325             */
326            public void fireWordNotFound() {
327                    cancel();
328            }
329            
330            public void fireAdjustmentValueChanged(AdjustmentEvent e) {
331                    if( fireAdjustementValueChangedEnabled == true ) {      
332                            svgp.setVisible(false);
333                    }
334            }
335    }