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

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.

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.

Notepad++ or UltraEdit: regex remove special duplicates

I need to remove duplicates if
key = anything
but NOT
key=anything
the key can be anything too
e.g.
edit_home=home must be in place
while
edit_home = home or even other string must be removed IF edit_home is a duplicate
for all the lines of the document
thank you
p.s. clearer example:
one=you are
two=we are
three_why=8908908
one = good
two = fine
three_4 = best
three_why = win
from that list i only need to keep:
one=you are
two=we are
three_why=8908908
three_4 = best // because three_4 doesn't have a duplicate
I found a method to do it, but I would need a better search list support by regex or a plugin or a direct regex (which I don't know).
That is: I have two files to compare.
One has the full keys, the other has incomplete.
I merge in a new file all the keys from the first file with those ones of the second, in groups (because the keys are in groups e.g. many keys titled one, many titled two and so on...). Then I regex replace all the keys in the new file by
find (.*)(\s\=\s) replace with \1\=
So they all become key=anything
Then I replace everything after = with empty to isolate the keys.
Then remove the duplicates.
At this point I have trouble to do something like
^.*(^keyone\b|^keytwo\b|^keythree\b).*$
to find all those keys in the document I need. So from that I can select all and replace with the correct keys.
Why? Because in this example the keys are 3 only BUT indeed the keys are many and the find field breaks at a certain point.
How to do it right?
Update: I found Toolbucket plugin which allows to search for many strings, but another issue is that in addition to duplicate, I also have to remove the original.
That is, if I find 2 times the same key "one" I have to remove all the lines containing one.
Ctrl + F
Find tab
Find what: ^.*\S=\S.*$
Find All in Current Document
Copy result from result window to a new window (the list of Line 1: Line 2: Line 3: ...)
Ctrl + F
Replace tab
(the following will remove the leading "Line number:" from every line)
Find what: ^.*?\d:\s
Replace with: Empty
ok, after all that i wrote, one solution could be (therefore, once i have the merged keys)
(?m)^(.*)$(?=\r?\n^(?!\1).*(?s).*?\1)
with this i can mark/highlight all the duplicated keys :-) so then i can manage those only, removing them from the first list and adding what remains to the second file...
If someone has a solution with a direct regex will be really appreciated
Here is a commented UltraEdit script for this task.
// Note: This script does not work for large files as it loads the
// entire file content into very limited scripting memory for fast
// processing even with multiple GB of RAM installed.
if (UltraEdit.document.length > 0) // Is any file opened?
{
// Define environment for this script and select entire file content.
UltraEdit.insertMode();
UltraEdit.columnModeOff();
UltraEdit.activeDocument.selectAll();
// Determine line termination used currently in active file.
var sLineTerm = "\r\n";
if (typeof(UltraEdit.activeDocument.lineTerminator) == "number")
{
// The two lines below require UE v16.00 or UES v10.00 or later.
if (UltraEdit.activeDocument.lineTerminator == 1) sLineTerm = "\n";
else if (UltraEdit.activeDocument.lineTerminator == 2) sLineTerm = "\r";
}
else // This version of UE/UES does not offer line terminator property.
{
if (UltraEdit.activeDocument.selection.indexOf(sLineTerm) < 0)
{
sLineTerm = "\n"; // Not DOS, perhaps UNIX.
if (UltraEdit.activeDocument.selection.indexOf(sLineTerm) < 0)
{
sLineTerm = "\r"; // Also not UNIX, perhaps MAC.
if (UltraEdit.activeDocument.selection.indexOf(sLineTerm) < 0)
{
sLineTerm = "\r\n"; // No line terminator, use DOS.
}
}
}
}
// Get all lines of active file into an array of strings
// with each string being one line from active file.
var asLines = UltraEdit.activeDocument.selection.split(sLineTerm);
var nTotalLines = asLines.length;
// Process each line in the array.
for(var nCurrentLine = 0; nCurrentLine < asLines.length; nCurrentLine++)
{
// Skip all lines not containing or starting with an equal sign.
if (asLines[nCurrentLine].indexOf('=') < 1) continue;
// Get string left to equal sign with tabs/spaces trimmed.
var sKey = asLines[nCurrentLine].replace(/^[\t ]*([^\t =]+).*$/,"$1");
// Skip lines beginning with just tabs/spaces left to equal sign.
if (sKey.length == asLines[nCurrentLine].length) continue;
var_dump(sKey);
// Build the regular expression for the search in all other lines.
var rRegSearch = new RegExp("^[\\t ]*"+sKey+"[\\t ]*=","g");
// Ceck all remaining lines for a line also starting with
// this key string case-sensitive with left to an equal sign.
var nLineCompare = nCurrentLine + 1;
while(nLineCompare < asLines.length)
{
// Does this line also has this key left to equal
// sign with or without surrounding spaces/tabs?
if (asLines[nLineCompare].search(rRegSearch) < 0)
{
nLineCompare++; // No, continue on next line.
}
else // Yes, remove this line from array.
{
asLines.splice(nLineCompare,1);
}
}
}
// Was any line removed from the array?
if (nTotalLines == asLines.length)
{
UltraEdit.activeDocument.top(); // Cancel the selection.
UltraEdit.messageBox("Nothing found to remove!");
}
else
{
// If version of UE/UES supports direct write to clipboard, use
// user clipboard 9 to paste the lines into file with overwritting
// everything as this is much faster than using write command in
// older versions of UE/UES.
if (typeof(UltraEdit.clipboardContent) == "string")
{
var nActiveClipboard = UltraEdit.clipboardIdx;
UltraEdit.selectClipboard(9);
UltraEdit.clipboardContent = asLines.join(sLineTerm);
UltraEdit.activeDocument.paste();
UltraEdit.clearClipboard();
UltraEdit.selectClipboard(nActiveClipboard);
}
else UltraEdit.activeDocument.write(asLines.join(sLineTerm));
var nRemoved = nTotalLines - asLines.length;
UltraEdit.activeDocument.top();
UltraEdit.messageBox("Removed " + nRemoved + " line" + ((nRemoved != 1) ? "s" : "") + " on updated file.");
}
}
Copy this code and paste it into a new ASCII file using DOS line terminators in UltraEdit.
Next use command File - Save As to save the script file for example with name RemoveDuplicateKeys.js into %AppData%\IDMComp\UltraEdit\MyScripts or wherever you want to have saved your UltraEdit scripts.
Open Scripting - Scripts and add the just saved UltraEdit script to the list of scripts. You can enter a description for this script, too.
Open the file with the list, or make this file active if it is already opened in UltraEdit.
Run the script by clicking on it in menu Scripting, or by opening Views - Views/Lists - Script List and double clicking on the script.

Gtk::TextView with constant string

I am using Gtkmm 3+ and What I am trying to do is have the text buffer have the constant string "> " even if the user tries to delete it. In addition when the user pressed return it will automatically be there again. Basically have a constant string like a terminal does.
The only way I can think about about accomplishing this would be to connect to the delete and backspace signals so the user cannot delete the string. But, is there a better way?
so far this is the only way I can think of:
//in constructor
txt_view_i_.signal_event().connect(sigc::mem_fun(*this, &MainWindow::inputEvent));
//function
bool MainWindow::inputEvent(GdkEvent* event)
{
if((event->key.keyval == GDK_KEY_BackSpace || event->key.keyval == GDK_KEY_Delete) && buffer_input_->get_char_count() < 3)
return true;
return false;
}
But doesn't work perfectly, because if you type in more then 3 characters then go to the beginning of the line you can delete the constant string.
Another way I just thought about was to add a label to the TextView widget. I did that but, the user could still delete it. Here is the code for that:
Gtk::TextBuffer::iterator it = buffer_input_->get_iter_at_line(1);
Glib::RefPtr<Gtk::TextChildAnchor> refAnchor = buffer_input_->create_child_anchor(it);
Gtk::Label* lbl = Gtk::manage(new Gtk::Label("> "));
txt_view_i_.add_child_at_anchor(*lbl, refAnchor);
This is very similar, but not quite identical, to the question I answered here: You can create a GtkTextTag that makes its contents uneditable, and apply it from the beginning of the buffer up to and including the "> " prompt.
Then when you receive input, append your output to the buffer and then append a new prompt on the next line, and re-apply the tag to make the whole thing uneditable.
The links in the linked answer show some C code where this is done, even including a prompt. It's not Gtkmm or C++, but it should serve as an illustration.
Here is the code I used to solve it:
Glib::RefPtr<Gtk::TextBuffer::Tag> tag = Gtk::TextBuffer::Tag::create();
tag->property_editable() = false;
Glib::RefPtr<Gtk::TextBuffer::TagTable> tag_table = Gtk::TextBuffer::TagTable::create();
tag_table->add(tag);
buffer_input_ = Gtk::TextBuffer::create(tag_table);
txt_view_i_.set_buffer(buffer_input_);
scroll_win_i_.add(txt_view_i_);
Gtk::TextBuffer::iterator buffer_it_ = buffer_input_->begin();
buffer_input_->insert_with_tag(buffer_it_, "> ", tag);
Here is how I made it so that the user cannot edit before the constant string:
//connect to the mark set signal
buffer_input_->signal_mark_set().connect(sigc::mem_fun(*this, &MainWindow::setMark));
//make the box uneditable
void MainWindow::setMark(const Gtk::TextBuffer::iterator& it, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark)
{
if(it.get_offset() < 2)
txt_view_i_.set_editable(false);
else
txt_view_i_.set_editable(true);
}
Hopefully someone will find this useful.

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"