Qt how to move selection to one line up - qt

I have a trouble. Here my code is:
void TextEditor::moveToLineUp()
{
QTextCursor cur = textCursor();
if(cur.hasSelection()) {
int start = cur.selectionStart();
int end = cur.selectionEnd();
QTextBlock startBlock = document()->findBlock(start);
QTextBlock endBlock = document()->findBlock(end);
cur.setPosition(startBlock.position());
cur.setPosition(endBlock.position()+endBlock.length(), QTextCursor::KeepAnchor);
} else {
// I'll fill it later
}
// ??? I don't know how not to write remove action in undo stack
QString text = cur.selectedText();
cur.removeSelectedText();
QTextCursor ncur(cur.block().previous());
ncur.insertText(text);
}
When user select some text and click on button "Line up" this function is called.
Every line which has selection should move to one line up. But after that undo stack have to steps: remove text and paste text. What should I do? I want to make it simple operation in one step.

Related

Qt - How to count and measure the lines in a QTextDocument?

In one of my project, I created a QTextDocument which owns a text I need to draw. The text is a word wrapped HTML formatted text, and it should be drawn in a rectangle area, for which I know the width. Its content will also never exceed a paragraph.
The text document is created as follow:
// create and configure the text document to measure
QTextDocument textDoc;
textDoc.setHtml(text);
textDoc.setDocumentMargin(m_TextMargin);
textDoc.setDefaultFont(m_Font);
textDoc.setDefaultTextOption(m_TextOption);
textDoc.setTextWidth(m_Background.GetMessageWidth(size().width()));
and here is a sample text I want to draw:
Ceci est un texte <img src=\"Resources/1f601.svg\" width=\"24\" height=\"24\"> avec <img src=\"Resources/1f970.svg\" width=\"24\" height=\"24\"> une <img src=\"Resources/1f914.svg\" width=\"24\" height=\"24\"> dizaine <img src=\"Resources/1f469-1f3fe.svg\" width=\"24\" height=\"24\"> de <img src=\"Resources/1f3a8.svg\" width=\"24\" height=\"24\"> mots. Pour voir comment la vue réagit.
The images are SVG images get from the qml resources.
In order to perform several operations while the text is drawn, I need to know how many lines will be drawn, after the word wrapping is applied, and the height of any line in the word wrapped text.
I tried to search in the functions provided by the text document, as well as those provided in QTextBLock, QTextFragment and QTextCursor. I tried several approaches like iterate through the chars with a cursor and count the lines, or count each fragments in a block. Unfortunately none of them worked: All the functions always count 1 line, or just fail.
Here are some code sample I already tried, without success:
// FAILS - always return 1
int lineCount = textDoc.lineCount()
// FAILS - always return 1
int lineCount = textDoc.blockCount()
// FAILS - return the whole text height, not a particular line height at index
int lineHeight = int(textDoc.documentLayout()->blockBoundingRect(textDoc.findBlockByNumber(lineNb)).height());
// get the paragraph (there is only 1 paragraph in the item text document
QTextBlock textBlock = textDoc.findBlockByLineNumber(lineNb);
int blockCount = 0;
for (QTextBlock::iterator it = textBlock.begin(); it != textBlock.end(); ++it)
{
// FAILS - fragments aren't divided by line, e.g an image will generate a fragment
QString blockText = it.fragment().text();
++blockCount;
}
return blockCount;
QTextCursor cursor(&textDoc);
int lineCount = 0;
cursor.movePosition(QTextCursor::Start);
// FAILS - movePosition() always return false
while (cursor.movePosition(QTextCursor::Down))
++lineCount;
I cannot figure out what I'm doing wrong, and why all my approaches fail.
So my questions are:
How can I count the lines contained in my word wrapped document
How can I measure the height of a line in my word wrapped document
Are the text document function failing because of the html format? If yes, how should I do to reach my objectives in a such context?
NOTE I know how to measure the whole text height. However, as each line height may be different, I cannot just divide the whole text height by the lines, so this is not an acceptable solution for me.
I finally found a way to resolve my issue. The text document cannot be used directly to measure individual lines, the layout should be used for this purpose instead, as explained in the following post:
https://forum.qt.io/topic/113275/how-to-count-and-measure-the-lines-in-a-qtextdocument
So the following code is the solution:
//---------------------------------------------------------------------------
int getLineCount(const QString& text) const
{
// create and configure the text document to measure
QTextDocument textDoc;
textDoc.setHtml(text);
textDoc.setDocumentMargin(m_TextMargin);
textDoc.setDefaultFont(m_Font);
textDoc.setDefaultTextOption(m_TextOption);
textDoc.setTextWidth(m_Background.GetMessageWidth(size().width()));
// this line is required to force the document to create the layout, which will then be used
//to count the lines
textDoc.documentLayout();
// the document should at least contain one block
if (textDoc.blockCount() < 1)
return -1;
int lineCount = 0;
// iterate through document paragraphs (NOTE normally the message item should contain only 1 paragraph
for (QTextBlock it = textDoc.begin(); it != textDoc.end(); it = it.next())
{
// get the block layout
QTextLayout* pBlockLayout = it.layout();
// should always exist, otherwise error
if (!pBlockLayout)
return -1;
// count the block lines
lineCount += pBlockLayout->lineCount();
}
return lineCount;
}
//---------------------------------------------------------------------------
int measureLineHeight(const QString& text, int lineNb, int blockNb) const
{
// create and configure the text document to measure
QTextDocument textDoc;
textDoc.setHtml(text);
textDoc.setDocumentMargin(m_TextMargin);
textDoc.setDefaultFont(m_Font);
textDoc.setDefaultTextOption(m_TextOption);
textDoc.setTextWidth(m_Background.GetMessageWidth(size().width()));
// this line is required to force the document to create the layout, which will then be used
//to count the lines
textDoc.documentLayout();
// check if block number is out of bounds
if (blockNb >= textDoc.blockCount())
return -1;
// get text block and its layout
QTextBlock textBlock = textDoc.findBlockByNumber(blockNb);
QTextLayout* pLayout = textBlock.layout();
if (!pLayout)
return -1;
// check if line number is out of bounds
if (lineNb >= pLayout->lineCount())
return -1;
// get the line to measure
QTextLine textLine = pLayout->lineAt(lineNb);
return textLine.height();
}
//---------------------------------------------------------------------------

save cursor position of qtextedit

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

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

Dragged QGraphicsItem not visible in items() function

I have created a QGraphicsScene scene and added some graphcis items (lines, rectangles) etc to the scene.
I can loop through them using this list :
QList<QGraphicsItem*> all = items();
I enabled movement for these items and I am able to drag them by click selecting them. But after an element has been dragged, it stops showing up in the call to items() function of the QGraphicsScene.
QList<QGraphicsItem*> all = items();
None of the dragged items show up in the above list, while non-dragged ones do show up.
Does dragging the QGraphicScene elements change their parent ? or any other reason somebody could suggest for such an issue ?
{P.S. Code is too very big to share}
Edit 1 :
I am using the flags QGraphicsItem::ItemIsSelectable and QGraphicsItem::ItemIsMovable for making the items movable.
foreach(QGraphicsItem* itemInVisualScene, items())
{
itemInVisualScene->setFlag(QGraphicsItem::ItemIsSelectable, itemsMovable);
itemInVisualScene->setFlag(QGraphicsItem::ItemIsMovable, itemsMovable);
}
By default I add few rectangle to the scene. Then in the 'move mode' I drag them around. Then in the 'add mode' I click on screen to add new rectangles. I have written a logic to check if I am clicking on any existing drawn rectangle :
void Scene::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
if(eDrawLines == sceneMode)
{
dragBeginPoint = event->scenePos();
dragEndPoint = dragBeginPoint;
QList<QGraphicsItem*> all = items();
for (int i = 0; i < all.size(); i++)
{
QGraphicsItem *gi = all[i];
// Clicked point lies inside existing rect
if( QGraphicsRectItem::Type == gi->type() && gi->contains(dragBeginPoint))
{
std::cout << "Pressed inside existing rect" << std::endl;
return;
}
}
std::cout << "Point not found, add new rectangle" << std::endl;
}
QGraphicsScene::mousePressEvent(event);
}
This adding of rectangles work fine for rects which were not dragged in the 'move mode'. But rects which were moved do not seem to recognize the click anymore. My control comes out of the loop even when I click on an existing rectangle which was dragged earlier.
QGraphicsItem's transform is changed after dragging and therefore need to transform the point to item's local coordinates.
gi->contains(gi->mapFromScene(dragBeginPoint))
To convert or get item's position in scene coordinates, use
gi->mapToScene(0,0) or gi->scenePos()

How to display a tooltip only if the mouse cursor is within certain coordinates?

Within my component, I'm drawing some rectangles as below:
var objGraphics:Graphics=graphics;
objGraphics.drawRect(start, end, total, 5);
objGraphics.endFill();
I need to display a custom tooltip for each rectange when the mouse cursor is hovering over it.
How can I do this? I'm using the MouseMove event to track when the cursor moves over these coordinates (that part is working), but when I change the tooltip text it's not refreshing.
private function this_MOUSE_MOVE(event:MouseEvent):void
{
//...some code to check the coordinates to find out which rectangle the cursor
//is over
//current tooltip is "Rectangle A";
ToolTipManager.destroyToolTip(_myToolTip);
var localPoint:Point=new Point(event.localX, event.localY);
var globalPoint:Point=new Point(localToGlobal(localPoint).x,
localToGlobal(localPoint).y);
//cursor is over Rectangle B, so changing the tooltip;
_myToolTip=ToolTipManager.createToolTip("Rectangle B",
globalPoint.x, globalPoint.y) as ToolTip;
callLater(addChild, [_myToolTip]);
}
Thanks for your help.
EDIT: The problem seems to be with the following line:
ToolTipManager.destroyToolTip(_myToolTip);
If I comment out the preceding line, it will display the new tooltip, but it will keep creating new ones and the old ones never get removed. But if I add that line, it doesn't add any tooltips! Is the code not being executed sequentially, i.e., is the code to remove the tooltip somehow getting executed after the code to add the tooltip?
Assuming what you're adding to the stage, is called "myShape", you could do something like this:
// in your class...
private var var tooltip:Tooltip; // Or whatever your tooltip is
myShape.addEventListener(MouseEvent.MOUSE_OVER, handleOver);
myShape.addEventListener(MouseEvent.MOUSE_OUT, handleOut);
private function handleOver(evt:MouseEvent):void
{
// Show here
// OR
// tooltip = new Tooltip();
// addChild(tooltip);
}
private function handleOut(evt:MouseEvent):void
{
// Hide here
// OR
// removeChild(tooltip);
}
Hope this helps.

Resources