Qt: how to move textEdit cursor to specific col and row - c++

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.

Related

Qt Writing mouse position to file

I'm writing a simple application using QT that should write the mouse position inside certain widget to a file (the writing is on user double click).
The situation is as follows:
The user performs double click inside the widget.
In the Debug window i can see the current mouse position.
The mouse position is written to the file as expected.
The issue when the mouse position (X or Y) is less then 100. In case that the position is less then 100 the number that is written to the file is always 3 digits number.
For example: Mouse position in the debug window is: 34, 251 and in the file the position is 344, 251. So i can't predict if the actual X position was 34 or 344 because both of them are valid values.
This is the part that responsible on writing the data to the file.
QByteArray temp1;
char buf[2];
::sprintf(buf, "%d", X); // X is the mouse x position
temp1.append(buf);
temp1.append(",");
::sprintf(buf, "%d", Y); // Y is the mouse y position
temp1.append(buf);
...
if (tempFile.open(QIODevice::ReadWrite)) {
QTextStream stream(&tempFile);
stream << temp1;
}
tempFile.close();
This code works good only for positions larger then 100 for some reason.
Thanks
Problem 1
The buffer is too small.
char buf[2];
There needs to be place for multiple digits and a terminating NUL byte, since your are using sprintf here:
::sprintf(buf, "%d", X);
Writing over the end of the array results in undefined behavior.
So you need to increase the size of the array to fix it.
Problem 2
the file is opened in ReadWrite mode
the X and Y value are written
the file is closed
The next time the values are written the same operations are called. ReadWrite mode does not delete the existing contents of the file. For example if you write once
128,1024
and then the next time you write the position: 60,30 it would look like this:
60,3024
One possible solution for this problem would be to apply Truncate mode.
if (tempFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) {

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.

Reading integers from files in Qt

I have a text file, with many lines that goes like this:
1,1
2
7,7
11,11
13,13
0,0
I would like to take every integer and assign it to a variable, using the text file system that Qt provides. I've thought about reading every line, then using QString::split(), but I think that there are easier methods to do this.
Use QFile::readAll, pass it to QString in constructor, split it to QStringList, iterate through it with toInt function.
Edited to suit better your purpose, this is simple console test app (i would assume, that line with only number 2 is a mistake and every line should have at least two numbers).
main.cpp:
QFile f("file.txt");
f.open(QIODevice::ReadOnly);
foreach (QString i,QString(f.readAll()).split(QRegExp("[\r\n]"),QString::SkipEmptyParts)){
QPoint pos;
pos.setX(i.section(",",0,0).toInt());
pos.setY(i.section(",",1,1).toInt());
// draw something here, pos holds your coords in x as first valur and in y second (pos.x(), pos.y() )
qDebug()<<pos;
}
f.close();
your coords will hold QPoint pos, it will have one line of coords at a time, so you can draw points or do whatever you want with them. file.txt should be in a dir with a binary file or you can change as it suit you.

Removing last line from QTextEdit

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);

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"