I have been learning about arrays and an interesting question popped up in my head.
I was wondering that with the current Java version, is there a way for me to print a character string n and make it appear for a brief moment at every index of an array consisting of only "", and then towards the end of the array, it can stop when it reaches the end index of the array.
For example if here is the given array and string n = "2" :
[2,"","","",""]
the code will continously update like
["2","","","",""]
["","2","","",""]
["","","2","",""]
["","","","2",""]
["","","","","2"]
and the end result would be
["","","","","2"]
I would like to see the whole movement of "2" being played out without printing any excess arrays ( no more than one array should be in the output).
Is this possible? If yes, can you please suggest what should I look over to learn how to do this?
You can do this with Java but you won't be able to do it reliably within all consoles or terminals. You can however do it reliably if you utilize a GUI mechanism like a JOptionPane or JDialog and display that during console operation, for example:
The above example is a JDialog. Below is the code (read comments within):
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class AnimatedMoveArrayElementDemo {
/* Default. The length of the Array to animate.
Can be changed via command-line (/L:n) argument. */
private int lengthOfArray = 8;
/* Default. The Array value to move from beginning to end.
Can be changed via command-line (/N:n) argument. */
private int arrayValueToMove = 2;
/* In Milliseconds (1000 = 1 Second).
Can be changed via command-line (/S:n) argument. */
private int animationSpeed = 1000;
/* Default. The dialog display font size.
Can be changed via command-line (/F:n) argument. */
private int displayFontSize = 24;
private String[] stringArray = {};
int arrayIndex = 0;
Timer animationTimer;
JButton startButton;
JLabel arrayLabel;
public static void main(String[] args) {
// App started this way to avoid the need for statics
new AnimatedMoveArrayElementDemo().startApp(args);
}
private void startApp(String[] args) {
if (args.length > 0) {
readCommandLineArguments(args);
}
fillArray();
createAndShowDialog();
}
private void createAndShowDialog() {
JDialog dialog = new JDialog();
dialog.setTitle("Moving Array Element To The End Position");
dialog.setBackground(Color.white);
dialog.getContentPane().setBackground(Color.white);
dialog.setAlwaysOnTop(true);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setModal(true);
arrayLabel = new JLabel();
resetDisplayLabel();
arrayLabel.setOpaque(false);
arrayLabel.setHorizontalAlignment(JLabel.CENTER);
arrayLabel.setVerticalAlignment(JLabel.CENTER);
arrayLabel.setFont(new Font(arrayLabel.getFont().getFamily(),
arrayLabel.getFont().getStyle(), displayFontSize));
dialog.add(arrayLabel, BorderLayout.NORTH);
int calculatedWidth = getStringPixelWidth(arrayLabel.getFont(),
arrayLabel.getText().replaceAll("<.+?>", "")) + 50;
int calculatedHeight = getStringPixelHeight(arrayLabel.getFont(),
arrayLabel.getText().replaceAll("<.+?>", "")) + 100;
dialog.setPreferredSize(new Dimension(calculatedWidth, calculatedHeight));
JPanel buttonPanel = new JPanel();
buttonPanel.setOpaque(false);
startButton = new JButton("Start Animation");
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Start Animation")) {
if (arrayIndex > stringArray.length - 1) {
resetDisplayLabel();
arrayIndex = 0;
}
startButton.setActionCommand("Stop Animation");
// Using a Swing Timer...for animation
ActionListener performTask = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
arrayIndex++;
if (arrayIndex > stringArray.length - 1) {
animationTimer.stop();
startButton.setText("Restart Animation");
startButton.setActionCommand("Start Animation");
return;
}
stringArray[arrayIndex - 1] = "\"\"";
stringArray[arrayIndex] = String.valueOf(arrayValueToMove);
String arrayString = "<html>" + Arrays.toString(stringArray) + "</html>";
arrayString = arrayString.replace(String.valueOf(arrayValueToMove),
"\"<font color=red>" + String.valueOf(arrayValueToMove)
+ "</font>\"");
arrayLabel.setText(arrayString);
}
};
animationTimer = new Timer(animationSpeed, performTask);
animationTimer.start();
startButton.setText("Stop Animation");
}
else {
animationTimer.stop();
startButton.setText("Start Animation");
startButton.setActionCommand("Start Animation");
}
}
});
buttonPanel.add(startButton);
dialog.add(buttonPanel, BorderLayout.SOUTH);
dialog.pack();
dialog.setLocationRelativeTo(null);
java.awt.EventQueue.invokeLater(() -> {
dialog.setVisible(true);
});
}
private void fillArray() {
stringArray = new String[lengthOfArray];
for (int i = 0; i < stringArray.length; i++) {
if (i == 0) {
stringArray[i] = "\"" + arrayValueToMove + "\"";
}
else {
stringArray[i] = "\"\"";
}
}
}
private void resetDisplayLabel() {
fillArray();
String arrayString = "<html>" + Arrays.toString(stringArray) + "</html>";
arrayString = arrayString.replace(String.valueOf(arrayValueToMove),
"<font color=red>" + String.valueOf(arrayValueToMove)
+ "</font>");
arrayLabel.setText(arrayString);
}
/**
* This application can currently accept four specific integer command-line
* arguments prefixed with a specific Command related to that argument.
*
* #param args (Command-Line varArgs [optional])<pre>
*
* Length Of Array: The length (# of elements) of the String[] Array
* to animate. The longer the array the smaller the
* Command: /L font size you <u>may</u> want to use so to fit the array
* into the display window. The display window will
* automatically size itself to try and accommodate
* the array length. The default is 8.
*
* Examples of acceptable command-line commands for
* this argument are: /L{value}, /L:{value}, etc.
* Basically, The command can be anything as long as
* it starts with /L (or /l) and contains no spaces
* or digit(s). Digits are reserved for the actual
* argument value passed along with the command, for
* example: /L:8 (/L: 8 is not acceptable) or you
* could use: /Length=8. Anything can be between the
* /L and the integer argument value. Either will tell
* the application the the length of the Array to
* display will contain 8 elements. No whitespaces
* are permitted within a Command-Line Command.
*
* Array Value To Move: This would be the integer value that is placed
* within the first element of the String Array at
* Command: /N index 0. The default value is: <b>2</b> however
* you can change this value to whatever you like.
*
* Examples of acceptable command-line commands for
* this argument are: /N{value}, /N:{value}, etc.
* Basically, The command can be anything as long as
* it starts with /N (or /n) and contains no spaces
* or digit(s). Digits are reserved for the actual
* argument value passed along with the command, for
* example: /N:8 (/N: 8 is not acceptable) or you
* could use: /Number=8. Anything can be between the
* /N and the integer argument value. Either will tell
* the application the the number within the Array to
* display will be the number 8. No whitespaces are
* permitted within a Command-Line Command.
*
* Animation Speed: Default is a value of 1000 milliseconds which is
* basically equivalent to 1 second. You can set the
* Command: /S animation speed to whatever you like but do keep
* in mind that you could set a speed that will be so
* fast that you can't tell there is any animation.
*
* The value passed with this command would be an
* integer value representing Milliseconds.
*
* Examples of acceptable command-line commands for
* this argument are: /S{value}, /S:{value}, etc.
* Basically, The command can be anything as long as
* it starts with /S (or /s) and contains no spaces
* or digit(s). Digits are reserved for the actual
* argument value passed along with the command, for
* example: /S:800 (/S: 800 is not acceptable) or you
* could use: /Speed=800. Anything can be between the
* /S and the integer argument value. Either will tell
* the application that the animation speed for the
* Array display will be 800ms. No whitespaces are
* permitted within a Command-Line Command.
*
* Display Font Size: Default is a font size of 24 but any font size can
* be used to display the Animation and the display
* Command: /F window will automatically size accordingly.
*
* Examples of acceptable command-line commands for
* this argument are: /F{value}, /F:{value}, etc.
* Basically, The command can be anything as long as
* it starts with /F (or /f) and contains no spaces
* or digit(s). Digits are reserved for the actual
* argument value passed along with the command, for
* example: /F:36 (/F: 36 is not acceptable) or you
* could use: /Font=36. Anything can be between the
* /F and the integer argument value. Either will tell
* the application that the animation Font size for the
* Array display will be 36pt. No whitespaces are allowed
* within a Command-Line Command.</pre>
*/
private void readCommandLineArguments(String[] args) {
String command = "";
int value;
for (String arg : args) {
// Split Alpha and Numeric.
String[] argParts = arg.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
command = argParts[0].substring(0, 2);
value = 0;
if (argParts.length == 2) {
value = Integer.parseInt(argParts[1]);
}
switch (command.toUpperCase()) {
case "/L":
this.lengthOfArray = value;
break;
case "/N":
this.arrayValueToMove = value;
break;
case "/S":
this.animationSpeed = value;
break;
case "/F":
this.displayFontSize = value;
break;
default:
System.err.println("Unknown Command-Line Argument!");
}
}
}
/**
* Returns the pixel width of the supplied String.<br>
*
* #param font (Font) The String Font to base calculations from.<br>
*
* #param characterString (String) The string to get the pixel width for.<br>
*
* #return (int) The pixel width of the supplied String.
*/
public int getStringPixelWidth(Font font, String characterString) {
FontMetrics metrics = new FontMetrics(font) {
private static final long serialVersionUID = 1L;
};
Rectangle2D bounds = metrics.getStringBounds(characterString, null);
return (int) bounds.getWidth();
}
/**
* Returns the pixel height of the supplied String.<br>
*
* #param font (Font) The String Font to base calculations from.<br>
*
* #param characterString (String) The string to get the pixel height for.<br>
*
* #return (int) The pixel height of the supplied String.
*/
public int getStringPixelHeight(Font font, String characterString) {
FontMetrics metrics = new FontMetrics(font) {
private static final long serialVersionUID = 1L;
};
Rectangle2D bounds = metrics.getStringBounds(characterString, null);
return (int) bounds.getHeight();
}
}
import java.io.*;
import java.util.Arrays;
public class MyClass {
public static void main(String args[]) {
String n = "2";
String array[] = new String[10];
Arrays.fill(array, "");
array[0] = n;
int i = 0;
System.out.println(Arrays.toString(array));
while(i < array.length-1){
// swap
String temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
System.out.println(Arrays.toString(array));
i++;
}
}
}
You could try something like the following:
public static void main(String[] args) throws InterruptedException {
annimate("2");
}
private static void annimate(String uniqueElement) throws InterruptedException {
String[] array = new String[]{"2", "", "", "", ""};
int uniqueElemIndex = 0;
while (uniqueElemIndex < array.length) {
System.out.println(Arrays.toString(array));
for (int i = 0; i < array.length; i++) {
if (array[i].equals(uniqueElement)) {
uniqueElemIndex = i;
break;
}
}
if (uniqueElemIndex + 1 < array.length) {
String elem = array[uniqueElemIndex];
array[uniqueElemIndex + 1] = elem;
array[uniqueElemIndex] = "";
}
uniqueElemIndex++;
Thread.sleep(500);
}
}
This outputs the following:
[2, , , , ]
[, 2, , , ]
[, , 2, , ]
[, , , 2, ]
[, , , , 2]
I am trying to create a program as practice in which the clock ticks every second. I tried to find information on this, but it was either too complicated for what I have learned, or not relevant.
I would like it to sort of work like this:
Clock starts at time: 12:00:00 AM
Clock has been set to time: 11:59:00 PM
TICK: 11:59:01 PM
This is the code that I have written so far:
public class Clock {
public static void main(String[] args) {
SimpleClock clock = new SimpleClock();
System.out.println("Clock starts at time: " + clock.time());
clock.set(11, 59, 00, false);
System.out.println("Clock has been set to time: " + clock.time());
for (int j = 0; j < 60; j++) {
for (int i = 0; i < 60; i++) {
clock.tick();
System.out.println("TICK: " + clock.time());
}
}
System.out.println("Clock finally reads: " + clock.time());
}
}
The GUI:
public class ClockView extends JFrame {
/* -----------------Private Member variables --------------------- */
private static final long serialVersionUID = 1L;
private static int ROWS_IN_GRID = 2;
private static int COLS_IN_GRID = 1;
private static int BUTTON_ROWS = 1;
private static int BUTTON_COLS = 2;
private SimpleClock clock;
private JLabel face;
/**
* Constructor. Takes a SimpleClock as an argument and builds a graphical
* interface using that clock the model. The Tick button increments the
* clock by 1 second, while the Reset button sets the clock back to midnight
* (12:00:00AM). *
*
* #param clock
* - the clock instance used to store the time for the view
*/
public ClockView(SimpleClock clock) {
super("SimpleClock Demo");
this.clock = clock;
this.face = new JLabel("<html><span style='font-size:20px'>"
+ this.clock.time() + "</span></html>");
this.setLayout(new GridLayout(ROWS_IN_GRID, COLS_IN_GRID));
this.add(this.face);
JPanel buttonPanel = new JPanel(
new GridLayout(BUTTON_ROWS, BUTTON_COLS));
JButton tickButton = new JButton("Tick");
tickButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
clock.tick();
ClockView.this.face
.setText("<html><span style='font-size:20px'>"
+ clock.time() + "</span></html>");
}
});
buttonPanel.add(tickButton);
JButton resetButton = new JButton("reset");
resetButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
clock.set(12, 0, 0, true);
ClockView.this.face
.setText("<html><span style='font-size:20px'>"
+ clock.time() + "</span></html>");
}
});
buttonPanel.add(resetButton);
this.add(buttonPanel);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ClockView v = new ClockView(new SimpleClock());
}
}
I am quit positive my logical error occurs in public void tick() of class SimpleClock
Essentially, it just switches from am to pm because I currently have the program switching at the end of the while loop. I know I have to move it, but I not sure how to as the clock doesn't even tick in the first place.
Your current tick method contains a lot of loop that will always end up with a time >= 12hours. Why ? Because you will add one seconds until the loop condition isn't meet. So until hours >= 12 but also somethime until minutes = 59 or seconds = 59
You should only add one seconds like the comment said: Advances the clock by 1 second. .
And then do the specific checks.
public void tick(){
seconds++;
if(seconds => 60){
seconds = 0;
minutes++;
if(....)
...
}
}
Also, morning will always be switch here, but you should only do it if hours reach the limit
I'm trying to set my int "n" to be defined by a users input. however it is never set and I'm not sure what's wrong. I'M NOT very good at java and this IS homework. I think my problem is very basic but I'm stuck.
So, to restate my question. Why can't I set my int n to be a user input? The "n" problem isn't the actual homework but in order for my homework to work correctly, "n" has to be set.
package printer.java;
import java.util.Queue;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Random;
import java.util.Scanner;
public class PrinterJava {
// Declaring ints needed
int count = 0;
int jobCount = 0;
int done = 0;
int time = 0;
int jobTimerDelay = 1000;
int jobTimerPeriod = 1000;
int timeTimerDelay = 1000;
int timeTimerPeriod = n * 60 * 1000;
// declaring timers needed
Timer jobTimerTimer = new Timer();
Timer timeTimerTimer = new Timer();
// This is a timer that is supposed to create new "pages" every 5 seconds.
//the pages have to be a random "size between 1 and 5 pages long"
public void jobTimer() {
jobTimerTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
count++;
Random dom = new Random();
int p = dom.nextInt(5) + 1;
if (count % 5 == 0) {
pages page = new pages(); // Creates a new page every 5 seconds
page.pages = p;
jobCount++;
jobQueue.offer(page); // pushes the newly created pages into the queue
System.out.println("A new Job has been created! Job queue size: " + jobQueue.size());
System.out.println("Total Jobs created: " + jobCount);
} else if (!jobQueue.isEmpty() && count > 2 && count % 2 == 0) {
done++;
jobQueue.remove();
System.out.println("Job printed successfully! total jobs printed: " + done);
}
}
}, jobTimerDelay, jobTimerPeriod);
}
// this is the queue that holds the pages
Queue<Object> jobQueue = new LinkedList<Object>();
public class pages { // pages
int pages;
// constructor
public pages() {
}
public pages(int NumPages) {
this.pages = NumPages;
}
}
public void timerTwo() {
timeTimerTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
PrinterJava runOne = new PrinterJava(); // creats an instance of my page creator
runOne.jobTimer();
System.out.println("Please Enter Run time in minutes as an integer: ");
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
}
}, timeTimerDelay, timeTimerPeriod);
}
public static void main(String[] args) {
PrinterJava runTwo = new PrinterJava(); // creats an instance of my page creator
runTwo.timerTwo();
}
}
This line won't compile because n is not defined yet:
int timeTimerPeriod = n * 60 * 1000;
However, if it would this won't work as expected either:
timeTimerTimer.scheduleAtFixedRate(new TimerTask() {...}, timeTimerDelay, timeTimerPeriod);
Because n is defined inside TimerTask.run() method. To solve this consider make this changes:
int timeTimerPeriod = 60 * 1000; // instance variable
int n = 0;
...
public void timerTwo() {
System.out.println("Please Enter Run time in minutes as an integer: ");
Scanner scan = new Scanner(System.in);
n = scan.nextInt(); // <-- read n here for first time
timeTimerTimer.scheduleAtFixedRate(new TimerTask() {...}, timeTimerDelay, timeTimerPeriod * n);
}
When you say int n you are declaring a new variable, so after that next line you don't have a reference to it anymore. Also, i don't see n declared as an instance variable or anywhere else.
What if you set a breakpoint right after the line int n = scan.nextInt(); and see if it is being set there (alternatively you could use System.out.println() to print it out.
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();
}
});
}
}
I have this program in Java import java.util.*;
public class Euclid {
private static final String EXCEPTION_MSG =
"Invalid value (%d); only positive integers are allowed. ";
public static int getGcd( int a, int b)
{
if (a < 0)
{
throw new IllegalArgumentException(String.format(EXCEPTION_MSG, a));
}
else
if (b < 0)
{
throw new IllegalArgumentException(String.format(EXCEPTION_MSG, b));
}
while (b != 0)
{
if (a > b)
{
a = a - b;
}
else
{
b = b - a;
}
}
return a;
}
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
public class EuclidGui
{
private static final String PROMPT_A = "#1";
private static final String PROMPT_B = "#2";
private static final String BUTTON_TEXT = "Get GCD >>";
private static final String EXCEPTION_TITLE = "Input Exception";
private static final String INSTRUCTIONS = "Type to integer and press 'Get GCD'";
private static final String DIALOG_TITLE = "Euclid's Algorithm";
private static final int FIELD_WIDTH = 6;
public static void main (String[] args)
{
final JTextField valueA = new JTextField (FIELD_WIDTH);
final JTextField valueB = new JTextField (FIELD_WIDTH);
final JTextField valueGcd = new JTextField (FIELD_WIDTH);
JLabel labelA = new JLabel(PROMPT_A);
JLabel labelB = new JLabel(PROMPT_B);
JButton computeButton = new JButton(BUTTON_TEXT);
Object[] options = new Object[] {labelA, valueA, labelB, valueB, computeButton, valueGcd};
valueGcd.setEditable (false);
computeButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{ try
{
int a = Integer.parseInt(valueA.getText());
int b = Integer.parseInt(valueB.getText());
int gcd = Euclid.getGcd(a , b);
valueGcd.setText(Integer.toString(gcd));
}
catch (Exception e)
{
JOptionPane.showMessageDialog(null, e.getMessage(), EXCEPTION_TITLE, JOptionPane.ERROR_MESSAGE);
}
}
});
JOptionPane.showOptionDialog(null, INSTRUCTIONS, DIALOG_TITLE, JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE,
null, options, null);
}
}
and I want to add calculation time code on it but I have no Idea how, and what is the correct code that I use.Please help me if any one have an Idea.
System.currentTimeMillis() would do the trick:
long start = System.currentTimeMillis();
// do stuff
long timeTaken = System.currentTimeMillis() - start;
The chances are that a single call to getGcd will take so little time that you will have difficulty measuring it reliable and accurately.
The System.currentTimeMillis() method will give you the wall clock time measured in milliseconds. However, the granularity of the millisecond clock is probably too coarse. (Read the javadoc!).
The System.nanoTime() method gives a system time with (as the javadoc says) "nanosecond precision, but not necessarily nanosecond accuracy.".
There are also issues with nanoTime() on multicore machines with some operating systems. For instance, I've heard that different cores can have independent nanoTime clocks that can drift with respect to each other. This can result in System.nanoTime() returning values that are non-monotonic; e.g. if the current thread is rescheduled by the OS to run on a different core between two nanoTime() calls.
If I was doing this, I'd put the calls to getGcd() into a loop that ran it 10,000 or 100,000 times, measure the time for the loop, and divide the measured time by the relevant factor.