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 }