save cursor position of qtextedit - qt

setCurrentCharFormat() function does not take a current cursor position as a parameter. And so in order to set the char format for arbitrary text in the control I have to save the curent cursor position, set the char format and then restore it.
However, I don't see any thing like cursorPosition() in the docs.
Am I missing something?
Or maybe there is a better way of doing what I want?

I think you're looking for the QTextEdit::textCursor() method which returns a copy of the editor's QTextCursor. You can then manipulate the cursor as needed (including changing char. format and inserting text with specific format). If you need the cursor changes to persist (like the char. format), then make sure to QTextEdit::setCursor() afterwards.
A very basic example of inserting some text:
QTextCursor cursor(ui->textEdit->textCursor());
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor, 1);
cursor.insertText(text);
ADDED:
Perhaps the Qt Text Edit example will help. Specifically, in textedit.cpp where we see something like this:
void TextEdit::textBold()
{
QTextCharFormat fmt;
fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal);
mergeFormatOnWordOrSelection(fmt);
}
void TextEdit::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
QTextCursor cursor = textEdit->textCursor();
if (!cursor.hasSelection())
cursor.select(QTextCursor::WordUnderCursor);
cursor.mergeCharFormat(format);
textEdit->mergeCurrentCharFormat(format);
}
2nd ADDITION (based on comment):
So, if my cursor is at the line 3 col 10 and I call the function like this: SetCharFormat( 2, 5, attr ); it stores the current cursor position as (3, 10), then selects the text for text characters 2 and 5, set the text attribute for the selection, and then cursor will move back to the old position/selection.
Below is a specific example of what I think you're describing. One thing overall though, the text cursor position only has one dimension, which is, essentially, the number of visible characters from the start of the document. (The text cursor should not be confused with a QCursor which represents a mouse pointer with x,y coordinates.)
This simple test shows an editor with the text "I like this program." and a button. The button (or Alt-D) will toggle bold formatting on the word "like" while keeping the visible cursor position (and any selection) unchanged.
I've also included some sample code which moves the visible cursor initially, and in the formatting function there is a commented-out example of how to save and restore the cursor position programmatically. It is not needed in this particular example because the visible cursor is never modified.
#include <QtWidgets>
class Dialog : public QDialog
{
public:
Dialog(QWidget *parent = nullptr) : QDialog(parent)
{
QTextEdit *textEdit = new QTextEdit("I like this program.", this);
// Position cursor at end of sentence (just as an example)
QTextCursor cursor(textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
textEdit->setTextCursor(cursor); // required for the visible cursor to actually move
QToolButton *btnTest = new QToolButton(this);
btnTest->setText("&Do it");
btnTest->setCheckable(true);
connect(btnTest, &QToolButton::toggled, this, [textEdit, btnTest](bool checked)
{
// Function to toggle bold formatting on a section of text in the editor.
const int start = 2; // start of "like"
const int end = start + 4; // length of "like"
// the formatting to be applied
QTextCharFormat format;
format.setFontWeight(checked ? QFont::Bold : QFont::Normal);
format.setForeground(checked ? QBrush(Qt::red) : QPalette().text());
format.setBackground(checked ? QBrush(Qt::gray) : QPalette().base());
QTextCursor cursor(textEdit->textCursor()); // get a copy of the editor's cursor
// const int oldCursorPos = cursor.position(); // save cursor position (not needed for this example)
cursor.setPosition(start, QTextCursor::MoveAnchor); // move w/out selection
cursor.setPosition(end, QTextCursor::KeepAnchor); // move and select
cursor.mergeCharFormat(format); // apply format to selection
// cursor.setCharFormat(format); // alternative to mergeChatFormat()
// cursor.setPosition(oldCursorPos); // restore cursor position
// cursor.setPosition(end); // or move it to the end of the affected text
// textEdit->setTextCursor(cursor); // required for the visible cursor to move
btnTest->setText(checked ? "Un&do it" : "Re&do it");
});
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(textEdit);
layout->addWidget(btnTest);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
return Dialog().exec();
}

Related

Qt connect QScrollBar with QLineEdit

I would like to "activate" the scrollbar when the lineedit text is too long to display on the window. I have already done it.
I want to move the cursor with the scrollbar. I also want to modify the scroll bar slider length with the increment/decrement of the text length.
.h
private:
Ui::MainWindow *ui;
QLineEdit* LineEdit;
QScrollBar* hScrollBar;
void HDScrollBar();
constructor:
resize(400,100);
LineEdit = new QLineEdit(this);
LineEdit->resize(400,100);
LineEdit->setFont(QFont("Times",20));
hScrollBar = new QScrollBar(Qt::Horizontal, LineEdit);
hScrollBar->resize(400,20);
hScrollBar->move(0,80);
hScrollBar->hide();
connect(LineEdit, &QLineEdit::textChanged, this, &MainWindow::HDScrollBar);
hide/display scrollbar
void MainWindow::HDScrollBar() {
QFont myFont(QFont("Times",20));;
QString str = LineEdit->text();
QFontMetrics fm(myFont);
int width = fm.horizontalAdvance(str);
(width >= 400) ? hScrollBar->show() : hScrollBar->hide();
}
As for the first part, you use can scrollbar's valueChanged signal, e.g. this way:
connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, ui->lineEdit, [&](int v) {
auto scrollbarAt = static_cast<double>(v) / ui->horizontalScrollBar->maximum();
auto cursorTo = std::round(ui->lineEdit->text().length() * scrollbarAt);
ui->lineEdit->setCursorPosition(cursorTo);
});
EDIT:
as for the latter part:
you can either alter the PageStep of the scrollbar, or you can set it to 1 and alter its maximum value, then also the first part should simplify:
ui->horizontalScrollBar->setPageStep(1);
ui->horizontalScrollBar->setMaximum(ui->lineEdit.text().length());
connect(ui->horizontalScrollBar, &QScrollBar::valueChanged,
ui->lineEdit, &QLineEdit::setCursorPosition);
//this can also ben done on textChanged, however for the price
//of more frequent execution...
connect(ui->lineEdit, &QLineEdit::cursorPositionChanged,
ui->horizontalScrollBar, [&](int, int n) {
ui->horizontalScrollBar->setMaximum(ui->lineEdit->text().length());
//...one gets the easy way to track the cursor
//with the slider
ui->horizontalScrollBar->setValue(n);
});

QPlainTextEdit: different char format for part of text

after scanning several examples I didn't manage to solve this problem:
I created a test for this having a simple MainWindow with a QPlainTextEdit containing some text. On a trigger I want a part of the text to be red and underlined. But that never happens.
Regards
My code:
void MainWindow::on_actionTest_triggered()
{
QTextCursor cur = ui.plainTextEdit->textCursor();
cur.setPosition(49);
QTextCharFormat oldFormat = cur.charFormat();
QTextCharFormat newFormat = oldFormat;
newFormat.setForeground(Qt::darkRed);
newFormat.setUnderlineColor(Qt::darkRed);
newFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
newFormat.setFontUnderline(true);
cur.setCharFormat(newFormat);
cur.setPosition(cur.position()+11);
cur.setCharFormat(oldFormat);
ui.plainTextEdit->setTextCursor(cur);
}
using QTextEdit instead doesn't change anything)
This is your solution:
Using QTextCharFormat to set Underline and background.
void MainWindow::on_pushButton_clicked()
{
int begin = 30; //begin highlight
int end = 40; //end highlight
QTextCharFormat fmt;
fmt.setBackground(Qt::red); // set color red
fmt.setUnderlineStyle(QTextCharFormat::SingleUnderline); //set underline
QTextCursor cursor(ui->plainTextEdit->document());
cursor.setPosition(begin, QTextCursor::MoveAnchor);
cursor.setPosition(end, QTextCursor::KeepAnchor);
cursor.setCharFormat(fmt);
}

Can you hide a QGroupBox frame but preserve it's content visible?

I have a QGroupBox. Depending on the context, it's title may be redundent (displayed in another place of the GUI), so I then need to make as if the QGroupBox was not here....but I must preserve it's content visible (so I don't want to call QGroupBox::hide())!
I need to do this dynamically at runtime and would like to avoid creating/destroying the QGroupBox + reparenting it's content....there must be an easier way to do this.
What I tried so far:
QGroupBox visible:
QGroupBox::setTitle("") removes the text.
QGroupBox::setFlat(true) makes the frame be a single line.
I end up with this:
Not too bad...but a line remains....is there a way to completely hide the QGroupBox frame but preserve it's content visible?
My option:
QGroupBox theBox;
theBox.setFlat(true);
//This removes the border from a QGroupBox named "theBox".
theBox.setStyleSheet("QGroupBox#theBox {border:0;}");
//This removes the border from the group box and all of its children
theBox.setStyleSheet("border:0;");
You can derive your own Group Box from the QGroupBox and reimplement the paintEvent() method. It should be very simple. Original QGroupBox::paintEvent() looks like this:
void QGroupBox::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
What you need to do is just to modify the style option right before the widget is painted:
void CMyGroupBox::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
// This should disable frame painting.
option.features = QStyleOptionFrame::None;
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
You can use QFrame + QGridLayout (or some more complex combination of layouts) + QSS instead of a QGroupBox.
Considering a QGroupBox only, a trivial solution via QSS could be:
static const char kSavedTitle[] = "_savedTitle";
void hideBoxFrame(QGroupBox * box) {
box->setProperty(kSavedTitle, box->title());
box->setTitle(QString());
box->setStyleSheet("border:none");
}
void showBoxFrame(QGroupBox * box) {
box->setTitle(box->property(kSavedTitle).toString());
box->setStyleSheet(QString());
}
Here's an example that does it by swapping the widgets and reparenting the children. It works for any widget that has direct children, not only QGroupBox. It would require special case handling for widgets such as QScrollArea and QMainWindow that wrap children in a special sub-widget.
See this question for a related discussion of programmatically promoting widgets.
// https://github.com/KubaO/stackoverflown/tree/master/questions/group-reparent-36603051
#include <QtWidgets>
/// Replaces the visible widget with a hidden widget, preserving the layout of the
/// children, and making the new widget visible.
void swapWidgets(QWidget * a, QWidget * b)
{
auto src = a->isVisible() ? a : b;
auto dst = a->isVisible() ? b : a;
Q_ASSERT(dst->isHidden());
/// Move the children to the destination
dst->setLayout(src->layout());
/// Replace source with destination in the parent
auto layout = src->parentWidget()->layout();
delete layout->replaceWidget(src, dst);
/// Unparent the source, otherwise it won't be reinsertable into the parent.
src->setParent(nullptr);
/// Only the destination should be seen.
src->hide();
dst->show();
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QGridLayout wLayout{&w};
QPushButton swapBtn{"Swap"};
wLayout.addWidget(&swapBtn);
QWidget noBox;
QGroupBox box{"Group"};
wLayout.addWidget(&box);
QGridLayout boxLayout{&box};
for (int i = 0; i < 16; ++i)
boxLayout.addWidget(new QLabel(QString("Tr%1").arg(i)), i/8, i%8);
swapBtn.connect(&swapBtn, &QPushButton::clicked, [&] { swapWidgets(&box, &noBox); });
w.show();
return app.exec();
}
Yes there is a alternative that you can Try.
You can morph into a QFrame which will keep the behavior But make the container boundaryless
You can simply right click on the Group Box in the QDesigner and Select the 'Morph Into' option to select from

How to continue text editing after formatting changes of QGraphicsTextItem?

I am trying to make changes (font changes) to a QGraphicsTextItem that is editable.
I am trying to change formatting of fragments of text, or the formatting applied at typing point (if I set text bold, the text I type after that action at cursor position would be bold).
Setting formatting for text fragments works - but I can't find a way to return the focus correctly to the item.
I can show the caret at the right position, but I can't type in the box unless I actually click in box (even though it seems hat I should be able to).
Simple sample (for some reason it crashes on closing program but I don't care about that since I am testing the text class, not the main program):
header: mytextitem.h
#include <QGraphicsTextItem>
class MyTextItem : public QGraphicsTextItem
{
Q_OBJECT
public:
MyTextItem();
~MyTextItem() {}
public slots:
void setItemBold(const int b);
};
mytextitem.cpp
#include "mytextitem.h"
#include <QTextCursor>
MyTextItem::MyTextItem()
{
setPlainText("ABCD");
setFont(QFont("Arial", 20));
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
setTextInteractionFlags(Qt::TextEditorInteraction);
}
void MyTextItem::setItemBold(const int b)
{
int _weight = (b != 0) ? QFont::Bold : QFont::Normal;
QTextCursor _cursor = textCursor();
//int p = _cursor.position(); // this won't help
QTextCharFormat _format = _cursor.charFormat();
_format.setFontWeight(_weight);
_cursor.setCharFormat(_format);
//_cursor.setPosition(p, QTextCursor::KeepAnchor); // makes no difference on allowing me to type, but I can make the cursor move
//_cursor.movePosition(QTextCursor::NoMove, QTextCursor::KeepAnchor, 0); // makes no difference but I just thought some action might
setTextCursor(_cursor);
setFocus(Qt::MouseFocusReason);
// grabKeyboard(); // does nothing
}
main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGridLayout>
#include <QtWidgets>
#include <QCheckBox>
#include "mytextitem.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene(-20, -20, 150, 100);
QGraphicsView view(&scene);
QWidget widget;
QGridLayout layout(&widget);
layout.addWidget(&view, 0, 0);
QCheckBox bold("Bold");
layout.addWidget(&bold, 0, 1);
MyTextItem* item = new MyTextItem();
scene.addItem(item);
QObject::connect(&bold, SIGNAL(stateChanged(int)), item, SLOT(setItemBold(int)));
view.ensureVisible(scene.sceneRect());
widget.show();
return a.exec();
}
Editing the item is possible only if clicking in the box.
Assuming that I am already in the box (editing), and I push the "Bold" checkbox, I expect to be able to continue editing - type in the box - but even though I try to
set focus (which places the blinking text cursor in the box),
set position for cursor (I can move it, or select things... that works but I want to keep current position and selection)
grab keyboard - seems to do nothing
nothing seems to return me to the box so I continue typing (with the new font setting).
How can I get the QTextCursor or anything else to allow me to keep editing the text ?
You need to focus on QGraphicsView after format change. You can't focus on QGraphicsTextItem because it isn't QWidget.

QTextBrowser - how to identify image from mouse click position

I'm using a QTextBrowser to display rich text including a number of images, each of them specified with a HTML <img> tag and added as resources using QTextDocument::addResource().
What I'd like to be able to do is, in a context menu handler (i.e. with a mouse click position available), identify the image that the click was over. It's possible to tell whether the click is over an image, because cursorForPosition(event->pos()).block().text() returns a string starting with Unicode 0xFFFC. Unfortunately the same string is returned for every image in the view.
It's possible to get all of the formats in use with QTextDocument::allFormats(), identify which of those are image formats, and get their image resource name. Unfortunately there seems to be no way to get their actual display position or bounding rectangle.
From the documentation:
Inline images are represented by an object replacement character (0xFFFC in Unicode) which has an associated QTextImageFormat. The image format specifies a name with setName() that is used to locate the image.
You can use charFormat().toImageFormat().name() on the cursor to extract the image's URL. Below is a self-contained example. There are two noteworthy details:
The cursor will sometimes point one character prior to the image. Thus the workaround; it seems necessary for both Qt 4.8.5 and 5.1.1.
The pop-up menus should be shown asynchronously so as not to block the rest of the application. The example code provided in the documentation is a source of bad user experience and should be considered an evil abomination. All widgets can automatically delete themselves when they get closed, so the menus won't leak. A QPointer is used only to demonstrate this fact. It tracks the menu's lifetime and nulls itself when the menu deletes itself.
#include <QApplication>
#include <QTextBrowser>
#include <QImage>
#include <QPainter>
#include <QMenu>
#include <QContextMenuEvent>
#include <QTextBlock>
#include <QPointer>
#include <QDebug>
class Browser : public QTextBrowser
{
QPointer<QMenu> m_menu;
protected:
void contextMenuEvent(QContextMenuEvent *ev) {
Q_ASSERT(m_menu.isNull()); // make sure the menus aren't leaking
m_menu = createStandardContextMenu();
QTextCursor cur = cursorForPosition(ev->pos());
QTextCharFormat fmt = cur.charFormat();
qDebug() << "position in block" << cur.positionInBlock()
<< "object type" << cur.charFormat().objectType();
if (fmt.objectType() == QTextFormat::NoObject) {
// workaround, sometimes the cursor will point one object to the left of the image
cur.movePosition(QTextCursor::NextCharacter);
fmt = cur.charFormat();
}
if (fmt.isImageFormat()) {
QTextImageFormat ifmt = fmt.toImageFormat();
m_menu->addAction(QString("Image URL: %1").arg(ifmt.name()));
}
m_menu->move(ev->globalPos());
m_menu->setAttribute(Qt::WA_DeleteOnClose); // the menu won't leak
m_menu->show(); // show the menu asynchronously so as not to block the application
}
};
void addImage(QTextDocument * doc, const QString & url) {
QImage img(100, 100, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::white);
QPainter p(&img);
p.drawRect(0, 0, 99, 99);
p.drawText(img.rect(), url);
doc->addResource(QTextDocument::ImageResource, QUrl(url), img);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextDocument doc;
Browser browser;
doc.setHtml("<img src=\"data://image1\"/><br/><img src=\"data://image2\"/>");
addImage(&doc, "data://image1");
addImage(&doc, "data://image2");
browser.show();
browser.setDocument(&doc);
return a.exec();
}

Resources