001 /** 002 * iFish - An iRiver iHP jukebox database creation tool 003 * 004 * Copyright (C) 2009 Richard "Shred" Körber 005 * http://ifish.shredzone.org 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published by 009 * the Free Software Foundation, either version 3 of the License, or 010 * (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 019 */ 020 package net.shredzone.ifish.gui; 021 022 import net.shredzone.ifish.db.*; 023 import javax.swing.*; 024 import java.io.File; 025 026 /** 027 * This is an extension of the NaviDb class to make it usable in JTables. 028 * 029 * @author Richard Körber <dev@shredzone.de> 030 * @version $Id: StatusProgressBar.java 291 2009-04-28 21:29:27Z shred $ 031 */ 032 public final class StatusProgressBar extends JProgressBar implements StatusCallback, Runnable { 033 private static final long serialVersionUID = 3258416123057027379L; 034 035 private int max; 036 private int index; 037 private String action; 038 private long starttime; 039 private Boolean changed = Boolean.FALSE; 040 private boolean countdown = false; 041 042 /** 043 * Create a new StatusProgressBar. 044 */ 045 public StatusProgressBar() { 046 super(); 047 setString( " " ); 048 setStringPainted( true ); 049 Thread th = new Thread( this, "StatusProgressBar" ); 050 th.setDaemon( true ); 051 th.start(); 052 } 053 054 /** 055 * Set the current action that is being done. 056 * 057 * @param action Action string 058 */ 059 public void setAction( String action ) { 060 if( action==null || action.equals("") ) 061 this.action = " "; 062 else 063 this.action = action; 064 } 065 066 /** 067 * Show that the process is finished. This will also disable the 068 * countdown mode automatically. 069 */ 070 public void done() { 071 synchronized( changed ) { 072 changed = Boolean.FALSE; 073 } 074 setCountdown( false ); 075 setMinimum( 0 ); 076 setMaximum( 1 ); 077 setValue( 0 ); 078 setString( " " ); 079 setStringPainted( true ); 080 setIndeterminate( false ); 081 } 082 083 /** 084 * Enable the countdown. The StatusProgressBar will now try to estimate 085 * the time until the index reaches the maximum. 086 * <p> 087 * If countdown is enabled, you must make sure 088 * to call <code>setMaxEntries()</code> only once at the beginning, 089 * and you must ensure that <code>setCurrentIndex()</code> is always 090 * only incrementing! 091 * 092 * @param cntdown true: activate countdown, false: disactivate 093 */ 094 public void setCountdown( boolean cntdown ) { 095 starttime = System.currentTimeMillis(); 096 this.countdown = cntdown; 097 } 098 099 100 /** 101 * Check if countdown is enabled. 102 * 103 * @return true: countdown activated, false: disactivated 104 */ 105 public boolean isCountdown() { 106 return countdown; 107 } 108 109 /** 110 * Set the maximum number of entries to be processed by the current 111 * action. If max is negative, then the maximum number is not known. 112 * Anyhow, during the operation setMaxEntries could be invoked multiple 113 * times. 114 * 115 * @param max Maximum number of entries. 116 */ 117 @Override 118 public void setMaxEntries( int max ) { 119 synchronized( changed ) { 120 this.max = max; 121 starttime = System.currentTimeMillis(); 122 changed = Boolean.TRUE; 123 } 124 } 125 126 /** 127 * Set the current entry number that is being processed by the current 128 * action. If the number is negative, then the current entry number is 129 * not known. 130 * 131 * @param index Current entry. 132 */ 133 @Override 134 public void setCurrentIndex( int index ) { 135 synchronized( changed ) { 136 this.index = index; 137 changed = Boolean.TRUE; 138 } 139 } 140 141 /** 142 * Set the current entry that is being processed by the current 143 * action. If null is passed, then no Entry is currently processed, 144 * or there are no Entries being processed. 145 * 146 * @param entry Entry currently being processed, or null. 147 */ 148 @Override 149 public void setCurrentEntry( Entry entry ) { 150 /*TODO: nothing for now... We could show filenames later. */ 151 } 152 153 /** 154 * Set the current directory being processed. If there are no 155 * directories to be processed, dir will be null. Note that this 156 * method might be invoked several times, even without any changes. 157 * 158 * @param base Base directory 159 * @param dir Current directory 160 */ 161 @Override 162 public void setCurrentDir( File base, File dir ) { 163 /*TODO: nothing for now... We could show filenames later. */ 164 } 165 166 /** 167 * Runnable implementation. All StatusCallback data is cached. The 168 * progress bar will only be updated every 500ms, to gain some 169 * performance. In this thread, all the text concatenations and 170 * countdown calculations are done as well, so the CPU does not 171 * need to bother too much with this time consuming calculations. 172 */ 173 @Override 174 public void run() { 175 try { 176 while(true) { 177 Thread.sleep( 500 ); 178 synchronized( changed ) { 179 if( changed.booleanValue() ) { 180 if( index>=0 ) { 181 StringBuffer text = new StringBuffer( action ); 182 183 //--- Percent --- 184 if( max>0 && index>=0 ) { 185 text.append( ": " ); 186 if( index<=max ) { 187 text.append( index*100 / max ); 188 }else { 189 text.append("100"); 190 } 191 text.append( '%' ); 192 } 193 194 //--- ETA --- 195 if( countdown ) { 196 int eta = estimateTime(); 197 if( eta>=0 ) { 198 text.append( " - " ); 199 text.append( eta/60 ); 200 text.append( ':' ); 201 if( eta%60<10 ) 202 text.append( '0' ); 203 text.append( eta%60 ); 204 } 205 } 206 207 //--- Set the progress bar --- 208 setMinimum( 0 ); 209 setMaximum( max ); 210 setValue( index ); 211 setString( text.toString() ); 212 setIndeterminate( false ); 213 }else { 214 //--- Set to indeterminate state --- 215 setString( action ); 216 setMinimum( 0 ); 217 setMaximum( 1 ); 218 setValue( 0 ); 219 setIndeterminate( true ); 220 } 221 changed = Boolean.FALSE; 222 } 223 } 224 } 225 }catch( Exception e ) {} 226 } 227 228 /** 229 * When countdown is enabled, this method will try to estimate the time 230 * until index reaches maximum. The estimated time in seconds will be 231 * returned. If an estimation is not possible yet, -1 will be returned. 232 * 233 * @return Estimated Time of Arrival 234 */ 235 public int estimateTime() { 236 int cMax = max; 237 int cIndex = index; 238 239 //--- Check prerequisites --- 240 if( !countdown || cMax<=0 || cIndex<=0 ) return -1; 241 242 //--- Compute elapsed time --- 243 long current = System.currentTimeMillis(); 244 long elapsed = current - starttime; 245 246 // Wait at least 3 seconds before estimating. Before that period, 247 // the guesswork is rather utopian than reasonable. 248 if( elapsed<3000 ) return -1; 249 250 //--- Compute ETA and required time --- 251 // The ETA is extrapolated from the elapsed time and the index 252 // in relation to the given maximum. 253 long eta = ( (elapsed * cMax) / cIndex ) + starttime; 254 long required = (eta - current) / 1000; // floored, in seconds 255 256 //--- Out of range? --- 257 if( required<0 || required>Integer.MAX_VALUE ) return -1; 258 259 //--- Return the time --- 260 return (int) required; 261 } 262 263 }