Proper character by character input? - c++

To better familiarize myself with C++, I'm redoing an old college OS assignment: program your own shell. The first thing I tackled was accepting commands but my approach leaves some features to be desired. Here's what I have:
string GetLine() {
string line;
char input = _getch();
while (input != 13) {
switch (input) {
case 8: // backspace
if (line.length() != 0) {
line = line.substr(0, line.length() - 1);
cout << "\b \b";
}
break;
case -32: // all arrows' first byte
input = _getch(); // distinctly different arrow byte
switch (input) {
case 72: // up
break;
case 80: // down
break;
case 75: // left
break;
case 77: // right
break;
}
break;
case 9: // tab
break;
default:
line += input;
cout << input;
break;
}
input = _getch();
}
cout << endl;
return line;
}
It works pretty well and I know I have more to do with the arrow keys (if I'm even distinguishing between them correctly as is). The main problem I'm asking about is a certain situation where the cursor has wrapped to the next line on the terminal. Aiming to emulate cmd.exe I print a prompt and wait at the end of it for the user's input. When this prompt is long, it only takes a few characters before my command runs across two lines. If I just type it out and let it wrap around and press enter everything works fine. If I wrap to the second line and want to backspace back to the first line, the cursor stops at the first character on it's new line and won't "unwrap" back to the first line as I'd hope. What can I do here to achieve this unwrapping behaviour?
I think the problem is that trying to print '\b' won't unwrap, but I don't have an idea for an alternative and I am hoping that I don't have to treat this as a special case and that code that will backspace in the middle of a line will also work at the beginning of a line.

You need to use the winapi, SetConsoleCursorPosition will help you. Here is a minimal example that moves the cursor up one row from the current position:
#include <iostream>
#include <windows.h>
int main() {
std::cout << "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaa";
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
GetConsoleScreenBufferInfo(hStdout, &csbiInfo);
csbiInfo.dwCursorPosition.Y -= 1; // Move Y coordinate up by one
SetConsoleCursorPosition(hStdout, csbiInfo.dwCursorPosition);
std::cin.get();
}
Also, Console Functions has an entire list of all the necessary functions to control the console. You will want to use these in your program.
You will need to make your own screen buffer with CreateConsoleScreenBuffer and then set it as the buffer first. Then after that handle everything written and read from the console yourself.

Related

How to get individual chars from user until he puts '.' or enter

I am looking for help with my task, i need get character string from user until he push '.' or enter? I have no idea how to get individual char using cin or even getch()? Please guys help me !
We can use std::cin.get() to get a single char.
std::vector<char> input;
for(bool key = false; !key;)
{
char keyPressed = std::cin.get();
switch(keyPressed)
{
case '.':
case '\n':
key = true;
break;
default:
input.append(keyPressed);
break;
}
}
Using a switch statement like above, we can sort out periods and newlines from the rest, making the loop exit when we hit one.

Implementing BackSpace with using getch() function

I would like to ask about inserting and deleting characters from string.
Here is the code:
void Edit::input() {
int len = 0;
COORD cord;
cord.X = _x;
cord.Y = _y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cord);
while (true) {
char ch = _getch();
if (ch == 13)
{
break;
}
else if (ch == 8)
{
if (len == 0)
{
continue;
}
pusty.pop_back();
std::cout << "\b \b";
len--;
}
else if (len == 6) {
break;
}
else {
pusty.push_back(ch);
len++;
}
std::cout << ch;
}
}
What is the problem exactly? I've got the coordinates of X and Y of the window where text will be entered in this window, length of whole string is 6 characters. The problem is in a moment when i want to use backspace when len value is at the last element. Then backspace creates blank space on 7th index. The picture below shows the issue. How to avoid this problem ? Thanks for all feedbacks.
That extra space is due to using getch, which echos to the console as well as returning the character to you. This means that the 7th position on the console's output is reached and thus the background is painted.
You just need to set the background colour to black (in a similar manner to how you set it to turquoise in your example) before printing \b \b, then set it back to turquoise afterwards - this'll hide your phantom extra character.
Rather than using getch, you should really consider learning ncurses - it provides really helpful functions that can make life a lot easier in this sort of situation (like a getch equivalent that doesn't echo). And it's cross-platform, so you can use it on Windows.

Reading from a file with getline never goes past the first line

I am trying to read the contents of an obj file in C++, and I am struggling with it. It's been a couple of years since I have done anything with C++, so I have forgotten a lot, but I am much more proficient in Java.
I am trying to read an obj file with contents like this
# This file uses centimeters as units for non-parametric coordinates.
mtllib crayon.mtl
g default
v 0.927930 -17.363054 -0.279038
...
vt 0.648603 0.107966
... etc
However, it doesn't seem to be able to read past the first line. Here is my code thus far:
void TextureMapper::read_file(string file_name)
{
ifstream file;
file.open(file_name);
if (file.is_open())
{
string line;
while (getline(file, line))
{
std::stringstream ss(line);
string token;
ss >> token;
switch (get_obj_type(token))
{
case GEOMETRIC_VERTEX:
break;
case TEXTURE_VERTEX:
break;
case VERTEX_NORMAL:
break;
case GROUP_NAME:
break;
case SMOOTHING_GROUP:
break;
case FACE:
break;
case MATERIAL_NAME:
break;
case MATERIAL_LIBRARY:
break;
case COMMENT:
break;
case BLANK:
break;
}
}
file.close();
}
else
std::cerr << "Couldn't open " << file_name << " for reading\n";
}
Where the enums (GEOMETRIC_VERTEX, GROUP_NAME, etc) are mapped to the parts of the obj file (like v, g vt, #, etc).
EDIT I realized the blank line in my file might be causing the exception, so I added an enum for that, and added it as a case to my switch statement, and now I don't get an exception, but it just never reads past the first line.
The first line (# This file uses centimeters as units ...) is read in properly, and I can see that when I debug it. However, when it re-enters the while loop again, it looks like it re-reads that same line, again and again.
I'm unsure what I am doing incorrectly when I read in this file. Let me know if you need more information to go on.

Flush out all other characters in input buffer

I am trying to control the usage of a small console application, and don't want the user to type anything more than one character. What I have so far
int main()
{
char choice;
string strChoice;
/*Set the title bar title to Binary Calculator*/
system("title Binary Calculator 2014");
do{
Menu();
getline(cin,strChoice);
choice = toupper(strChoice[0]); //convert value to uppercase for conformity
DetermineChoice(choice);
} while (mistakes < 3);
}
But when I type
bbbbbbbb
The screen goes buggy as all hell ( I believe it's caused from the do while loop ) so I need to just flush all characters besides the first one. Also when I select B the first time the program runs, then I go back and try to select B again it says I don't have anything but a carriage return in the input buffer.
Above is my int main. I'll show you the determine choice function and the error handling function.
void DetermineChoice(char value)
{
/*
Purpose: Determine what character was passed to value
Pre: a hopefully valid character
Post: Will go through cases below and then pass to another function
*/
string binary;
int decimal;
switch (value)
{
case 'B':
case 'C':
ConversionOperation(value);
case 'P':
cout << "Process File" << endl;
break;
case '+':
case '-':
case '/':
case '*':
case '%': ArithmeticActions(value);
case 'Q':
PrintSummary();
break;
default:
HandleChoiceError(value);
break;
}
}
Choice Error:
void HandleChoiceError(char value)
{
/*
Purpose: Handles the errorenous character passed
Pre: an invalid character
Post: Will output the error, pausing the screen then reprint the menu
*/
system("cls");
mistakes++;
cout << setw(40) << "The option you selected (" << value << ") is not a valid choice." << endl;
cout << setw(25) << "You have made " << mistakes << " mistake" << (mistakes > 1 ? "s" : "") <<" becareful only three allowed!" << endl;
cout << setw(60) << "Please press enter and try again" << endl;
if (mistakes < 3)
{
system("pause");
}
}
Some things that need to be aware of::
I can only use system (so please don't tell me it's bad or resource hog!)
I can't use fflush or any flushing besides cin.clear()
I can't use any other libraries besides iostream , iomanip , fstream , string , and ctype.h
Thanks everyone I now have the program working correctly.
int main()
{
char choice;
string strChoice;
/*Set the title bar title to Binary Calculator*/
system("title Binary Calculator 2014");
do{
Menu();
if ( getline(cin, strChoice) )
{
choice = toupper(strChoice[0]);
DetermineChoice(choice);
cin.ignore(10000, '\n');
}
else
{
cout << "Something went wrong with the input, please restart the program and try again." << endl;
break;
}
} while (mistakes < 3);
return 0;
}
There is no such thing as "flushing" an std::istream. Flushing is an output concept. Since there are multiple buffers for input from the console (the input buffer inside the std::istream's std::streambuf and an operating system consolde buffer) there is no reliable way to actually get rid of all input characters. You can get rid of all characters by disabling the concole's buffer (on UNIXes you'd use tcsetattr() and tcgetattr() to clear the ICANON flag).
The approaches which should be good enough for you needs are to either ignore all charactesr on the current line or to remove all characters in the input buffers:
To remove all characters on the currently you'd use std::istream::ignore() with the maximum number of characters to be ignored and the character where to stop, i.e., '\n' for the newline. To match as many characters as needed, you'd pass the magic value std::numeric_limits<std::streamsize>::max() (a reasonably large value, say 10000, would do the trick for practical needs, too).
You can find out a lower bound of characters immediately available using in.rdbuf()->in_avail(). This function determines how many characters can be read without the stream blocking. In practice, this is the number of characters in the std::istream's input buffer, i.e., something like in.ignore(in.rdbuf()->in_avail()) should remove all characters.
Pesronally, I would go with using in.ignore(count, '\n') with a suitable count (I'd obviously use std::numeric_limits<std::streamsize>::max() but it seems you can't use this function, probably because I'm currently helping with your homework assignment). Of course, std:getline() already reads the entire line anyway, i.e., there isn't really anything to be ignored.
Note that you should always verify whether the input operation was actuall successful:
if (std::getline(std::cin, line)) {
// process successful line
}
else {
// deal with the input having failed
}
Note, that input of a line is successful even when the line is empty! Before accessing the first character you should verify that it is present, e.g., using !line.empty().
BTW, as you have mentioned clear(): that actually doesn't ignore any characters. Instead, it clears the stream error flags, i.e., what is being tested to verify if input is successful. While being on notes: the argument to any of the <cctype> functions has to be a non-negative int in the value range of unsigned char plus EOF. Since char tends to be signed, passing an arbitrary char, e.g., strChoice[0] can result in undefined beheavior (e.g. when passing an ISO-Latin-1 or UTF-8 representation of my second name). The normal fix is to use
std::toupper(static_cast<unsigned char>(strChoice[0]))
Easiest to patch in: Replace
cin.ignore(1);
with
cin.ignore(numeric_limits<streamsize>::max(), '\n');
and
#include <limits>
at the top of the file. What this means is: "ignore all characters in the stream up to the next newline character (\n)". This is because
cin.ignore(n, c);
means "ignore n characters from cin, but stop after you found c," and numeric_limits<streamsize>::max() is the largest value in the streamsize type and a special case for istream::ignore where it means infinity.
This will work nicely for your use case because user input is generally line-based. It is better than discarding the input buffer because what's in the input buffer is not always predictable (someone could pipe a file to your program, the terminal could buffer strangely, the user could be a very fast typist, among other things). Discarding the input buffer wholesale will sometimes yield strange results, but nobody will be terribly surprised if you discard the rest of the line after a command.

How do I use a loop to display a menu and re-prompt for input?

I want to have a menu display that accepts user input. However, I want the user to be able to go back to the beginning of the menu to reselect options.
while(end != 1) {
display menu options
prompt user for input
if(input == x) {
do this
}
else
do that
}
then, i want it to skip back up to the beginning of the loop and ask the question over again. Howcan I do this without creating an infinite loop of the menu printing across the screen?
Unfortunately you didn't really show the code you are using but rather some pseudo code. Thus, it is hard to tell what you are actually trying to do. From the description of your problem and your pseudo code I suspect, however, that the root of the problem is that you don't check your inputs and don't restore the stream to a reasonably good state! To read the menu selection you probably want to use code akin to this:
int choice(0);
if (std::cin >> choice) {
deal with the choice of the menu here
}
else if (std::cin.eof()) {
// we failed because there is no further input: bail out!
return;
}
else {
std::string line;
std::cin.clear();
if (std::getline(std::cin, line)) {
std::cout << "the line '" << line << "' couldn't be procssed (ignored)\n";
}
else {
throw std::runtime_error("this place should never be reached! giving up");
}
}
This is just a rough layout of how the input would basically look like. It would probably be encapsulated into a function (in which case you'd want to bail out of from a closed input somewhat differently, possibly using an exception or a special return value). The main part of his is to
restore the stream back to good state using std::isteam::clear()
skip over the bad input, in this case using std::getline() with a std::string; you could also just std::istream::ignore() the remainder of the line
There may be other problems with your menu but without seeing concrete code I'd think it is hard to tell what the concrete problems are.
Instead of using a while, consider using a function, so you can call it where you need it:
void f()
{
if(end != 1) {
display menu options
prompt user for input
if(input == x) {
do this
f();
}
else{
do that
f();
}
}
}
I am not sure what your looking for either but this is some rough code of a menu
while(1){
cout<<"******* Menu ********\n";
cout<<"-- Selections Below --\n\n";
cout<<"1) Choice 1\n";
cout<<"2) Choice 2\n";
cout<<"3) Choice 3\n";
cout<<"4) Choice 4\n";
cout<<"5) Exit\n";
cout<<"Enter your choice (1,2,3,4, or 5): ";
cin>>choice;
cin.ignore();
switch(choice){
case 1 :
// Code for whatever you need here
break;
case 2 :
// Code for whatever you need here
break;
case 3 :
// Code for whatever you need here
break;
case 4 :
// Code for whatever you need here
break;
case 5 :
return 0;
}