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

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

Related

Stop QTextCursor::insertText() from modifying QTextDocument scrollbar range

I have a QTextEdit that contains a QTextDocument, which is being programatically edited using the QTextCursor interface. The document is being edited with QTextCursor::insertText().
I load the text file being edited in chunks, so the initial size of the QTextDocument might only be 20 lines even though the document is 100,000 lines. However, I want the QTextEdit scrollbar to reflect the full size of the document instead of just the 20 line document it's currently displaying.
The QTextEdit's scrollbar range is set with QScrollBar::setMaximum() which adjusts the scrollbar to the proper size on the initial opening of the file, but when QTextCursor::insertText() is called the QScrollBar's range is recalculated.
I've already tried calling QScrollBar::setMaximum() after each QTextCursor::insertText() event, but it just makes the whole UI jerky and sloppy.
Is there any way to keep the range of the QScrollBar while the QTextDocument is being modified?
Yes. You'd depend on the implementation detail. In QTextEditPrivate::init(), the following connection is made:
Q_Q(QTextEdit);
control = new QTextEditControl(q);
...
QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), q, SLOT(_q_adjustScrollbars()))
Here, q is of the type QTextEdit* and is the Q-pointer to the API object. Thus, you'd need to disconnect this connection, and manage the scroll bars on your own:
bool isBaseOf(const QByteArray &className, const QMetaObject *mo) {
while (mo) {
if (mo->className() == className)
return true;
mo = mo->superClass();
}
return false;
}
bool setScrollbarAdjustmentsEnabled(QTextEdit *ed, bool enable) {
QObject *control = {};
for (auto *ctl : ed->children()) {
if (isBaseOf("QWidgetTextControl", ctl->metaObject()) {
Q_ASSERT(!control);
control = ctl;
}
}
if (!control)
return false;
if (enable)
return QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), ed, SLOT(_q_adjustScrollbars()), Qt::UniqueConnection);
else
return QObject::disconnect(control, SIGNAL(documentSizeChanged(QSizeF)), ed, SLOT(_q_adjustScrollbars()));
}
Hopefully, this should be enough to prevent QTextEdit from interfering with you.

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.

How to remove focus from QLineEdit

I am developing a cpp/Qt tool.When I click on a QLineEdit field, its frame turns to a different color and the cursor starts blinking.When I type on Return in the field, I want its cursor to stop blinking and its frame color to default back to normal.I can intercept the Return Pressed, but when I then start the clearFocus() command, the keyboard input does not come anymore to the QLineEdit field (which is the behaviour I am expecting), but its frame doesn't go back to the default color and the cursor continues blinking. How to really remove focus from the field (i.e.: No cursor blinking anymore and frame back to default color) ?
=== EDIT ===
Here is the code:
void myQLineEditClass::keyPressEvent(QKeyEvent *e)
{
if(e->text().length()>0)
{
int asciiVal = e->text().at(0).toAscii();
if (asciiVal==3||asciiVal==13)
{
MGlobal::displayInfo(MQtUtil::toMString(QString().sprintf("Focus cleared"))); // -> this is properly displayed
clearFocus();
}
}
QLineEdit::keyPressEvent(e);
}
Thanks.

Clear ExtraSelections Qt on slot disconnect

I am implementing a focus mode in QT QTextEdit in which I am highlighting single line where cursor is present. So far I can enable focus mode but when I disable focus mode, i want the state restored back to what it was.
The function that calls connect and disconnect is:
void MainWindow::onFocus_Mode_triggered()
{
QTextEdit *texed = qobject_cast<QTextEdit*>(ui->tabWidget->currentWidget());
if(ui->actionFocus_Mode->isChecked()){
connect(texed, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
}
else {
disconnect(texed, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); //First disconnect and then call method to clear ExtraSelections
BacktoNormal(); //Help needed in implementing this
}
}
Now when the menu item actionFocus_Mode is checked, the line where the cursor is currently present is highlighted in yellow by the function given below.
void MainWindow::highlightCurrentLine() {
QTextEdit *texed = qobject_cast<QTextEdit*>(ui->tabWidget->currentWidget());
QList<QTextEdit::ExtraSelection> extraSelections;
QTextEdit::ExtraSelection selection;
QColor lineColor = QColor(Qt::yellow).lighter(160);
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = texed->textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
texed->setExtraSelections(extraSelections);
}
So I am able to highlight it in yellow but if(!ui->actionFocus_Mode->isChecked()), i.e, if menu item (focus mode) is unchecked then I wish to revert back to normal mode. How would I implement BacktoNormal() function.
What I think right now is that I should set lineColor to transparent or something to get it back to normal (if it is possible at all).
I am unable to find anything related to this. Any help would be useful as I am completely stuck at this point.
In your BackNormal try to set just nothing as extra selections.
QTextEdit *texed = qobject_cast<QTextEdit*>(ui->textEdit);
QList<QTextEdit::ExtraSelection> extraSelections;
QTextEdit::ExtraSelection selection;
QColor lineColor = QColor(Qt::yellow).lighter(160);
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = texed->textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
extraSelections.clear();//nothing
texed->setExtraSelections(extraSelections);
When did I try this on my computer(with another code), this selections was successfully removed.
Smaller version:
QTextEdit *texed = qobject_cast<QTextEdit*>(ui->textEdit);
QList<QTextEdit::ExtraSelection> extraSelections;//empty list
texed->setExtraSelections(extraSelections);

How to disable middle button functionality of QTextEdit?

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