Removing last line from QTextEdit - c++

I'm using this bit of code to try to remove the last line from a QTextEdit:
ui->textEdit_2->textCursor().setPosition( QTextCursor::End);
auto k = ui->textEdit_2->textCursor().currentTable();
k->removeRows(k->rows() - 1, 1);
but I get a segmentation fault. After debugging I found that that k is null when removeRows is called.
Am I doing something wrong? if yes, how can it be fixed?

Try this (Tested):
ui->textEdit_2->setFocus();
QTextCursor storeCursorPos = ui->textEdit_2->textCursor();
ui->textEdit_2->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
ui->textEdit_2->moveCursor(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
ui->textEdit_2->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
ui->textEdit_2->textCursor().removeSelectedText();
ui->textEdit_2->textCursor().deletePreviousChar();
ui->textEdit_2->setTextCursor(storeCursorPos);

(Just leaving this undeleted to show another way of doing the same action)
You can try this to remove last line:
QTextCursor cursor = ui->textEdit_2->textCursor();
cursor.movePosition(QTextCursor::End);
cursor.select(QTextCursor::LineUnderCursor);
cursor.removeSelectedText();
cursor.deletePreviousChar(); // Added to trim the newline char when removing last line
ui->textEdit_2->setTextCursor(cursor);
If you want to restore cursor position to where it originally was, Just save the cursor position before calling
cursor.movePosition(QTextCursor::End);
and then after removing text.
ui->textEdit_2->setTextCursor(savedCursorPos);

Related

Qt: how to move textEdit cursor to specific col and row

I can get the current position in row and col via QTextCursor::blockNumber() and QTextCursor::positionInBlock(). My question is that how to move the cursor to the specific position with row and col. like
setPosition(x,y) // The current cursor would move to row x and col y.
Is it possible to do that?
Easy solution:
Just move the cursor there:
textEdit.moveCursor(QTextCursor::Start); //set to start
for( <... y-times ...> )
{
textEdit.moveCursor(QTextCursor::Down); //move down
}
for( < ... x-times ...>)
{
textEdit.moveCursor(QTextCursor::Right); //move right
}
moveCursor is also the goto-way if you need to "select" text for changing it. A similar approach without the loops is also at the end.
A bit more explanation and maybe better solution:
In theory a text has no "lines" like shown in GUIs but the endline-character (\n or \r\n depending on operating system and framework) is only just another character. So for a cursor mostly everything is just one "text" without lines.
There are wrapper functions to deal with this but I get to them later. First you cannot access those directly via the QTextEdit interface but you have to manipulate the cursor directly.
QTextCursor curs = textEdit.textCursor(); //copies current cursor
//... cursor operations
textEdit.setTextCursor(curs);
Now for the "operations":
If you know at what position you want to go in the string you have setPosition() here. This "position" is not in respect to vertical lines though, but the whole text.
This is what a multi-line string looks internally:
"Hello, World!\nAnotherLine"
This would show
Hello, World!
AnotherLine
setPosition() wants the position of the internal string.
To move to another line you would have to calculate the position by looking for the first \n in the text and add your x-offset. If you want the 3rd line look for the first 2 \n etc.
Luckily there is also the function setVerticalMovement which seems to wrap this and maybe is what you want to do. It moves the cursor vertically.
So you could do:
curs.setPosition(x); //beginning at first line
curs.setVerticalMovement(y); //move down to the line you want.
After that call setTextCursor like shown above with the cursor.
Note:
The order is important though. setPosition sets the position in the whole text. So setPosition(5) while maybe in the 3rd line will not set it to the 5th character in the line you are but of the whole text. So move the x-cordinate first then y.
You need to be aware of the lengths of the lines though.
some longer line
short
another longer line
If you now specify line 2 and column 7 it will be "out-of-bounds". I am not sure how verticalMovement here behaves. I assume the cursor will be at the end of the line.
When you use the QTextCursor class directly you can also use the move operations without the loops because they have an extra parameter to repeat the operations.
curs.movePosition(QTextCursor::Start);
curs.movePosition(QTextCursor::Down,<modeMode>,y); //go down y-times
curs.movePosition(QTextCursor::Right,<moveMode>,x); //go right x-times
I think the best way is via QTextCursor.
For example, if your QTextEdit is called textEdit:
QTextCursor textCursor = ui->textEdit->textCursor();
textCursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, x);
textCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, y);
ui->textEdit->setTextCursor(textCursor);
Where x and y are the required position.
If the document layout uses 1 block per line (e.g. it's a QPlainTextEdit or the document contains text with very basic formatting), then you can set the position directly:
void setCursorLineAndColumn (QTextCursor &cursor, int line, int col,
QTextCursor::MoveMode mode)
{
QTextBlock b = cursor.document()->findBlockByLineNumber(line);
cursor.setPosition(b.position() + col, mode);
// you could make it a one-liner if you really want to, i guess.
}
The major advantage over the Down + Right methods is it is much simpler to select text between two locations:
QTextCursor cursor = textEdit->textCursor();
setCursorLineAndColumn(cursor, startLine, startCol, QTextCursor::MoveAnchor);
setCursorLineAndColumn(cursor, endLine, endCol, QTextCursor::KeepAnchor);
textEdit->setTextCursor(cursor);
There's also a performance advantage, if relevant.

push_back() in std::vector<std::string> overwriting current string

I'm trying to implement an undo function when inserting a new line for a very simple text editor as a project for class. In the program, the column is where the cursor currently is, and the row is the current line the vector is on.
I was able to successfully create my "insertNewLine()" function which uses a std::vector<std::string> that is able to display the text to the screen. This is how I implemented it:
void Editor::insertNewLine()
{
// get a substring
prevLine = lines[row - 1];
size_t tempSize = prevLine.size();
int lengthOffset = getSubstringOffset(tempSize, (column - 1));
std::string cutTemp = prevLine.substr((column - 1), lengthOffset);
lines[row - 1].erase(column - 1);
// after incrementing, row and amount of lines, initialize the new row
row++;
numberOfLines++;
column = 1;
lines.push_back(cutTemp); // insert substring into new line
}
Here is an example of what the current output looks like this (where | is the cursor):
hello world| (user enters hello world, column = 11, row = 1)
hello|world (user moves cursor to column 5, still on row 1)
(user presses button that calls insertNewLine())
hello
|world (splits where the cursor is to a new line, cursor begins at column 1)
Now, I am able to undo any other command, but when trying to undo a new line, I need to have the cursor return to the previous column, and push the word back where it originally was. I tried implementing that by doing this:
void Editor::undoNewLine()
{
std::string source = lines[row - 1]; // save current line
lines[row-1].clear(); // clear current line
row--; // revert up one row
numberOfLines--; // revert amount of lines
lines.push_back(source); // append
}
With this function, I expected the output to look like this (from the example above):
(user presses a button that calls undoNewLine())
hello|world
But, the problem is, this is the output I get from the current code:
(user presses a button that calls undoNewLine())
|world
Essentially, using push_back(source) overwrites whatever was originally there and brings the cursor to the front. I tried to increment column to the original position it was in before the undo stage, however, this didn't work either. I just ended up with this output:
(user presses a button that calls undoNewLine())
world|
So how should I try and implement this undo function? Any tips or ideas on what I'm doing wrong?
In your solution, you're erasing the previous line's content (hello) with the call to clear(). Instead, just append the current line. The string class makes this easy:
lines[row-1] += lines[row];
After that you can remove the current line with vector::erase.
Note
Be aware that this is might be inefficient, since all the lines below need to be repositioned.
If this does become an issue you can switch to std::list but then you lose random access to your lines.

Having trouble concatenating CStrings in an MFC calculator application

void CcalculatorDlg::OnBnClickedButton1()
{
CString grabData = _T("");
m_display.GetLine(0,grabData.GetBuffer(10),10);
grabData += _T("1");
m_display.SetWindowTextW(grabData.GetBuffer());
grabData.ReleaseBuffer();
}
I am trying to make a basic calculator application using MFC, and I am having some trouble with the number inputs.
Above is the code for when the "1" button is pressed. I want it to read in what's already being displayed in the display control, and then add a 1 onto the end of it like real calculators do. However I just can't get it to work.
Basically the first button press it works and changes the blank display (edit control) to a 1. But then successive presses don't continue to add 1's, and I cannot figure out why.
I think the problem in your code is that you tried to modify the string (concatenating _T("1")) after calling GetBuffer() but before calling ReleaseBuffer(). Moreover, you have unbalanced GetBuffer()/ReleaseBuffer() calls.
Assuming that m_display is a CEdit instance, you can try code like this (worked for me):
void CcalculatorDlg::OnBnClickedButton1()
{
// Get current text from edit control
// (assume a single-line edit control)
CString grabData;
m_display.GetWindowText(grabData);
// Concatenate "1"
grabData += L'1';
// Update edit control text
m_display.SetWindowText(grabData);
}
If you have a multi-line edit control and you want to grab the first (top-most) line using CEdit::GetLine(), you can use code like this (note that according to MSDN documentation, EM_GETLINE doesn't NUL-terminate the copied line, so you have to explicitly specify line length to ReleaseBuffer()):
//
// Read first line from edit control
//
CString grabData;
static const int kMaxBufferLength = 80;
wchar_t* buffer = grabData.GetBuffer(kMaxBufferLength + 1);
// Note '+ 1' for NUL string terminator (it seems that EM_GETLINE, which is
// wrapped by CEdit::GetLine(), doesn't NUL-terminate the returned string).
const int grabDataLength = m_display.GetLine(0, buffer, kMaxBufferLength);
grabData.ReleaseBuffer(grabDataLength);
// *After* calling ReleaseBuffer(), you can modify the string, e.g.:
grabData += L'1'; // concatenate "1"

How to change the position where next character will be place in the Edit Control from MFC?

I have piece of code that erases the last character of a string and then set the text in the Edit Control to that new string. The problem is that, afterwards, the position of the character that will be typed next changes.
Example:
Edit control box: [ 12345| ] (The slash is where the next character
typed will be placed)
After doing the code mentioned
Edit control box: [ |12345 ] (The position now moved to the front,
before 1)
How would I move the position to the end of the string again?
My code:
CString str1 = ""; //Temporary CString
eb1->GetWindowText(str1); //Store text from Edit Control to the CString
string strCheck1 = str1.GetString(); //Place the CString into a regular string
int s1 = strCheck1.length() -1; //Get index of last character and new size
bool check1 = true; //Boolean variable for the checking
//Get if character is valid
if((strCheck1[s1] <= '0' || strCheck1[s1] >='9') && strCheck1[s1] != '.') {check1 = false;}
//If is invalid I erase last character and put it back intact into the Edit Control
if(check1 == false) {strCheck1.erase(s1,1); eb1->SetWindowTextA(strCheck1.c_str());}
Have you tried SetSel() operation of edit control?
// get the initial text length
int nLength = edit.GetWindowTextLength();
// put the selection at the end of text
edit.SetSel(nLength, nLength);
You can use CEdit::SetSel() (I'm assuming that you are using CEdit). Just let the start and end of selection both be the end of string, you should be able to move the cursor there. Details may be found at http://msdn.microsoft.com/en-us/library/w9kftda4(v=vs.80).aspx

How to update a printed message in terminal without reprinting

I want to make a progress bar for my terminal application that would work something like:
[XXXXXXX ]
which would give a visual indication of how much time there is left before the process completes.
I know I can do something like printing more and more X's by adding them to the string and then simply printf, but that would look like:
[XXXXXXX ]
[XXXXXXXX ]
[XXXXXXXXX ]
[XXXXXXXXXX ]
or something like that (obviously you can play with the spacing.) But this is not visually aesthetic. Is there a way to update the printed text in a terminal with new text without reprinting? This is all under linux, c++.
try using \r instead of \n when printing the new "version".
for(int i=0;i<=100;++i) printf("\r[%3d%%]",i);
printf("\n");
I'd say that a library like ncurses would be used to such things. curses helps move the cursor around the screen and draw text and such.
NCurses
Something like this:
std::stringstream out;
for (int i = 0; i< 10; i++)
{
out << "X";
cout << "\r" << "[" << out.str() << "]";
}
The sneaky bit is the carriage return character "\r" which causes the cursor to move to the start of the line without going down to the next line.
Others have already pointed out that you can use \r to go back to the beginning of the current line, and overwrite the entire line.
Another possibility is to use the backspace character ("\b") to erase a few spaces, and overwrite only those spaces. This can have a couple of advantages. First, it obviously avoids having to regenerate everything in the line, which can sometimes be mildly painful (though that is fairly unusual). Second, it can avoid some pain in displaying data that (for one example) shrinks in size as you write it -- for example, if you're displaying a count-down from 100 to 0, with \r you have to be careful about overwriting the entire previous length, or your countdown will go from (for example) 100 to 990 (i.e., leaving the previous "0" intact).
Note, however, that while back-space within a line normally works, a backspace at the beginning of a line may or may not move the cursor/write position back to a previous line. For most practical purposes, you can only move around within a single line.
'\r' will perform a carriage return. Imagine a printer doing a carriage return without a linefeed ('\n'). This will return the writing point back to the start of the line... then reprint your updated status on top of the original line.
It's a different language, but this question might be of assistance to you. Basically, the escape character \r (carriage Return, as opposed to \n Newline) moves you back to the beginning of your current printed line so you can overwrite what you've already printed.
Another option is to simply print one character at a time. Typically, stdout is line buffered, so you'll need to call fflush(stdout) --
for(int i = 0; i < 50; ++i) {
putchar('X'); fflush(stdout);
/* do some stuff here */
}
putchar('\n');
But this doesn't have the nice terminating "]" that indicates completion.
I've written this loading bar utility some time ago. Might be useful...
https://github.com/BlaDrzz/CppUtility/tree/master/LoadingBar
You can customise basically anything in here.
int max = 1000;
LoadingBar* lb = new LoadingBar(10, 0, max);
for (size_t i = 0; i <= max; i++)
{
lb->print();
lb->iterate();
}
cout << lb->toString() << endl;
Very simple and customisable implementation..