Digital clock acts strange when running it. (Java) - java

So I have made a program where I made so a clock appears when running the program. so. When I press a button "Start" a clock should go random inside a bounds and go as a clock aswell (I mean like digital, 15:18:19, 15:18:20). but my problem is now whenever I press the program. the clock stays on the same spot and when changing the sleep time to etc. 500. then it starts to move but the time semms to go alot faster (since it should sleep 1000). however I don't see the problem in the code where it should go. but however maybe some of you could help me with that.
public void startClock() {
Thread t2 = new Thread() {
public void run() {
if (clocking) {
Random rand = new Random();
while(clocking){
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
movingClock.setText(hour + ":" + minute + ":" + second);
int x = rand.nextInt(100) + 1;
int y = rand.nextInt(100) + 1;
movingClock.setBounds(x, y, 150, 150);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t2.start();
}
where clocking is = true. because whenever I press the button. I turn it the clocking to true and then run the startClock().
but as I said before. the digital clock only goes randomly if I change the sleep time below 500. and how can I make it work if it's sleep(1000)?
EDIT NEW ONE:
public void startMoving() {
Thread t1 = new Thread() {
public void run() {
if (moving) {
Random random = new Random();
while (moving) {
int x = random.nextInt(100) + 1;
int y = random.nextInt(100) + 1;
movingDisplay.setBounds(x, y, 150, 150);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t1.start();
}
public void startClock() {
Thread t2 = new Thread() {
public void run() {
if (clocking) {
Random rand = new Random();
while(clocking){
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
movingClock.setText(hour + ":" + minute + ":" + second);
int x = rand.nextInt(100) + 1;
int y = rand.nextInt(100) + 1;
movingClock.setBounds(x, y, 150, 150);
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
movingClock.setText(hour + ":" + minute + ":" + second);
movingClock.setBounds(x, y, 150, 150);
movingDisplay.setBounds(x, y, 150, 150);
}
} );
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t2.start();
}

I guess you are trying to move your clock on some Swing component like JPanel. You should do all the modifications to the components on an Event Dispatch Thread
Your code should look something like this:
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
movingClock.setText(hour + ":" + minute + ":" + second);
movingClock.setBounds(x, y, 150, 150);
}
} );
Look at:
Java Event-Dispatching Thread explanation

Related

Problem syncing custom timer to system clock

I have a custom timer that I'm keeping synced to the system clock. It's working fine for one minute intervals, but I also need it to sync to 36 second intervals (for updating fields when the user wants times displayed in decimal hours). Inside the Runnable I recalculate the adjustment (accurate enough for my needs) for the next minute, but can't find a way to conveniently deal with the different offsets needed for 36 seconds.
EDIT
private static class DecimalTimer extends TimerClass {
final int[] seconds = new int[101];
private boolean keepRunning = true;
private Runnable mRunnable;
#Override
void kill() {
keepRunning = false;
}
#Override
Runnable getRunnable() {
return mRunnable;
}
DecimalTimer() {
final int[] counter = new int[1];
for (int i = 0, j = 0; i < 3600; i += 36, j++) {
seconds[j] = i;
}
mRunnable = new Runnable() {
#Override
public void run() {
if (!keepRunning)
return;
final Calendar c = Calendar.getInstance();
int hourSeconds = c.get(Calendar.MINUTE) * 60 + c.get(Calendar.SECOND);
for (counter[0] = 0; counter[0] < 100; counter[0]++) {
if (seconds[counter[0]] > hourSeconds) {
c.add(Calendar.SECOND, seconds[counter[0]]);
break;
}
}
long adjustedDelay = (c.getTimeInMillis() - System.currentTimeMillis()) % 36000;
for (IntervalTimer listener : sListeners) {
listener.updateTime();
}
sHandler.postDelayed(this, adjustedDelay);
}
};
}
}
This edit fires every 36 seconds, but I need to get it to sync to 36 seconds on the clock (ie: 12:00:00, then 12:00:36 .. 12:01:12 .. 12:01:48)
Overthinking things as usual, this is what I came up with. It's accurate enough for what I need, but if anyone has an improvement for accuracy (It's a little off when it executes with 900ms or more) it'd be appreciated.
DecimalTimer() {
mRunnable = new Runnable() {
#Override
public void run() {
if (!keepRunning)
return;
final Calendar c = Calendar.getInstance();
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
do {
c.add(Calendar.SECOND, 36);
} while (c.getTimeInMillis() < System.currentTimeMillis());
long adjustedDelay = (c.getTimeInMillis() - System.currentTimeMillis()) % 36000;
for (IntervalTimer listener : sListeners) {
listener.updateTime();
}
sHandler.postDelayed(this, adjustedDelay);
}
};
}

Print current date every 5 sec with in a 30 sec loop

The do while loop will execute for a 30 sec duration.With in that I have to print the current date in every 5 sec... For that I have written a code as below. But it is not working as expected...
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
long duration = (30 * 1000);
do {
while (true) {
try {
System.out.println(" Date: " + new Date());
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} while ((System.currentTimeMillis() - startTime) < duration);
}
Other answers demonstrated doing this using while loop and Timer; here is how you can do it using ScheduledExecutorService:
private final static int PERIOD = 5;
private final static int TOTAL = 30;
...
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
System.out.println(new LocalDate());
}, PERIOD, PERIOD, TimeUnit.SECONDS);
executor.schedule(executor::shutdownNow, TOTAL, TimeUnit.SECONDS);
Infinite loop while(true) is causing the trouble for you.
You do not need a do-while loop for this, unless it is a specific requirement.
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
long duration = (30 * 1000);
while ((System.currentTimeMillis() - startTime) < duration) {
System.out.println(" Date: " + new Date());
Thread.sleep(5000);
}
}
For do-while loop, you can just refactor as below:
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
long duration = (30 * 1000);
do {
System.out.println(" Date: " + new Date());
Thread.sleep(5000);
} while ((System.currentTimeMillis() - startTime) < duration);
}
I would use a java.util.Timer; create an anonymous TimerTask to display the Date 6 times on a five second period and then cancel() itself. That could look something like
java.util.Timer t = new java.util.Timer();
java.util.TimerTask task = new java.util.TimerTask() {
private int count = 0;
#Override
public void run() {
if (count < 6) {
System.out.println(new Date());
} else {
t.cancel();
}
count++;
}
};
t.schedule(task, 0, TimeUnit.SECONDS.toMillis(5));

updating JLabel with multithreading every second

i try to make thread count down and i want to update this count in my Jlabel but it always show empty label this is my code
public static String all;
private static int sec = 59, hour = 7, min = 59;
#Override
public void run() {
while (sec > 0) {
System.out.format("%02d:" + "%02d:" + "%02d\n", hour, min, sec);
try {
sec--;
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
if (sec == 0) {
if (min == 0) {
if (hour == 0) {
break;
}
hour--;
min = 59;
}
min--;
sec = 59;
}
StringBuilder builder = new StringBuilder();
builder.append(String.valueOf(hour))
.append(":")
.append(String.valueOf(min))
.append(":")
.append(String.valueOf(sec));
all = builder.toString();
tesmy.myNum(all); //this method send the for my gui the time every second
}
}
}
i sure that he send time correct but i dont know how to update my label every second
i play this class in thread
this is my Jlabel
JLabel jLabel = new JLabel();
jLabel.setBounds(175, 170, 70, 20);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
jLabel.setText(myString);
}
});
add(jLabel);
Why do you want to use Thread?
Java provides Swing Timer to update text of a label every second.
See this tutorial here
Hope this helps :)

Java Thread - Make a clock and own clock

So I'm starting to get really interested about Threads and how it works, Kinda amazing how it works but I just learned about it today and trying to make a program to work. So im trying to make a clock that gives me in a Jlabel a current clock which is right now and a Textfield where I enter the time by myself. so what I want to do is it should start by showing me the time, when pressing the button "Set time" it should change the time as I entered and go from there as a clock.
So my problem right now is that whenever I press the Set time now it changes for a second and then turn back to the current clock again. and I don't really know how to stop the first thread when pressing Set time (Which starts the second Thread)
However I think this is kinda simple but fun to work with,
EDIT: I Also found out that I will have a problem by counting when entering a own "clock". My code:
public Clock() {
initialize();
Thread1();
}
.......
JButton btnSetTime = new JButton("Set time");
btnSetTime.setBounds(474, 262, 89, 23);
frame.getContentPane().add(btnSetTime);
btnSetTime.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(e.getSource() == btnSetTime) {
Thread2();
}
}
});
}
public void Thread2() {
Thread t2 = new Thread() {
public void run() {
try {
for(;;) {
int hour = Integer.parseInt(tfhour.getText());
int minute = Integer.parseInt(tfminute.getText());
int second = Integer.parseInt(tfsecond.getText());
lblKlockan.setText(hour + ":" + minute + ":" + second);
sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};t2.start();
}
public void Thread1() {
Thread t1 = new Thread() {
public void run() {
try {
for(;;) {
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
lblKlockan.setText(hour + ":" + minute + ":" + second);
sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};t1.start();
}
}
So as I can see, the int hour = Integer.parseInt(tfhour.getText()); will probably not count as a Clock if I change to my own clock later on if i'm right? In that case, How can I fix it?
I hope I have all my problems out here in the post and I hope anyone here is willing to help me aswell :)
EDIT:
To make it easier to see:
EDIT PART 3:0
You have to define a global variable to stop the thread 1, when the thread 2 starts. Here is an example,
//define a variable that controls the thread 1
static boolean clockSet = false;
Now thread 1 is written such as it runs only when the clockSet is false, that is clock not set by a click.
Thread t1 = new Thread(){
public void run(){
while(!clockSet){
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
lblKlockan.setText(hour + ":" + minute + ":" + second);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
In your thread 2, set the clockSet to true, so the thread 1 will stop. I see you are just displaying the value entered, but not setting those on a Calendar object. So create a Calendar object in thread 2, set the user defined values, and increment the time after every second of sleep.
Thread t2 = new Thread(){
public void run(){
clockSet = true;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(tfhour.getText()));
cal.set(Calendar.MINUTE, Integer.parseInt(tfminute.getText()));
cal.set(Calendar.SECOND, Integer.parseInt(tfsecond.getText()));
while(true){
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
lblKlockan.setText(hour + ":" + minute + ":" + second);
try {
sleep(1000);
cal.add(Calendar.SECOND, 1);
} catch (InterruptedException e) {
e.printStackTrace();}
}
}
};
Have fun..!
Sure, you see the code
try {
for(;;) {
int hour = Integer.parseInt(tfhour.getText());//this one
int minute = Integer.parseInt(tfminute.getText());//and this one
int second = Integer.parseInt(tfsecond.getText());//and this too
lblKlockan.setText(hour + ":" + minute + ":" + second);
sleep(1000);
}
you are setting hour, minute & second each and every time inside the for loop. So the clock is reseting.
Make them to get initialize only once, i.e. take them out of the loop.
Further this code
for(;;) {
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
lblKlockan.setText(hour + ":" + minute + ":" + second);
sleep(1000);
}
is making your timer work as clock and not a timer because after 1 sec. the hour, minute & second are set to the values according to the time you get from Calendar.
Due to this your code will work as a clock that show the current time.
In my opinion you don't need the second thread at all. What I would do in this case is create three Int fields, hour-, minute- and second-offset, which you change when the button is pressed to, for example, hourOffset = Calendar.HOUR_OF_DAY - Integer.parseInt(tfhour.getText())%24; (example for the hours). Than you just need to add the offset to the time in thread1, which allways works since you start with 0 as offset.

Issues: Creating a very accurate Swing Timer

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.
I think that to do something like this, you need a Swing Timer that ticks at a constant rate, say 15 msec, as long as it's fast enough to allow the time granularity you require, and then trip the desired behavior inside the timer when the elapsed time is that which you require.
In other words, don't change the Timer's delay at all, but just change the required elapse times according to your need.
You should not have a while (true) loop on the EDT. Let the "while loop" be the Swing Timer itself.
To make your logic more fool proof, you need to check if elapsed time is >= needed time.
Again, don't set the Timer's delay. In other words, don't use it as a timer but rather as a poller. Have it beat every xx msec constantly polling the elapsed time, and then reacting if the elapsed time is >= to your need.
The code I'm suggesting would look something like so:
public void actionPerformed(ActionEvent actionEvent) {
if (index > WORDS.length || stringIndex >= doc.getLength()) {
((Timer)actionEvent.getSource()).stop();
}
currentElapsedTime = calcCurrentElapsedTime();
if (currentElapsedTime >= elapsedTimeForNextChar) {
setNextCharAttrib(stringIndex);
stringIndex++;
if (atNextWord(stringIndex)) {
stringIndex++; // skip whitespace
deltaTimeForEachChar = calcNextCharDeltaForNextWord();
} else {
elapsedTimeForNextChar += deltaTimeForEachChar;
}
}
// else -- we haven't reached the next time to change char attribute yet.
// keep polling.
}
For example, my SSCCE:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.List;
import javax.swing.*;
import javax.swing.text.*;
public class Reminder3 {
private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
private static final String[] WORDS = TEXT.split(" ");
private static final int[] TIMES = { 100, 400, 300, 900, 1000, 600, 200,
700, 700, 200, 200, 200, 200 };
private static final int POLLING_TIME = 12;
private StyledDocument doc;
private JTextPane textpane;
private JPanel mainPanel = new JPanel();
private List<ReminderWord> reminderWordList = new LinkedList<ReminderWord>();
private Timer timer;
// private int stringIndex = 0;
public Reminder3() {
doc = new DefaultStyledDocument();
textpane = new JTextPane(doc);
textpane.setText(TEXT);
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
JPanel textPanePanel = new JPanel();
textPanePanel.add(new JScrollPane(textpane));
JButton startBtn = new JButton(new AbstractAction("Start") {
#Override
public void actionPerformed(ActionEvent arg0) {
goThroughWords();
}
});
JPanel btnPanel = new JPanel();
btnPanel.add(startBtn);
mainPanel.setLayout(new BorderLayout());
mainPanel.add(textPanePanel, BorderLayout.CENTER);
mainPanel.add(btnPanel, BorderLayout.SOUTH);
}
public void goThroughWords() {
if (timer != null && timer.isRunning()) {
return;
}
doc = new DefaultStyledDocument();
textpane.setDocument(doc);
textpane.setText(TEXT);
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
int wordStartTime = 0;
for (int i = 0; i < WORDS.length; i++) {
if (i > 0) {
wordStartTime += TIMES[i - 1];
}
int startIndexPosition = 0; // set this later
ReminderWord reminderWord = new ReminderWord(WORDS[i], TIMES[i],
wordStartTime, startIndexPosition);
reminderWordList.add(reminderWord);
}
int findWordIndex = 0;
for (ReminderWord word : reminderWordList) {
findWordIndex = TEXT.indexOf(word.getWord(), findWordIndex);
word.setStartIndexPosition(findWordIndex);
findWordIndex += word.getWord().length();
}
timer = new Timer(POLLING_TIME, new TimerListener());
timer.start();
}
public JComponent getMainPanel() {
return mainPanel;
}
private void setNextCharAttrib(int textIndex) {
doc.setCharacterAttributes(textIndex, 1,
textpane.getStyle("Red"), true);
}
private class TimerListener implements ActionListener {
private ReminderWord currentWord = null;
private long startTime = System.currentTimeMillis();
#Override
public void actionPerformed(ActionEvent e) {
if (reminderWordList == null) {
((Timer) e.getSource()).stop();
return;
}
if (reminderWordList.isEmpty() && currentWord.atEnd()) {
((Timer) e.getSource()).stop();
return;
}
// if just starting, or if done with current word
if (currentWord == null || currentWord.atEnd()) {
currentWord = reminderWordList.remove(0); // get next word
}
long totalElapsedTime = System.currentTimeMillis() - startTime;
if (totalElapsedTime > (currentWord.getStartElapsedTime() + currentWord
.getIndex() * currentWord.getTimePerChar())) {
setNextCharAttrib(currentWord.getStartIndexPosition() + currentWord.getIndex());
currentWord.increment();
}
}
}
private static void createAndShowGui() {
Reminder3 reminder = new Reminder3();
JFrame frame = new JFrame("Reminder");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(reminder.getMainPanel());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class ReminderWord {
private String word;
private int totalTime;
private int timePerChar;
private int startTime;
private int startIndexPosition;
private int index = 0;
public ReminderWord(String word, int totalTime, int startTime,
int startIndexPosition) {
this.word = word;
this.totalTime = totalTime;
this.startTime = startTime;
timePerChar = totalTime / word.length();
this.startIndexPosition = startIndexPosition;
}
public String getWord() {
return word;
}
public int getTotalTime() {
return totalTime;
}
public int getStartElapsedTime() {
return startTime;
}
public int getTimePerChar() {
return timePerChar;
}
public int getStartIndexPosition() {
return startIndexPosition;
}
public int increment() {
index++;
return index;
}
public int getIndex() {
return index;
}
public boolean atEnd() {
return index > word.length();
}
public void setStartIndexPosition(int startIndexPosition) {
this.startIndexPosition = startIndexPosition;
}
#Override
public String toString() {
return "ReminderWord [word=" + word + ", totalTime=" + totalTime
+ ", timePerChar=" + timePerChar + ", startTime=" + startTime
+ ", startIndexPosition=" + startIndexPosition + ", index=" + index
+ "]";
}
}
Okay so I have been looking at the some code (the code I posted in your last question about Karaoke timer)
Using that code I put up some measuring system using System.nanoTime() via System.out.println() which will help us to see what is happening:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class KaraokeTest {
private int[] timingsArray = {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};//word/letters timings
private String[] individualWordsToHighlight = {" \nHello\n", " world\n", " Hello", " world", " Hello", " world", " Hello", " world", " Hello", " world"};//each individual word/letters to highlight
private int count = 0;
private final JTextPane jtp = new JTextPane();
private final JButton startButton = new JButton("Start");
private final JFrame frame = new JFrame();
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Long> charsTiming = new ArrayList<>();
public KaraokeTest() {
initComponents();
}
private void initComponents() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
jtp.setEditable(false);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
startButton.setEnabled(false);
count = 0;
charsTiming.clear();
chars.clear();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
//System.out.println(String.valueOf(s.charAt(i)));
}
}
//calculate each letters timings
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
individualWordsToHighlight[x] = individualWordsToHighlight[x].replace("\n", " ").replace("\r", " ");//replace line breaks
charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));//dont count spaces
//System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
Timer t = new Timer(1, new AbstractAction() {
long startTime = 0;
long acum = 0;
long timeItTookTotal = 0;
long dif = 0, timeItTook = 0, timeToTake = 0;
int delay = 0;
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
if (count == 0) {
startTime = System.nanoTime();
System.out.println("Started: " + startTime);
}
timeToTake = charsTiming.get(count);
acum += timeToTake;
//highlight the next word
highlightNextWord();
//System.out.println("Acum " + acum);
timeItTook = (acum - ((System.nanoTime() - startTime) / 1000000));
timeItTookTotal += timeItTook;
//System.out.println("Elapsed since start: " + (System.nanoTime() - startTime));
System.out.println("Time the char should take: " + timeToTake);
System.out.println("Time it took: " + timeItTook);
dif = (timeToTake - timeItTook);
System.out.println("Difference: " + dif);
//System.out.println("Difference2 " + (timeToTake - dif));
//calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
delay = (int) (timeToTake - dif);
if (delay < 1) {
delay = 1;
}
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay((int) timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
//((Timer) ae.getSource()).setInitialDelay(delay);
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
long timeStopped = System.nanoTime();
System.out.println("Stopped: " + timeStopped);
System.out.println("Time it should take in total: " + acum);
System.out.println("Time it took using accumulator of time taken for each letter: " + timeItTookTotal
+ "\nDifference: " + (acum - timeItTookTotal));
long timeItTookUsingNanoTime = ((timeStopped - startTime) / 1000000);
System.out.println("Time it took using difference (endTime-startTime): " + timeItTookUsingNanoTime
+ "\nDifference: " + (acum - timeItTookUsingNanoTime));
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
});
t.setRepeats(false);
t.start();
}
});
frame.add(jtp, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private void reset() {
startButton.setEnabled(true);
jtp.setText("");
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
JOptionPane.showMessageDialog(frame, "Done");
}
private void highlightNextWord() {
//we still have words to highlight
int sp = 0;
for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
sp += 1;
}
while (chars.get(sp - 1).equals(" ")) {
sp += 1;
count++;
}
//highlight words
Style style = jtp.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new KaraokeTest();
}
});
}
}
The output on my PC is:
Started: 10289712615974
Time the char should take: 166
Time it took: 165
Difference 1
...
Time the char should take: 166
Time it took: 155
Difference 11
...
Time the char should take: 166
Time it took: 5
Difference 161
Stopped: 10299835063084
Time it should take in total: 9960
Time it took using accumulator of time taken for each letter: 5542
Difference: 4418
Time it took using difference (endTime-startTime): 10122
Difference: -162
Thus my conclusion is the Swing Timer is actually running faster than we expect as the code in the Timers actionPerformed will not necessarily take as long as the letters expected highlighting time this of course causes an avalanche effect i.e the faster/slower the timer runs the greater/less the difference will become and timers next execution on restart(..) will take be at a different time i.e faster or slower.
in the code do this:
//calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
delay = (int) (timeToTake - dif);
//restart timer with new timings
//((Timer) ae.getSource()).setInitialDelay((int)timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
((Timer) ae.getSource()).setInitialDelay(delay);
((Timer) ae.getSource()).restart();
Produces a more accurate result (maximum latency Ive had is 4ms faster per letter):
Started: 10813491256556
Time the char should take: 166
Time it took: 164
Difference 2
...
Time the char should take: 166
Time it took: 164
Difference 2
...
Time the char should take: 166
Time it took: 162
Difference 4
Stopped: 10823452105363
Time it should take in total: 9960
Time it took using accumulator of time taken for each letter: 9806
Difference: 154
Time it took using difference (endTime-startTime): 9960
Difference: 0
Have you considered java.util.Timer and scheduleAtFixedRate? You will need a little extra work to do stuff on the EDT, but it should fix the issue of accumulated delays.
ScheduledExecutorService tends to be more accurate than Swing's Timer, and it offers the benefit of running more than one thread. In particular, if one tasks gets delayed, it does not affect the starting time of the next tasks (to some extent).
Obviously if the tasks take too long on the EDT, this is going to be your limiting factor.
See below a proposed SSCCE based on yours - I have also slightly refactored the startColoring method and split it in several methods. I have also added some "logging" to get a feedback on the timing of the operations. Don't forget to shutdown the executor when you are done or it might prevent your program from exiting.
Each words starts colouring with a slight delay (between 5 and 20ms on my machine), but the delays are not cumulative. You could actually measure the scheduling overhead and adjust accordingly.
public class Reminder {
private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
"arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
"arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
"arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
"arey chod chaad ke apnee saleem ki gali anarkali disco chalo\n" +
"arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
private static final String[] WORDS = TEXT.split("\\s+");
private JFrame frame;
private StyledDocument doc;
private JTextPane textpane;
private static final int[] TIMES = {100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200,
100, 400, 300, 900, 1000, 600, 200, 700, 700, 200, 200, 200};
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
private int currentLetterIndex;
private long start; //for logging
public void startColoring() {
start = System.currentTimeMillis(); //for logging
int startTime = TIMES[0];
for (int i = 0; i < WORDS.length; i++) {
scheduler.schedule(colorWord(i, TIMES[i + 1]), startTime, TimeUnit.MILLISECONDS);
startTime += TIMES[i+1];
}
scheduler.schedule(new Runnable() {
#Override
public void run() {
scheduler.shutdownNow();
}
}, startTime, TimeUnit.MILLISECONDS);
}
//Color the given word, one letter at a time, for the given duration
private Runnable colorWord(final int wordIndex, final int duration) {
final int durationPerLetter = duration / WORDS[wordIndex].length();
final int wordStartIndex = currentLetterIndex;
currentLetterIndex += WORDS[wordIndex].length() + 1;
return new Runnable() {
#Override
public void run() {
System.out.println((System.currentTimeMillis() - start) + " ms - Word: " + WORDS[wordIndex] + " - duration = " + duration + "ms");
for (int i = 0; i < WORDS[wordIndex].length(); i++) {
scheduler.schedule(colorLetter(wordStartIndex + i), i * durationPerLetter, TimeUnit.MILLISECONDS);
}
}
};
}
//Color the letter on the EDT
private Runnable colorLetter(final int letterIndex) {
return new Runnable() {
#Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
System.out.println("\t" + (System.currentTimeMillis() - start) + " ms - letter: " + TEXT.charAt(letterIndex));
doc.setCharacterAttributes(letterIndex, 1, textpane.getStyle("Red"), true);
}
});
}
};
}
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.invokeLater(new Runnable() {
#Override
public void run() {
Reminder reminder = new Reminder();
reminder.initUI();
reminder.startColoring();
}
});
}
}

Categories

Resources