LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Command-line progress meter in C++ (https://www.linuxquestions.org/questions/programming-9/command-line-progress-meter-in-c-626650/)

JMJ_coder 03-08-2008 03:02 PM

Command-line progress meter in C++
 
Hello,

In C++, in a program that runs from the terminal, I want to show some sort of progress meter.


I am thinking of a revolving bar: \ | / -- \ | ,etc. - this would be stationary and the character would change to reflect revolving motion.

Or of a percentage completion number: [10%] -> [36%] -> [100%] - again remain stationary and only the numbers would change.

Or of a progress bar - [****> 23% ] -> [**********> 42% ], etc. this would remain stationary, and the numbers would change and the number of stars in the bar would increase.



How do I do this type of dynamic change on a terminal output?

jailbait 03-08-2008 03:17 PM

Quote:

Originally Posted by JMJ_coder (Post 3082358)


How do I do this type of dynamic change on a terminal output?

You do it using escape sequences.

http://www.linux-mag.com/downloads/2...sequences.html

The escape sequence that you need most is backspace. I think that backspace is ^H

http://www.linux.com/articles/53729

In any case the thing to do is look up how to pass escape sequences in C++.

-----------------------
Steve Stites


P.S. This documentation says that in C++ backspace is passed as \b.

http://linux.isoe.ch/Prog/Array/escape.html

osor 03-08-2008 07:58 PM

Quote:

Originally Posted by jailbait (Post 3082370)
P.S. This documentation says that in C++ backspace is passed as \b.

You will most likely not want to use backspace, but carriage-return (‘\r’), so you can keep an internal string representation of your bar, print a carriage-return (which moves the cursor to the beginning of the current line, but does not descend to the next line), followed by the entire next line. Here is a previous post pertaining to what you want (except it is not in C++, and there is no revolving bar). Also, take a look at the source of e2fsprogs.

JMJ_coder 03-08-2008 08:24 PM

Hello,

Quote:

Originally Posted by osor (Post 3082582)
You will most likely not want to use backspace, but carriage-return (‘\r’), so you can keep an internal string representation of your bar, print a carriage-return (which moves the cursor to the beginning of the current line, but does not descend to the next line), followed by the entire next line. Here is a previous post pertaining to what you want (except it is not in C++, and there is no revolving bar). Also, take a look at the source of e2fsprogs.

Thanks for the links! I'll look over the code (and hopefully will understand it).

ta0kira 03-08-2008 09:43 PM

All you need to know is to use a \r instead of \n (as was suggested) and remember to overwrite the entire used part of the line each time so that you don't have residual display data. You'll really need to use parts of the POSIX C API to make it work reliably because you'll 1) need to write to the terminal or skip the progress bar entirely if there is no terminal, 2) determine the width of the terminal so that you don't get bizarre output for, say, a 20-character terminal. Let us know when you get a basic working version of what you're doing and we'll help with the terminal part if you'd like.
ta0kira

PS Backspace characters don't always perform the function of backspace in the terminal. That only happens if the terminal is set up that way.

JMJ_coder 03-10-2008 02:29 PM

Hello,

Quote:

Originally Posted by ta0kira (Post 3082639)
Let us know when you get a basic working version of what you're doing and we'll help with the terminal part if you'd like.

Thanks. I think for my current program, the revolving bar will work best (thanks osor for the link). But, I am still going to write a block of code for the progress bar so I'll know how to do it in the future.

I'll post the code in a couple of days after it is written.


Again, thanks for all the help!

JMJ_coder 03-17-2008 07:17 PM

Hello,

Here is the code for the rotating bar:

Code:

#include <iostream>

void rotateBar(int);

int main()
{

  int i;

  for (i = 0; i < 1000; i++){
   
    rotateBar(i);

  }

  std::cout << std::endl;

  return 0;

}


void rotateBar(int numIn)
{

  char barspin[4] = {'\\', '|', '/', '-'};
  int whichOne;

  whichOne = numIn % 4;

  if (whichOne == 3)

    std::cout << '\r'
              << barspin[whichOne]
              << barspin[whichOne]
              << " please wait while function is processing";

  else
   
    std::cout << '\r'
              << barspin[whichOne]
              << " please wait while function is processing";
 

  return;

}

It works, but it goes so fast that all you see is the final one. How do I slow it down so that a human can see it spinning?

I am assuming it has something to do with sleeping or counting a number of clock ticks per cycle, but I have no idea how to do that.

osor 03-17-2008 08:49 PM

Quote:

Originally Posted by JMJ_coder (Post 3092028)
It works, but it goes so fast that all you see is the final one. How do I slow it down so that a human can see it spinning?

Actually, it doesn’t. As ta0kira said before, you have to be careful to erase the entire line (overwrite it with spaces) to get a good effect. Now, the second character is always a ‘-’ (which is why it doesn’t appear to change). To correct this behavior, you would need something like:
Code:

else
   
    std::cout << '\r'
              << barspin[whichOne] << ' '
              << " please wait while function is processing";

IMHO, you do not need the second character. It looks out of place, especially if the user is used to a spinner whose width is always 1.
Quote:

Originally Posted by JMJ_coder (Post 3092028)
I am assuming it has something to do with sleeping or counting a number of clock ticks per cycle, but I have no idea how to do that.

Well, as a demonstration, you could simply do:
Code:

for (i = 0; i < 1000; i++){
   
    rotateBar(i);
    sleep(1);

  }

In real life, you would need divide your task into chunks and call rotateBar() after each chunk is completed. If you want to use C idioms, it would be best not to pass an argument to the function, and instead give it static storage within the function (so its value is preserved between function calls). If you want to use C++ idioms, you would want to make rotateBar() a member function and the internal state integer a member variable.

JMJ_coder 03-17-2008 09:53 PM

Hello,

Quote:

Originally Posted by osor (Post 3092074)
IMHO, you do not need the second character. It looks out of place, especially if the user is used to a spinner whose width is always 1.

You know, you're right. I guess I was thinking in the way '-' is half as long as '|', '\', or '/' so print it twice.


Quote:

Originally Posted by osor (Post 3092074)
Well, as a demonstration, you could simply do:
Code:

for (i = 0; i < 1000; i++){
   
    rotateBar(i);
    sleep(1);

  }


I tried that before I posted the code here, but I get nothing (so I took it out). The program compiles, but when I run it, it just sits there with no output.



Quote:

Originally Posted by osor (Post 3092074)
In real life, you would need divide your task into chunks and call rotateBar() after each chunk is completed. If you want to use C idioms, it would be best not to pass an argument to the function, and instead give it static storage within the function (so its value is preserved between function calls). If you want to use C++ idioms, you would want to make rotateBar() a member function and the internal state integer a member variable.

I'll keep that in mind for when I actually will use this. Right now, it is just experimentation on my part.

osor 03-17-2008 10:18 PM

Quote:

Originally Posted by JMJ_coder (Post 3092135)
I tried that before I posted the code here, but I get nothing (so I took it out). The program compiles, but when I run it, it just sits there with no output.

This is because of buffering (I should have tried it out myself). Try putting this statement at the end of rotateBar():
Code:

std::cout.flush();

JMJ_coder 03-18-2008 08:19 AM

Hello,

Quote:

Originally Posted by osor (Post 3092149)
This is because of buffering (I should have tried it out myself). Try putting this statement at the end of rotateBar():
Code:

std::cout.flush();

Yes, that worked. Why?

Also, sleep() only takes an int for an argument. What if I only want it to sleep for say half a second or a tenth of a second?

osor 03-18-2008 12:16 PM

Quote:

Originally Posted by JMJ_coder (Post 3092578)
Yes, that worked. Why?

This is because stdout is normally line-buffered (where “line” is defined as a series of characters delimited by the newline character ‘\n’). Since you don’t have this occuring in your writing to stdout, you have to force a buffer flush.
Quote:

Originally Posted by JMJ_coder (Post 3092578)
Also, sleep() only takes an int for an argument. What if I only want it to sleep for say half a second or a tenth of a second?

Try usleep() or nanosleep().

JMJ_coder 03-18-2008 12:31 PM

Hello,

Quote:

Originally Posted by osor (Post 3092835)
This is because stdout is normally line-buffered (where “line” is defined as a series of characters delimited by the newline character ‘\n’). Since you don’t have this occuring in your writing to stdout, you have to force a buffer flush.

Try usleep() or nanosleep().

I understand, thanks for all the help.


All times are GMT -5. The time now is 02:09 PM.