In order to make SwingTimer
accurate, I like the logic and example suggested by @Tony Docherty
On CR. Here is the Link.
In order to highlight the given words, again and again, there is always a few microsecond delays. If I have words to highlight say: "hello how are" and the values for each word are (delays): 200,300,400 ms respectively, then the actual time taken by the timer is always more. Say instead of 200 ms, it takes 216 ms. Like this, if I have many words..in the end, the extra delay is noticeable.
I have to highlight each letter say: 'h''e''l''l''0' each should get 200/length(i.e 5) = 40 ms approx. Set the delay after each letter.
My logic is, take the current time say startTime
, just before starting the process. Also, calculate the totalDelay
which is totalDelay+=delay/.length().
Now check the condition: (startTime+totalDelay-System.currentTime
)
if this is -ve, that means the time consumption is more, so skip the letter. Check till there is a positive delay.This means I am adding the timings till now, and overcheck it with the difference in the time taken by the process when it got started.
This may result into skipping to highlight the letters.
But something is wrong. What, it’s difficult for me to make out. It's some problem with the looping thing maybe. I have seen it is entering the loop (to check whether the time is -ve ) just twice. But this should not be the case. And I am also not sure about setting up my next delay. Any ideas?
Here is an SSCCE:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class Reminder {
private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
private static final String[] WORDS = TEXT.split(" ");
private JFrame frame;
private Timer timer;
private StyledDocument doc;
private JTextPane textpane;
private int[] times = new int[100];
private long totalDelay=0,startTime=0;
private int stringIndex = 0;
private int index = 0;
public void startColoring() {
times[0]=100;times[9]=200;times[10]=200;times[11]=200;times[12]=200;
times[1]=400;times[2]=300;times[3]=900;times[4]=1000;times[5]=600;times[6]=200;times[7]=700;times[8]=700;
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent)
{
doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);
stringIndex++;
try {
if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")|| doc.getText(stringIndex, 1).equals("\n"))
{
index++;
}
if (index < WORDS.length) {
double delay = times[index];
totalDelay+=delay/WORDS[index].length();
/*Check if there is no -ve delay, and you are running according to the time*/
/*The problem is here I think. It's just entered this twice*/
while(totalDelay+startTime-System.currentTimeMillis()<0)
{
totalDelay+=delay/WORDS[index].length();
stringIndex++;
/*this may result into the end of current word, jump to next word.*/
if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ") || doc.getText(stringIndex, 1).equals("\n"))
{
index += 1;
totalDelay+=delay/WORDS[index].length();
}
}
timer.setDelay((int)(totalDelay+startTime-System.currentTimeMillis()));
}
else {
timer.stop();
System.err.println("Timer stopped");
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
};
startTime=System.currentTimeMillis();
timer = new Timer(times[index], actionListener);
timer.setInitialDelay(0);
timer.start();
}
public void initUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
doc = new DefaultStyledDocument();
textpane = new JTextPane(doc);
textpane.setText(TEXT);
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
panel.add(textpane);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
Reminder reminder = new Reminder();
reminder.initUI();
reminder.startColoring();
}
});
}
}
UPDATE:
For better understanding:
The EG given by @Tony Docherty :
Lets take the word "Test" and say it needs to be highlighted for 1 second, therefore each letter is highlighted for 250ms. Doing things the way you originally, did meant that you set a timer for 250ms for each letter but if each cycle actually took 260ms and lets say the 'e' cycle took 400ms (maybe due to GC or something else using CPU cycles) by the end of the word you would have taken 180ms more than you should have. This error will continue to build for each word until the error is so large highlighting is no longer visually in sync.
The way I am trying, is rather than repeatedly saying this letter needs to be highlighted for x amount of time, calculate the time for each letter relative to the beginning of the sequence ie T = 250, e = 500, s = 750, t = 1000.
So to get the actual time delay you need to add the start time and subtract the current time. To run through the example using the timings I gave above:
StartTime Letter Offset CurrentTime Delay ActualTimeTaken
100000 T 250 100010 240 250
100000 e 500 100260 240 400
100000 s 750 100660 90 100
100000 t 1000 100760 240 250
So you should be able to see now that the timing for each letter is adjusted to take account of any overrun of time from the previous letter. Of course it is possible that a timing overrun is so great that you have to skip highlighting the next letter (or maybe more than 1) but at least I will remaining broadly in sync.
EDITED SSCCE
Update2
In first phase, I take the timings for each word. That is, when the user hits ESC key, the time is stored for a particular word (he does it as the song is played in background.) When the ESC key is pressed, the current word is highlighted and the time spent on the current word is stored in an array. I keep on storing the timings. When the user ends, now I would like to highlight the words as per the set timings. So here, the timing by the user is important. If the timings are fast, so is the highlighting of words or if slow, vice-versa.
New update: progress
The answers below have different logic, but to my surprise, they work more or less the same. A very very weird problem I have found out with all the logic (including mine) is that they seem to work perfectly for few lines, but after that they gain speed, that's also not slowly, but with a huge difference.
Also if you think I should think in a different way, your suggestions are highly appreciated.
sleep()
and than ofcourse the action... Also if you're measuring elapsed time by subtracting two timestamps, you should useSystem.nanoTime()
to get those timestamps as it uses the "most precise available system timer". As I have suggested before use aThread
with while loop,SwingUtilities.invokeXX
andThread.sleep(time-15)
to create your own more accurate timer. – Barghest