I want to make a QGraphicsTextItem editable on double click, and make it movable when I click out.
#include <QApplication>
#include <QPainter>
#include <QGraphicsItem>
#include <QGraphicsView>
class TextItem: public QGraphicsTextItem
{
public:
TextItem()
{
setPlainText("hello world");
QFont f;
f.setPointSize(50);
f.setBold(true);
f.setFamily("Helvetica");
setFont(f);
setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsFocusable |
QGraphicsItem::ItemIsSelectable);
setTextInteractionFlags(Qt::NoTextInteraction);
}
virtual void paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget = NULL)
{
QGraphicsTextItem::paint(painter, option, widget);
}
protected:
virtual void focusOutEvent (QFocusEvent * event)
{
Q_UNUSED(event);
setTextInteractionFlags(Qt::NoTextInteraction);
}
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)
{
Q_UNUSED(event);
setTextInteractionFlags(Qt::TextEditable); // TextEditorInteraction
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TextItem* t = new TextItem();
QGraphicsView view(new QGraphicsScene(-200, -150, 400, 300) );
view.scene()->addItem(t);
view.show();
return a.exec();
}
It does what I want - except I have to double-click twice
- first time I double click, I see a cursor but am unable to edit text (with either option, TextEditable or TextEditorInteraction (I probably want the latter). Then I double-click again and I can type to add or delete text.
It is a behavior that a user probably doesn't expect - and nothing I do seems to change it.
Am I doing something wrong, or is there anything I need to add ?
I expected a mouse action on a focusable item to give it focus automatically. I guess not...
In the mouseDoubleClickEvent, I added a call to setFocus()
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)
{
Q_UNUSED(event);
setTextInteractionFlags(Qt::TextEditorInteraction);
setFocus();
}
Related
Is it possible to prevent right-click from opening the default context menu on QGraphicsTextItem ? The menu with "Undo, Redo, Cut, Copy, Paste..". On Ubuntu 18.04, that is. I don't know how this behaves on Windows.
I have overridden the mouse press handler to eat right-clicks in my view and tried to do that also in the item class itself. This actually did prevent the menu on Qt 5.10.0, but for some reason not anymore on 5.11.1:
void EditorView::mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::RightButton)
{
return;
}
...
doOtherHandlingStuff();
...
}
In the item itself it doesn't have any effect if I do this:
void TextEdit::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
event->ignore();
return;
}
You have to override the contextMenuEvent method of QGraphicsTextItem:
#include <QtWidgets>
class GraphicsTextItem: public QGraphicsTextItem
{
public:
using QGraphicsTextItem::QGraphicsTextItem;
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
{
event->ignore();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView w{&scene};
auto it = new GraphicsTextItem("Hello World");
it->setTextInteractionFlags(Qt::TextEditable);
scene.addItem(it);
w.show();
return a.exec();
}
In my project, I have a QMenu with a submenu item. The submenu has a lot of items so it's height is relatively large.
I want to vertically center the submenu relative to the item that executed the submenu.
I have already subclassed the submenu I want to reposition and tried changing the geometry on "aboutToShow" just to test things, but this has no effect:
class MySubMenu : public QMenu
{
Q_OBJECT
public:
QuickMod();
~QuickMod();
private slots:
void centerMenu();
};
MySubMenu::MySubMenu()
{
connect(this, SIGNAL(aboutToShow()), this, SLOT(centerMenu()));
}
MySubMenu::~MySubMenu()
{
}
void MySubMenu::centerMenu()
{
qDebug() << x() << y() << width() << height();
setGeometry(x(), y()-(height()/2), width(), height());
}
Here is an image I quickly MS Painted that I hope visually explains what I'm trying to achieve: (Before and After)
Thanks for your time!
aboutToShow is emited before the geometry is updated so the changes are overwritten later. The solution is to change the position an instant after they are displayed, for this we can use a QTimer with a small time.
Example:
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QTimer>
class CenterMenu: public QMenu{
Q_OBJECT
public:
CenterMenu(QWidget *parent = Q_NULLPTR):QMenu{parent}{
connect(this, &CenterMenu::aboutToShow, this, &CenterMenu::centerMenu);
}
CenterMenu(const QString &title, QWidget *parent = Q_NULLPTR): QMenu{title, parent}{
connect(this, &CenterMenu::aboutToShow, this, &CenterMenu::centerMenu);
}
private slots:
void centerMenu(){
QTimer::singleShot(0, [this](){
move(pos() + QPoint(0, -height()/2));
});
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
auto fileMenu = new QMenu("Menu1");
w.menuBar()->addMenu(fileMenu);
fileMenu->addAction("action1");
fileMenu->addAction("action2");
auto children_menu = new CenterMenu("children menu");
children_menu->addAction("action1");
children_menu->addAction("action2");
children_menu->addAction("action3");
children_menu->addAction("action4");
children_menu->addAction("action5");
children_menu->addAction("action6");
fileMenu->addMenu(children_menu);
w.show();
return a.exec();
}
#include "main.moc"
//oneLed.h
#pragma once
#include<QPushButton>
class oneLed :public QPushButton
{
Q_OBJECT
public:
oneLed(QWidget* parent = 0);
protected:
void doPainting();
};
#include"oneLed.h"
#include<QPainter>
oneLed::oneLed(QWidget* parent)
:QPushButton(parent)
{
connect(this, &QPushButton::clicked, this, &oneLed::doPainting);
}
void oneLed::doPainting()
{
QPainter painter(this);
//painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(QBrush("#888"), 1));
painter.setBrush(QBrush(QColor("#888")));
painter.drawEllipse(0, 0, this->width(), this->height());
//painter.drawEllipse(0, 0, 30, 30);
}
//main.cpp
#include"oneLed.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
oneLed w;
w.resize(100, 500);
w.show();
return a.exec();
}
I want to achieve the following effect:
When I clicked on the oneLed object, A circle appears at the position of the oneled object. When I click on the oneLed object again, the circle disappears.
But in fact when I click on the oneLed object, the circle doesn't appear.
I guess you got it wrong. What happens in your code is:
the button is clicked and your doPainting slot is called
you do your custom painting
the actual button paint event is triggered by Qt main event loop and overwrites your painting
You need to override the paintEvent method.
In your custom slot, raise a boolean flag that indicates the button has been pressed.
void oneLed::slotClicked()
{
m_clicked = !m_clicked;
}
Then do something like this:
void oneLed::paintEvent(QPaintEvent *event)
{
// first render the Qt button
QPushButton::paintEvent(event);
// afterward, do custom painting over it
if (m_clicked)
{
QPainter painter(this);
painter.setPen(QPen(QBrush("#888"), 1));
painter.setBrush(QBrush(QColor("#888")));
painter.drawEllipse(0, 0, this->width(), this->height());
}
}
The method that you implement is paintEvent, in the slot that doPainting you must change a flag and call the update() method.
Important: The update method calls paintEvent.
oneLed.h
#ifndef ONELED_H
#define ONELED_H
#include <QPushButton>
class oneLed : public QPushButton
{
Q_OBJECT
public:
oneLed(QWidget* parent = 0);
protected:
void paintEvent(QPaintEvent * event);
private slots:
void doPainting();
private:
bool state;
};
#endif // ONELED_H
oneLed.cpp
#include "oneled.h"
#include <QPainter>
oneLed::oneLed(QWidget *parent):QPushButton(parent)
{
state = false;
connect(this, &QPushButton::clicked, this, &oneLed::doPainting);
}
void oneLed::paintEvent(QPaintEvent *event)
{
QPushButton::paintEvent(event);
if(state){
QPainter painter(this);
//painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(QBrush("#888"), 1));
painter.setBrush(QBrush(QColor("#888")));
painter.drawEllipse(0, 0, width(), height());
}
}
void oneLed::doPainting()
{
state = !state;
update();
}
I have QGraphicsView, QGraphicsScene and QGraphicsRectItem.
QGraphicsRectItem in the QGraphicsScene and the last one in the QGraphicsView. I want to move QGraphicsRectItem with mouse by clicking on it only! But in my implementation it moves if I click on any position on my QGraphicsScene. Whether it is my QGraphicsRectItem or some other place. And the second issue. The item has been moved to the center of the scene. Clicking on it again it starts to move from the home location.
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset); //p1 movable item
}
}
What do I do wrong?
UPDATE:
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
widget.h
#ifndef STEER_H
#define STEER_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QPoint>
#include <QGraphicsRectItem>
class Steer : public QGraphicsView
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint offset;
public:
explicit Steer(QGraphicsView *parent = 0);
~Steer(){}
public slots:
void mousePressEvent(QMouseEvent * click);
void mouseMoveEvent(QMouseEvent * event);
};
#endif // STEER_H
widget.cpp
#include "widget.h"
#include <QBrush>
Steer::Steer(QGraphicsView *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(760, 160, 10, 80);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset);
}
}
I'd try a different approach that is a little easier to understand:
#include <QtWidgets>
class Steer : public QGraphicsView
{
public:
Steer()
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(0, 0, 10, 80);
p1->setX(760);
p1->setY(160);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
protected:
void mousePressEvent(QMouseEvent * click)
{
if (p1->contains(p1->mapFromScene(click->localPos()))) {
lastMousePos = click->pos();
} else {
lastMousePos = QPoint(-1, -1);
}
}
void mouseMoveEvent(QMouseEvent * event)
{
if(!(event->buttons() & Qt::LeftButton)) {
return;
}
if (lastMousePos == QPoint(-1, -1)) {
return;
}
p1->setPos(p1->pos() + (event->localPos() - lastMousePos));
lastMousePos = event->pos();
}
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint lastMousePos;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
There are a few things to point out here:
Don't use setRect() to set the position of a QGraphicsRectItem. It doesn't work the way you think it might. Always use setPos() to change the position of an item.
Rename offset to something more descriptive. I chose lastMousePos. Instead of just updating it once when the mouse is pressed, also update it whenever the mouse is moved. Then, it's simply a matter of getting the difference between the two points and adding that to the position of the item.
Check if the mouse is actually over the item before reacting to move events. If the mouse isn't over the item, you need some way of knowing that, hence the QPoint(-1, -1). You may want to use a separate boolean flag for this purpose. This solves the problem that you saw, where it was possible to click anywhere in the scene to get the item to move.
Also, note the mapFromScene() call: the contains() function works in local coordinates, so we must map the mouse position which is in scene coordinates before testing if it's over the item.
The event functions are not slots, they're virtual, protected functions.
You could also consider handling these events in the items themselves. You don't need to do it from within QGraphicsView, especially if you have more than one of these items that need to be dragged with the mouse.
What I'm trying to do is pretty simple, when the mouse is over the qgraphicsitem I want it to change it's text value. Later on I want to use this to pop-up text when I click an image (i.e. the info of the image)
Here's my code so far:
#include <QtGui/QApplication>
#include <QtGui/QGraphicsItem>
#include <QtGui/QGraphicsTextItem>
#include <QtGui/QGraphicsScene>
#include <QtGui/QGraphicsView>
#include <QtGui/QPixmap>
int main( int argc, char * * argv )
{
QApplication app( argc, argv );
QGraphicsScene scene;
QGraphicsView view( &scene );
QGraphicsTextItem text( "this is my text");
scene.addItem(&text);
scene.setActivePanel(&text);
text.setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
text.setAcceptHoverEvents(true);
text.setAcceptTouchEvents(true);
if (text.isUnderMouse() || text.isSelected()){
text.setPlainText("test");
}
view.show();
return( app.exec() );
}
Some people use double-click events but I was hoping not to use them, but... if that's the only way to get the job done then it's ok.
This code block:
if (text.isUnderMouse() || text.isSelected()){
text.setPlainText("test");
}
is run exactly once, before your view is even shown; so this has absolutely no chance of doing what you expect it to.
You'll need to do a bit more work for that, namely create a custom subclass of QGraphicsTextItem and override the appropriate event handlers.
Here's how you could do that to handle changing text when hovering:
class MyTextItem: public QGraphicsTextItem
{
public:
MyTextItem(QString const& normal, QString const& hover,
QGraphicsItem *parent=0)
: QGraphicsTextItem(normal, parent), normal(normal), hover(hover)
{
}
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
setPlainText(hover);
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
setPlainText(normal);
}
private:
QString normal, hover;
};
Add that to your code, and change the text declaration to:
MyTextItem text("this is my text", "test");
and it should do what you expect.