How to disable middle button functionality of QTextEdit? - c++

I don't want mouse middle button to paste text in my QTextEdit. This code doesn't work. TextEdit inherits QTextEdit. After mouse middle button pastes it pastes copied text.
void TextEdit::mousePressEvent ( QMouseEvent * e ) {
if (e->button() == Qt::MidButton) {
e->accept();
return;
};
QTextEdit::mousePressEvent(e);
}

As mouse clicks are usually registered when the button is released, you should redefine the mouseReleaseEvent function.
You don't even need to redefine mousePressEvent, because the middle button isn't handled at all by that function.

I'm assuming you're using Linux here; right clicking in the window is likely to be triggering an insertion of mime data before you get to handle the mouse event, which is why it is still pasting text.
Therefore, according to Qt docs for paste: - " to modify what QTextEdit can paste and how it is being pasted, reimplement the virtual canInsertFromMimeData() and insertFromMimeData() functions."

I've been in the same case, that is to say: having parts of my CustomQTextEdit required to be non-editable.
As I truly love the middle mouse button paste feature, I did not wanted to disable it. So, here is the (more or less quick and dirty coded) workaround I used:
void QTextEditHighlighter::mouseReleaseEvent(QMouseEvent *e)
{
QString prev_text;
if (e->button() == Qt::MidButton) {
// Backup the text as it is before middle button click
prev_text = this->toPlainText();
// And let the paste operation occure...
// e->accept();
// return;
}
// !!!!
QTextEdit::mouseReleaseEvent(e);
// !!!!
if (e->button() == Qt::MidButton) {
/*
* Keep track of the editbale ranges (up to you).
* My way is a single one range inbetween the unique
* tags "//# BEGIN_EDIT" and "//# END_EDIT"...
*/
QRegExp begin_regexp = QRegExp("(^|\n)(\\s)*//# BEGIN_EDIT[^\n]*(?=\n|$)");
QRegExp end_regexp = QRegExp("(^|\n)(\\s)*//# END_EDIT[^\n]*(?=\n|$)");
QTextCursor from = QTextCursor(this->document());
from.movePosition(QTextCursor::Start);
QTextCursor cursor_begin = this->document()->find(begin_regexp, from);
QTextCursor cursor_end = this->document()->find(end_regexp, from);
cursor_begin.movePosition(QTextCursor::EndOfBlock);
cursor_end.movePosition(QTextCursor::StartOfBlock);
int begin_pos = cursor_begin.position();
int end_pos = cursor_end.position();
if (!(cursor_begin.isNull() || cursor_end.isNull())) {
// Deduce the insertion index by finding the position
// of the first character that changed between previous
// text and the current "after-paste" text
int insert_pos; //, end_insert_pos;
std::string s_cur = this->toPlainText().toStdString();
std::string s_prev = prev_text.toStdString();
int i_max = std::min(s_cur.length(), s_prev.length());
for (insert_pos=0; insert_pos < i_max; insert_pos++) {
if (s_cur[insert_pos] != s_prev[insert_pos])
break;
}
// If the insertion point is not in my editable area: just restore the
// text as it was before the paste occured
if (insert_pos < begin_pos+1 || insert_pos > end_pos) {
// Restore text (ghostly)
((MainWindow *)this->topLevelWidget())->disconnect(this, SIGNAL(textChanged()), ((MainWindow *)this->topLevelWidget()), SLOT(on_textEdit_CustomMacro_textChanged()));
this->setText(prev_text);
((MainWindow *)this->topLevelWidget())->connect(this, SIGNAL(textChanged()), ((MainWindow *)this->topLevelWidget()), SLOT(on_textEdit_CustomMacro_textChanged()));
}
}
}
}

Related

How to emit QTextTexid::textChanged signal only in certain cases selectively?

Can you trigger the textChanged signal only for certain cases? for example triggering it for inserted text, inserted space characters but not backspace?
I am running checks for typed characters inside a QTextEdit every time the text changes and based on the results highlighting text inside a read-only QTextEdit in the background used as an always-on placeholder text for the user to look at while typing. If the user makes a mistake the character gets highlighted red and reset to it's initial background color after the mistake is fixed. The problem arises when the backspace key is pressed, as it is registered as a mistake and as a result the previous character also gets highlighted red.
void Widget::onTextChanged()
{
QChar c;
QString txt_contents = txtedit_->toPlainText();
if(txt_contents.isEmpty()){
c = '\0';
//reset text display
txtdisplay_->clear();
txtdisplay_->append(*label_text_);
}
else
c = txtedit_->toPlainText().back();
if(!texteditq_->isEmpty()){
if(c == texteditq_->head()){
//refresh text display
correct_++;
txtdisplay_->clear();
txtdisplay_->append(*label_text_);
//remove character that was used for the successful check from the
//queue
texteditq_->dequeue();
}else{
//set backgroud color to red for errors
fmt_->setBackground(Qt::red);
if(!txtedit_->toPlainText().isEmpty()){
//move cursor to the end of the editor, where the error is and save
//the position the error occurs at
c_edit_->movePosition(QTextCursor::End);
quint32 error_pos = c_edit_->position();
//move the cursor in the display for the background text and
//use the KeepAnchor move mode to highlight the misspelled char
c_display_->setPosition(error_pos-1,QTextCursor::MoveAnchor);
c_display_->setPosition(error_pos,QTextCursor::KeepAnchor);
//apply formating to that character
c_display_->setCharFormat(*fmt_);
}
}
}
}
As per OP's request, I am posting a solution that is a class called CustomTextEdit which is subclassing QTextEdit. It is hooking to keyPressEvent() and checking the key that was pressed. If it was other than Backspace then the custom signal keyPressed() will get emitted.
class CustomTextEdit : public QTextEdit {
Q_OBJECT
public:
explicit CustomTextEdit(QWidget *parent = nullptr) : QTextEdit(parent) {}
signals:
void keyPressed();
protected:
void keyPressEvent(QKeyEvent *e) override {
if (e->key() != Qt::Key_Backspace) {
emit keyPressed();
}
QTextEdit::keyPressEvent(e);
}
};

How to get QTextBrowser to always insert text at the end

I am trying to make a serial terminal program by using QTextBrowser to display incoming data from a serial port. I have set a QTimer to call the paintEvent every 100ms, and show characters on the QTextBrowser widget if anything was received on the serial port.
My problem is that every time I click say in the middle of the QTextBrowser, it is as if though the cursor moves and then on all subsequent ui->tbOutput->insertPlainText(QString(buf));, only half of the QTextBrowser gets updated.
When I click on the bottom of the QTextBrowser widget, the whole QTextBrowser is updated again.
This is the code that I have, where from various other articles, I have tried to scroll to the bottom, and move the text cursor to the end, but it does not do what I want.
void MainWindow::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
static char buf[10240];
if (terminal->serialport.bytesAvailable() > 0)
{
// sizeof(buf)-1 so that there is space for zero termination character
qint64 numread = terminal->serialport.read(buf,sizeof(buf)-1);
if ((numread > 0) && (numread < sizeof(buf)))
{
buf[numread] = 0; // set zero termination
ui->tbOutput->insertPlainText(QString(buf));
ui->tbOutput->verticalScrollBar()->setValue(
ui->tbOutput->verticalScrollBar()->maximum());
ui->tbOutput->textCursor().setPosition(QTextCursor::End);
}
}
}
A few things:
QTextBrowser::textCursor returns a copy, so any modification is not applied to the document
QTextBrowser::setPosition moves the cursor to an absolute position, therefore you are always moving to position 11 (int value to QTextCursor::End). Use QTextBrowser::movePosition instead
finally, it would be better to move the cursor before adding the text, so you are sure it will be added at the end of the document.
Here the modified code:
void MainWindow::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
static char buf[10240];
if (terminal->serialport.bytesAvailable() > 0)
{
// sizeof(buf)-1 so that there is space for zero termination character
qint64 numread = terminal->serialport.read(buf,sizeof(buf)-1);
if ((numread > 0) && (numread < sizeof(buf)))
{
buf[numread] = 0; // set zero termination
auto textCursor = ui->tbOutput->textCursor();
textCursor.movePosition(QTextCursor::End);
ui->tbOutput->setTextCursor(textCursor);
ui->tbOutput->insertPlainText(QString(buf));
ui->tbOutput->verticalScrollBar()->setValue(
ui->tbOutput->verticalScrollBar()->maximum());
}
}
}
On the other hand, some additional considerations:
QIODevice::read(char* data, qint64 maxSize) will read at most maxSize bytes, so checking if the number of read bytes is smaller than your buffer is unnecessary.
Do not do it in the paintEvent, it is not the place to read data but to display it. Instead, connect the timer with a slot and read data there and re-paint your console (ui->tbOutput->update()) only if new data has arrived.

QTextEdit - conditional drag and drop according to QCursor position

I have a QTextEdit with text. The user is allowed to change the text only from the QCursor position stored in startPos variable to the end of document. The begining of the text must remain the same.
I managed to do that by conditioning of QCursor position.
But user can at any moment drag and drop some text in forbidden area.
I want to make a conditional drag and drop according to QCursor position. So, if user drop some text in forbidden area (before cursor position startPos) I want to put that text at the end of the document. And if user drop text after cursor position startPos, user to be allowed to do so.
class BasicOutput : public QTextEdit, public ViewWidgetIFace
{
Q_OBJECT
public:
BasicOutput();
~BasicOutput();
virtual void dragEnterEvent(QDragEnterEvent *e);
virtual void dropEvent(QDropEvent *event);
private:
int startPos;
};
and the rest of simplified (non-functional) code:
BasicOutput::BasicOutput( ) : QTextEdit () {
setInputMethodHints(Qt::ImhNoPredictiveText);
setFocusPolicy(Qt::StrongFocus);
setAcceptRichText(false);
setUndoRedoEnabled(false);
}
void BasicOutput::dragEnterEvent(QDragEnterEvent *e){
e->acceptProposedAction();
}
void BasicOutput::dropEvent(QDropEvent *event){
QPoint p = event->pos(); //get position of drop
QTextCursor t(textCursor()); //create a cursor for QTextEdit
t.setPos(&p); //try convert QPoint to QTextCursor to compare with position stored in startPos variable - ERROR
//if dropCursorPosition < startPos then t = endOfDocument
//if dropCursorPosition >= startPos then t remains the same
p = t.pos(); //convert the manipulated cursor position to QPoint - ERROR
QDropEvent drop(p,event->dropAction(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers(), event->type());
QTextEdit::dropEvent(&drop); // Call the parent function w/ the modified event
}
The errors are:
In member function 'virtual void BasicOutput::dropEvent(QDropEvent*)':
error: 'class QTextCursor' has no member named 'setPos' t.setPos(&p);
error: 'class QTextCursor' has no member named 'pos'p = t.pos();
How to protect the forbidden text area from user drag and drop?
Rspectfully,
Florin.
FINAL CODE
void BasicOutput::dragEnterEvent(QDragEnterEvent *e){
if (e->mimeData()->hasFormat("text/plain"))
e->acceptProposedAction();
else
e->ignore();
}
void BasicOutput::dragMoveEvent (QDragMoveEvent *event){
QTextCursor t = cursorForPosition(event->pos());
if (t.position() >= startPos){
event->acceptProposedAction();
QDragMoveEvent move(event->pos(),event->dropAction(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers(), event->type());
QTextEdit::dragMoveEvent(&move); // Call the parent function (show cursor and keep selection)
}else
event->ignore();
}
You currently have...
QTextCursor t(textCursor()); //create a cursor for QTextEdit
t.setPos(&p);
If you want a QTextCursor associated with the proposed drop location you should use...
QTextCursor t = cursorForPosition(p);
That should fix the first compilation error. Unfortunately there doesn't appear to be any obvious way to get the QPoint associated with a QTextCursor (though there may be a way going via QTextDocument and QTextBlock, I haven't checked). If that's the case then you'll have to perform the drop yourself...
if (t.position() < startPos)
t.movePosition(QTextCursor::End);
setTextCursor(t);
insertPlainText(event->mimeData()->text());
However, can I suggest that what you are attempting to do might prove very confusing to the user. There should be some visual indicator as to what will happen if the text is dropped. How is the user to know that if they drop the text on the forbidden area it will be appended to the end of the current text -- which may not even be visible on a large document?
With that in mind a better approach might be to override dragMoveEvent...
void BasicOutput::dragMoveEvent (QDragMoveEvent *event)
{
QTextCursor t = cursorForPosition(p);
if (t.position() >= startPos)
event->acceptProposedAction();
}
Here the proposed drop action is only accepted if the mouse pointer is not in the forbidden region. Otherwise the user will see (via the pointer glyph or whatever) that the drop will not be accepted.

QTextEdit. How to select text manually?

There are functions like textEdit->textCursor()->selectionStart() and textEdit->textCursor()->selectionEnd(), but there are no functions setSelectionStart, setSelectionEnd.
Is there any way to select some part of text manually?
QTextCursor c = textEdit->textCursor();
c.setPosition(startPos);
c.setPosition(endPos, QTextCursor::KeepAnchor);
textEdit->setTextCursor(c);
This piece of code moves the cursor to the start position of the selection using setPosition, then moves it to the end of the selection, but leaves the selection anchor at the old position by specifying a MoveMode as the second parameter.
The last line sets the selection to be visible inside the edit control, so you should skip it if you just want to do some manipulations with the selected text.
Also, if you don't have the exact positions, movePosition is helpful: you can move the cursor in various ways, such as one word to the right or down one line.
I encountered a similar problem.
In Windows 10, there might be a bug of 'drag/move'. We use QT_NO_DRAGANDDROP as a compiler option, which makes text selection in QTextEdit not work anymore.
Solution:
void QTextEditEx::mouseMoveEvent(QMouseEvent *event)
{
QTextEdit::mouseMoveEvent(event);
if (event->buttons() & Qt::LeftButton)
{
QTextCursor cursor = textCursor();
QTextCursor endCursor = cursorForPosition(event->pos()); // key point
cursor.setPosition(pos, QTextCursor::MoveAnchor);
cursor.setPosition(endCursor.position(), QTextCursor::KeepAnchor);
setTextCursor(cursor);
}
}
void QTextEditEx::mousePressEvent(QMouseEvent *event)
{
QTextEdit::mousePressEvent(event);
if (event->buttons() & Qt::LeftButton)
{
QTextCursor cursor = cursorForPosition(event->pos());
// int pos; member variable
pos = cursor.position();
cursor.clearSelection();
setTextCursor(cursor);
}
}
reference:
Two existing answers
QTextEdit: get word under the mouse pointer?
Try to use:
QTextCursor cur = tw->textCursor();
cur.clearSelection();
tw->setTextCursor(cur);

Why does this code not highlight the search term once found?

The code below does not highlight the search term when it is found. In fact the cursor disappears from the QPlainTextEdit (called ui->Editor) after pressing the 'next' button. What's causing it?
void TextEditor::findNextInstanceOfSearchTerm()
{
QString searchTerm = this->edtFind->text();
if(this->TextDocument == NULL)
{
this->TextDocument = ui->Editor->document();
}
QTextCursor documentCursor(this->TextDocument);
documentCursor = this->TextDocument->find(searchTerm,documentCursor);
if(!documentCursor.isNull())
{
documentCursor.select(QTextCursor::WordUnderCursor);
}else
{
ui->statusbar->showMessage("\""+searchTerm+"\" could not be found",MESSAGE_DURATION);
}
}
Firstly, your code creates a new cursor at the beginning of the document each time you press the next button, so you will always search from the beginning. Secondly, you must understand that the cursor you manipulate has nothing to do with the one in your QPlainTextEdit: you manipulate a copy. If you want to impact the text edit, you must modify its cursor using setTextCursor. Here is a working solution:
void TextEditor::findNextInstanceOfSearchTerm()
{
QString searchTerm = this->edtFind->text();
if(this->TextDocument == NULL)
{
this->TextDocument = ui->Editor->document();
}
// get the current cursor
QTextCursor documentCursor = ui->Editor->textCursor();
documentCursor = this->TextDocument->find(searchTerm,documentCursor);
if(!documentCursor.isNull())
{
// needed only if you want the entire word to be selected
documentCursor.select(QTextCursor::WordUnderCursor);
// modify the text edit cursor
ui->Editor->setTextCursor(documentCursor);
}
else
{
ui->statusbar->showMessage(
"\""+searchTerm+"\" could not be found",MESSAGE_DURATION);
}
}
As a side note, you might want to know that QPlainTextEdit provides a find method, so this might be an easier way to achieve what you want:
void TextEditor::findNextInstanceOfSearchTerm()
{
QString searchTerm = this->edtFind->text();
bool found = ui->Editor->find(searchTerm);
if (found)
{
QTextCursor cursor = ui->Editor->textCursor();
cursor.select(QTextCursor::WordUnderCursor);
ui->Editor->setTextCursor(cursor);
}
else
{
// set message in status bar
}
}
Use QTextCursor::EndOfWord
Use QPlainTextEdit::setExtraSelections to select/highlight something in QPlainTextEdit
Simply you already have cursor that would highlight word, but you didn't apply it to text edit