QTextBrowser - how to identify image from mouse click position - qt

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

Related

Would that be possible to build a particular customized QPushButton?

I was trying to understand if there is a way to build a particular customized QPushButton?
What I am trying to achieve is the following layout and appearance:
The button is shown below, notice the red line (which meas that the button is not clicked). I am not sure how to achieve the red line. I think it could be widget? or a QProgressbar, that when is clicked goes/loads up to green..I am not sure because I don't have enough experience and have been trying to build it. However this seems to be a bit tough:
And below how it should look like right after the click happened (note the green line):
Despite my efforts, I found some useful sources that I could use to get me started: for example this source was great to understand how to start. I studied the fact that in order to achieve that, the button need to be subclassed, and that is great because it lays some sort of route.
Below the code I used:
custombutton.h
#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H
#include <QPushButton>
class CustomButton : public QPushButton
{
public:
CustomButton( const QString& text, QWidget* parent = 0 );
void writeText();
};
#endif // CUSTOMBUTTON_H
custombutton.cpp
#include "CustomButton.h"
#include "algorithm"
CustomButton::CustomButton( const QString& text, QWidget* parent )
: QPushButton( text, parent )
{
}
void CustomButton::writeText()
{
QString buttonText = text();
setText( buttonText );
}
main
#include <QApplication>
#include "CustomButton.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomButton w( "MyButton" );
w.show();
w.writeText();
a.exec();
return 0;
}
Another useful source I found is this one which also was useful.
The official documentation points to use the styles, but I am trying not to do that because I would like to solve the problem understanding what is the potential of subclassing with Qt.
Unless going in the style direction is the only possible way to solve this problem?
I would like to thank anyone in advance for sharing or pointing to a potential solution on how to do that.
You can set your button as checkable and then set a different icon for the 2 states.
In your case you'd have to set the red icon for the Normal mode and the green one for the Selected mode
Here's an example:
https://www.dropbox.com/s/x40byuyu2ph8m1y/CheckableButton.zip?dl=0
Here someone asked the same thing:
https://forum.qt.io/topic/72363/change-icon-of-pushbutton
Here you can read abouth the modes:
https://doc.qt.io/qt-5/qicon.html#Mode-enum
PS: Of course overriding QAbstractButton::paintEvent(QPaintEvent *event) is a viable option too

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.

How can I get my caret to show up on first double-click action on a QGraphicsTextItem?

I am struggling to get a QGraphicsTextItem to work as a user friendly object.
Since it is very hard to move while being editable, I start it as not editable, and make it editable on double-click. Then turn editing off on losing focus.
My problem is, the caret does not show up on first edit.
I have tried getting the position based on mouse position (as in this question that was trying to solve a different problem), or calling the QGraphicsTextItem::mouseDoubleClickEvent(event);
No matter what I try, the caret is invisible on first action - until I start typing (or if I focus out and back in) - even though it is at the correct location.
After typing, or unselecting and reselecting , the caret shows up in normal location every time.
I have tried to call the QTextCursor in the item constructor, setting its position at 0, made no difference.
What made a difference : one of the 2 situations (neither of which I can do though):
a) start item with Qt::TextEditorInteraction in constructor
b) start item with no moving/focus/selectable flags
I can't do either - because my default state of item must be movable, and that interferes with text editing (as explained at start).
I have tried to disable those flags during editing though... with no effect.
Here is a simple code to demonstrate the problem, I hope somebody can have an idea.
mytextitem.h
#ifndef TEXTITEM_H
#define TEXTITEM_H
#include <QGraphicsTextItem>
class MyTextItem : public QGraphicsTextItem
{
Q_OBJECT
public:
MyTextItem();
protected:
virtual void focusOutEvent (QFocusEvent * event);
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event);
};
#endif // TEXTITEM_H
mytextitem.cpp
#include "mytextitem.h"
#include <QTextCursor>
#include <QAbstractTextDocumentLayout>
#include <QGraphicsSceneMouseEvent>
#include <QFont>
MyTextItem::MyTextItem()
{
setHtml("ABCD");
setFont(QFont("Arial", 50));
setTextInteractionFlags(Qt::NoTextInteraction);
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
}
void MyTextItem::focusOutEvent(QFocusEvent *event)
{
Q_UNUSED(event);
setTextInteractionFlags(Qt::NoTextInteraction);
QTextCursor _cursor = textCursor();
_cursor.clearSelection();
setTextCursor(_cursor);
}
void MyTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
setTextInteractionFlags(Qt::TextEditorInteraction);
QGraphicsTextItem::mouseDoubleClickEvent(event); // or the version in linked question
}
main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene s;
QGraphicsView view(&s);
s.setSceneRect(-20, -100, 800, 600);
view.show();
MyTextItem* t = new MyTextItem();
s.addItem(t);
return app.exec();
}
I have also considered editing text - not sure if that would work but I think it would affect the undo stack which I will have to deal with soon....
How can I get my caret to show up on first double-click action on the text item ?
(As a user, not seeing a caret would make me uncertain if I can type... even though it works... I would not have confidence in the object if I do not have feedback of my action. That's why I care about this problem.)
I can't explain it... after trying EVERYTHING to get the caret to show, the solution was so simple:
I had to change the order of flags being set, in constructor.
The QGraphicsTextItem flag must be set AFTER setting the QGraphicsItem flags.
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
setTextInteractionFlags(Qt::NoTextInteraction);

QWidget::geometry() vs. QWidget::frameGeometry()

Although Qt's docs indicate that these two functions are different (the first doesn't include the frame) no matter what widget I choose - including the main window of my application - someWidget->frameGeometry().height() always returns the same value as someWidget->geometry.height().
What am I missing here?
I think, you don't give enough time to widget to be painted. There is little example:
#include <QApplication>
#include <QMainWindow>
#include <QDebug>
class MainWindow : public QMainWindow
{
public:
MainWindow() {
startTimer(500);
}
void timerEvent(QTimerEvent *e) {
// Here values are different
qDebug() << geometry().height() << frameGeometry().height();
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
// Here values are equals
qDebug() << mainWin.geometry().height() << mainWin.frameGeometry().height();
return app.exec();
}
First debug output will produce the same values for geometry and frameGeometry, but the second (in timerEvent) will produce different.
The QWidget class cannot have a frame. For example, QWidget doesn't have a frame, but QFrame has a frame.
if QWidget is toplevel window then you can see borders and title bar around it. We call it frame or decoration frame and frameGeometry() returns exactly that: window size and position which includes OS decorations.On the other side geometry() returs QWidget inner rect which is available for other child controls or painting.See http://doc.qt.io/qt-4.8/application-windows.html#window-geometry for more details.Toplevel geometry() / frameGeometry() differs if our window is not frameless or fullscreen ... or we are talking about some frameless window manager under x11.
this is an old post, but that could help those searching for the same problem.
Just call
adjustSize();
before prompting for some geometry attributes
As user fasked notes, frameGeometry() may not include the frame margins early in the window creation lifecycle. I have found that the following code works in some situations where calling frameGeometry() does not.
QMargins frameMargins;
QWindow *window = widget->windowHandle();
if (window) {
window->create();
frameMargins = window->frameMargins();
}
QRect myFrameGeometry = widget->geometry().adjusted(
-frameMargins.left(), -frameMargins.top(),
frameMargins.right(), frameMargins.bottom());

How to find global position of text cursor?

I would like to execute a QMenu object at the position of text cursor in a QPlainTextEdit. My problem is that QTextCursor is only define by its position in the Text (index of the character).
How can I find global position of the QTextCursor? Should I use an other object than QTextCursor in order to find the position of the text cursor where I want to open my QMenu?
Thank you by advance.
I've never tried myself, but doesn't QPlainTextEdit::cursorRect() work? It should give you position of the cursor in viewport coordinates. You can then get the viewport using viewport() and map the local position to global using viewport()->mapToGlobal().
I have found similar query to your in some online forum and here's someone suggested the output as
Note: Reference from http://www.unix.com/unix-linux-applications/81388-read-position-mouse-cursor.html, Author of below posting is daggilli, registered user of UNIX online forums. Credit of below posting in its complete form goes to daggilli.
This is the complete code for a Qt application I threw together in about ten minutes (called crosshair) which displays the current mouse coordinates in a window. You might be able to pull enough out of it to be useful. This is Qt 3.1, but Qt 4 is not a great deal different. You will need the Qt development libraries, not just the runtimes. The code comprises two files, crosshair.h and crosshair.cpp.
crosshair.h:
Code:
#ifndef CROSSHAIR_H
#define CROSSHAIR_H
#include <qwidget.h>
#include <qstring.h>
#include <qlabel.h>
#include <qevent.h>
class Crosshair : public QLabel
{
Q_OBJECT
public:
Crosshair(QWidget *parent=0);
protected:
void mousePressEvent(QMouseEvent *);
private:
QTimer *timer;
private slots:
void timerfire();
};
#endif
crosshair.cpp:
Code:
#include <qapplication.h>
#include <qpushbutton.h>
#include <qtimer.h>
#include <qcursor.h>
#include <iostream>
#include "crosshair.h"
using namespace std;
int main(int argc,char **argv)
{
QApplication a(argc,argv);
Crosshair mousepos;
a.setMainWidget(&mousepos);
mousepos.show();
return a.exec();
}
Crosshair::Crosshair(QWidget *parent) : QLabel(parent)
{
setIndent(20);
resize(100,30);
move(1200,200);
setText("0,0");
timer=new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(timerfire()));
timer->start(50,false);
}
void Crosshair::mousePressEvent(QMouseEvent *)
{
qApp->quit();
}
void Crosshair::timerfire()
{
QPoint p=QCursor::pos();
this->setText(QString().sprintf("%d,%d",p.x(),p.y()));
}
To build this, put both files in a directory called crosshair. cd to that directory and type
Code:
qmake -project
qmake
make
This does nothing more complex than inherit from a QLabel, set a timer to run 20x a second, grab the current cursor coordinates and write them into the label's text. Clicking in the window closes it. I use it for fixing up alignment bugs in JavaScript when I'm laying out objects.
You could open a file in the Crosshair class's constructor to store your data, and use gettimeofday(2) to get a timestamp. Nothing says Qt has to run in GUI mode (you can tell it explicitly not to in the QApplication constructor).
Qt from Trolltech: http://doc.trolltech.com

Resources