How to change a parent widget's background when a child widget has focus? - qt

I would like to highlight a QFrame, if one of it's child widgets has focus (so the users know where to look for the cursor ;-)
using something along
ui->frame->setFocusPolicy(Qt::StrongFocus);
ui->frame->setStyleSheet("QFrame:focus {background-color: #FFFFCC;}");
highlights the QFrame when I click on it, but it loses its focus once one of its child widgets is selected.
Possible approaches:
I could connect() QApplication::focusChanged(old,now) and check each new object if it is a child of my QFrame, but this gets messy.
I could also subclass each child widget and reimplement focusInEvent()/focusOutEvent() and react on that, but with a lot of different widgets, this is also a lot of work.
Is there a more elegant solution?

Well, you can extend QFrame to make it listen on focus change of its children widgets.
Or you can also install an event filter on children widgets to catch QFocusEvent.
Here is an example:
MyFrame.h
#ifndef MYFRAME_H
#define MYFRAME_H
#include <QFrame>
class MyFrame : public QFrame
{
Q_OBJECT
public:
explicit MyFrame(QWidget* parent = 0, Qt::WindowFlags f = 0);
void hookChildrenWidgetsFocus();
protected:
bool eventFilter(QObject *object, QEvent *event);
private:
QString m_originalStyleSheet;
};
#endif // MYFRAME_H
MyFrame.cpp
#include <QEvent>
#include "MyFrame.h"
MyFrame::MyFrame(QWidget *parent, Qt::WindowFlags f)
: QFrame(parent, f)
{
m_originalStyleSheet = styleSheet();
}
void MyFrame::hookChildrenWidgetsFocus()
{
foreach (QObject *child, children()) {
if (child->isWidgetType()) {
child->installEventFilter(this);
}
}
}
bool MyFrame::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusIn) {
setStyleSheet("background-color: #FFFFCC;");
} else if (event->type() == QEvent::FocusOut) {
setStyleSheet(m_originalStyleSheet);
}
return QObject::eventFilter(object, event);
}
MainWindow.cpp
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include "MyFrame.h"
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setWindowTitle(tr("Test"));
MyFrame *frame1 = new MyFrame(this);
frame1->setLayout(new QVBoxLayout());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->hookChildrenWidgetsFocus();
MyFrame *frame2 = new MyFrame(this);
frame2->setLayout(new QVBoxLayout());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->hookChildrenWidgetsFocus();
QHBoxLayout *centralLayout = new QHBoxLayout();
centralLayout->addWidget(frame1);
centralLayout->addWidget(frame2);
QWidget *centralWidget = new QWidget();
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
}

I believe the both answers you were given are wrong. They work for simple cases but are extremely fragile and clumsy. I believe that the best solution is what you actually suggested in your question. I would go for connecting to QApplication::focusChanged(from, to). You simply connect your main frame object to this signal and in the slot you check if the to object (the one which received focus) is a child of your frame object.
Frame::Frame(...)
{
// ...
connect(qApp, &QApplication::focusChanged, this, &Frame::onFocusChanged);
// ...
}
// a private method of your Frame object
void Frame::onFocusChanged(QWidget *from, QWidget *to)
{
auto w = to;
while (w != nullptr && w != this)
w = w->parentWidget();
if (w == this) // a child (or self) is focused
setStylesheet(highlightedStylesheet);
else // something else is focused
setStylesheet(normalStylesheet);
}
The advantage is obvious. This code is short and clean. You connect only one signal-slot, you do not need to catch and handle events. It responds well to any layout changes done after the object is created. And if you want to optimize away unnecessary redrawing, you should cache the information whether any child is focused and change the stylesheet only and only if this cached value gets changed. Then the solution would be prefect.

First, create a simple subclass of QFrame which reimplements the eventFilter(QObject*, QEvent*) virtual function:
class MyFrame : public QFrame {
Q_OBJECT
public:
MyFrame(QWidget *parent = 0, Qt::WindowFlags f = 0);
~MyFrame();
virtual bool eventFilter(QObject *watched, QEvent *event);
};
Use MyFrame instead of QFrame to contain your widgets. Then, somewhere in your code where you create the widgets contained in MyFrame, install an event filter on those widgets:
// ...
m_myFrame = new MyFrame(parentWidget);
QVBoxLayout *layout = new QVBoxLayout(myFrame);
m_button = new QPushButton("Widget 1", myFrame);
layout->addWidget(m_button);
m_button->installEventFilter(myFrame);
//...
At that point, MyFrame::eventFilter() will be called before any event is delivered to the widget, letting you act on it before the widget is aware of it. Within MyFrame::eventFilter(), return true if you want to filter the event out (i.e. you don't want the widget to process the event), or return false otherwise.
bool MyFrame::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_button) { // An event occured on m_button
switch (event -> type()) {
case QEvent::FocusIn:
// Change the stylesheet of the frame
break;
case QEvent::FocusOut:
// Change the stylesheet back
break;
default:
break;
}
}
return false; // We always want the event to propagate, so always return false
}

Related

QLabel click event using Qt?

I'm new in Qt and have a question.
I have QLabel and QLineEdit objects, and when QLabel text is clicked on, I want to set this text in QLineEdit.
Also I have read that QLabel has not clicked signal.
Can you explain how can I do this and write code for me ?!
Either style another type of QWidget such as a specific QPushButton to look like a QLabel and use its clicked() signal or inherit QLabel yourself and emit your own clicked() signal.
See this example:
https://wiki.qt.io/Clickable_QLabel
If you choose the latter option you can pass the text in the signal. Then connect the necessary signals/slots up between the QLabel and the QLineEdit like so:
QObject::connect(&label, SIGNAL(clicked(const QString& text)),
&lineEdit, SLOT(setText(const QString& text)));
A simple way to accomplish that, without a need for any subclassing, is a signal source that monitors the events on some object and emits relevant signals:
// main.cpp - this is a single-file example
#include <QtWidgets>
class MouseButtonSignaler : public QObject {
Q_OBJECT
bool eventFilter(QObject * obj, QEvent * ev) Q_DECL_OVERRIDE {
if ((ev->type() == QEvent::MouseButtonPress
|| ev->type() == QEvent::MouseButtonRelease
|| ev->type() == QEvent::MouseButtonDblClick)
&& obj->isWidgetType())
emit mouseButtonEvent(static_cast<QWidget*>(obj),
static_cast<QMouseEvent*>(ev));
return false;
}
public:
Q_SIGNAL void mouseButtonEvent(QWidget *, QMouseEvent *);
MouseButtonSignaler(QObject * parent = 0) : QObject(parent) {}
void installOn(QWidget * widget) {
widget->installEventFilter(this);
}
};
The emit keyword is an empty macro, Qt defines it as follows:
#define emit
It is for use by humans as a documentation aid prefix only, the compiler and moc ignore it. As a documentation aid, it means: the following method call is a signal emission. The signals are simply methods whose implementation is generated for you by moc - that's why we have to #include "main.moc" below to include all the implementations that moc has generated for the object class(es) in this file. There's otherwise nothing special or magical to a signal. In this example, you could look in the build folder for a file called main.moc and see the implementation (definition) of void MouseButtonSignaler::mouseButtonEvent( .. ).
You can then install such a signaler on any number of widgets, such as a QLabel:
int main(int argc, char ** argv) {
QApplication app(argc, argv);
MouseButtonSignaler signaler;
QWidget w;
QVBoxLayout layout(&w);
QLabel label("text");
QLineEdit edit;
layout.addWidget(&label);
layout.addWidget(&edit);
signaler.installOn(&label);
QObject::connect(&signaler, &MouseButtonSignaler::mouseButtonEvent,
[&label, &edit](QWidget*, QMouseEvent * event) {
if (event->type() == QEvent::MouseButtonPress)
edit.setText(label.text());
});
w.show();
return app.exec();
}
#include "main.moc"
You need to create one Custom Label class, which will inherit QLabel. Then you can use MouseButtonRelease event to check clicking of Label and emit your custom signal and catch in one SLOT.
Your .h file will be as below:
class YourLabelClass : public QLabel{
signals:
void myLabelClicked(); // Signal to emit
public slots:
void slotLabelClicked(); // Slot which will consume signal
protected:
bool event(QEvent *myEvent); // This method will give all kind of events on Label Widget
};
In your .cpp file, your constructor will connect signal & slot as below :
YourLabelClass :: YourLabelClass(QWidget* parent) : QLabel(parent) {
connect(this, SIGNAL(myLabelClicked()), this, SLOT(slotLabelClicked()));
}
Remaining event method and SLOT method will be implemented as below:
bool YourLabelClass :: event(QEvent *myEvent)
{
switch(myEvent->type())
{
case(QEvent :: MouseButtonRelease): // Identify Mouse press Event
{
qDebug() << "Got Mouse Event";
emit myLabelClicked();
break;
}
}
return QWidget::event(myEvent);
}
void YourLabelClass :: slotLabelClicked() // Implementation of Slot which will consume signal
{
qDebug() << "Clicked Label";
}
For Changing a Text on QLineEdit, you need to create a Custom Class and share object pointer with custom QLabel Class. Please check test code at this link
In the above example the header needs Q_OBJECT:
class YourLabelClass : public QLabel{
Q_OBJECT
signals:

QWidget can we get/modify existing context menu of a widget

I have third party widget which provides me some context menu. Lets say cut, copy paste, select all.
Now I just want to modify only the paste functionality of the existing context menu. I know that I can implement whole context menu from scratch in the contextMenuEvent. But I do not want to do that as I am satisfied with other context menu actions, and just want to modify only the paste functionality.
I am using QT 4.8 on Mac OSX.
If such a thing is not possible at the moment can someone give me link/reference for that ? So that I can satisfy my stakeholders.
Edit: To be more clearer on what I am trying to do is, disable the paste context menu for some reason, and want to enable it later on depending on the situation/events.
I'm not sure that it can be done in a common way.
Here is a tricky solution:
In contextMenuEvent create a queued call to some slot:
QMetaObject::invokeMethod(this, "patchMenu", Qt::QueuedConnection);
Get visible windows in the slot and find QMenu. Get actions out of it and enable/disable them:
Q_SLOT patchMenu()
{
QWidgetList widgets = QApplication::topLevelWidgets();
foreach (QWidget* widget, widgets)
{
if (QMenu* menu = qobject_cast<QMenu*>(widget))
{
QList<QAction*> actions = menu->actions();
// here you can either get an action by index actions[5]
// or search the action by text
actions;
}
}
}
EDIT:
Here is a working example which demonstrates this approach:
window.h
#pragma once
#include <QtGui>
class Window: public QMainWindow
{
Q_OBJECT
public:
Window(QWidget *parent = 0);
};
class A : public QWidget
{
public:
virtual void contextMenuEvent(QContextMenuEvent* e);
};
class B : public A
{
Q_OBJECT;
public:
virtual void contextMenuEvent(QContextMenuEvent*);
Q_SLOT void patchMenu();
};
window.cpp
#include "window.h"
Window::Window(QWidget *parent) : QMainWindow(parent)
{
setCentralWidget(new B());
}
void B::patchMenu()
{
QWidgetList widgets = QApplication::topLevelWidgets();
foreach (QWidget* widget, widgets)
{
if (QMenu* menu = qobject_cast<QMenu*>(widget))
{
QList<QAction*> actions = menu->actions();
// here you can either get an action by index actions[5]
// or search the action by text
actions;
}
}
}
void B::contextMenuEvent(QContextMenuEvent* e)
{
QMetaObject::invokeMethod(this, "patchMenu", Qt::QueuedConnection);
A::contextMenuEvent(e);
}
void A::contextMenuEvent(QContextMenuEvent* e)
{
QMenu menu;
QAction* action = new QAction(QIcon(), "text", &menu);
menu.addAction(action);
menu.exec(e->globalPos());
}

Extend mousePressEvent() for dragging but retain previous behaviour for clicking

I'm trying to add the Drag and Drop functionality to a QListWidget. The first milestone was reached with the following code.
//draglist.h
#include <QListWidget>
class SourceList : public QListWidget
{
public:
SourceList(QWidget * parent = 0);
protected:
void mousePressEvent(QMouseEvent *event);
};
//draglist.cpp
#include "draglists.h"
#include <QMouseEvent>
#include <QDebug>
#include <QMimeData>
#include <QDrag>
SourceList::SourceList(QWidget * parent)
:QListWidget(parent)
{
}
void SourceList::mousePressEvent(QMouseEvent *event)
{
//Data from the selected QListWidgetItem
QString itemData = currentItem()->data(Qt::DisplayRole).toString();
QMimeData *mimeData = new QMimeData;
mimeData->setText(itemData);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->exec();
}
It enabled the drag functionality for the list items, but they can't be clicked any more.
How to retain the functionality in which one left click selects the item in the list (this is the behaviour if mousePressEvent() isn't overridden) ?
Possible solution
Check the source code for the original QAbstractItemView::mousePressEvent(QMouseEvent *event) and recopy the needed code int the SourceList::mousePressEvent(QMouseEvent *event) implementation. I'm looking for an alternative to that, if existent.
To preserve the default functionality you can propagate the event up the the parent class:
void SourceList::mousePressEvent(QMouseEvent *event)
{
// Your handling of mouse event.
[..]
QListWidget::mousePressEvent(event);
}
If you need to handle the left mouse button press, you can use QMouseEvent::button() function and compare returning value with one or more Qt::MouseButton values. For example:
if (event->button() & Qt::LeftButton) {
// Handle the left mouse button click.
}

Make a floating QDockWidget unfocusable

I'm creating a simple virtual keyboard in a QDockWidget...
When the widget is docked into the QMainWindow, the selected widget (for example a qdoublespinbox) is highlighted and if I click on the virtual keyboard clearFocus() works...
When the QDockWidget is floating above the window and I click a button, clearFocus doesn't work and I can't see the focused widget in QMainWindow...
How can I force the QDockWidget to not have any focus at all?
Thanks :-)
This is the code:
// class MyVirtualKeyboard : public QDockWidget
void MyVirtualKeyboard::sendKey(Qt::Key key, Qt::KeyboardModifier mod)
{
this->clearFocus();
QMainWindow *w = dynamic_cast<QMainWindow *>(this->parent());
if(w == NULL) return;
QWidget *widget = w->focusWidget();
QString repr = QKeySequence(key).toString();
QKeyEvent *pressEvent = new QKeyEvent(QEvent::KeyPress, key, mod, repr);
QKeyEvent *releaseEvent = new QKeyEvent(QEvent::KeyRelease, key, mod, repr);
qDebug("%s", pressEvent->text().toAscii().data());
MyApplication *app = MyApplication::myInstance();
app->postEvent(widget, pressEvent);
app->postEvent(widget, releaseEvent);
}
void MyVirtualKeyboard::on_BTN_1_clicked()
{
sendKey(Qt::Key_1);
}
...
The clearFocus() call should be unnecessary. Your dock widget and all of its widgets must have the Qt::NoFocus policy.
The code below shows how you might do it.
// https://github.com/KubaO/stackoverflown/tree/master/questions/vkb-focus-18558664
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class Keyboard : public QDockWidget {
Q_OBJECT
QWidget m_widget;
QGridLayout m_layout{&m_widget};
QToolButton m_buttons[10];
void sendKey(Qt::Key key, Qt::KeyboardModifier mod)
{
if (! parentWidget()) return;
auto target = parentWidget()->focusWidget();
if (! target) return;
auto repr = QKeySequence(key).toString();
auto pressEvent = new QKeyEvent(QEvent::KeyPress, key, mod, repr);
auto releaseEvent = new QKeyEvent(QEvent::KeyRelease, key, mod, repr);
qApp->postEvent(target, pressEvent);
qApp->postEvent(target, releaseEvent);
qDebug() << repr;
}
Q_SLOT void clicked() {
auto key = sender()->property("key");
if (key.isValid()) sendKey((Qt::Key)key.toInt(), Qt::NoModifier);
}
public:
explicit Keyboard(const QString & title, QWidget *parent = nullptr) : Keyboard(parent) {
setWindowTitle(title);
}
explicit Keyboard(QWidget *parent = nullptr) : QDockWidget(parent) {
int i{};
for (auto & btn : m_buttons) {
btn.setText(QString::number(i));
btn.setProperty("key", Qt::Key_0 + i);
m_layout.addWidget(&btn, 0, i, 1, 1);
connect(&btn, SIGNAL(clicked()), SLOT(clicked()));
btn.setFocusPolicy(Qt::NoFocus);
++i;
}
setWidget(&m_widget);
setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
}
};
int main(int argc, char ** argv)
{
QApplication a(argc, argv);
QMainWindow w;
w.setCentralWidget(new QLineEdit);
w.addDockWidget(Qt::TopDockWidgetArea, new Keyboard("Keyboard", &w));
w.show();
return a.exec();
}
#include "main.moc"
You can prevent a widget from taking focus by setting QWidget::focusPolicy = Qt::NoFocus.
However, there are two concepts here that you're mixing - the focused control (per window), and the active window (per desktop). I think in the scenario you're describing (a torn-off popup window), the OS window manager is likely to still change the active top-level window even if Qt doesn't set a focused control. That will result in nobody having keyboard focus (which is a valid state!).
So I think a full answer to your question will involve some non-portable bits. I don't know what GUI environment you're working in, but I know some of the answer for Win32, so I'll keep going and hope that's useful:
Win32
There's a pretty good discussion of the state tracking for Win32 on MSDN in the article Win32 Activation and Focus. I'm not aware that Qt does anything to wrap this level, so you'd have to use QWidget::nativeEvent or QCoreApplication::installNativeEventFilter to get at the low-level event. If you can subclass the window, I'd prefer the former, since it's more self-contained.
bool FooWidget::nativeEvent(const QByteArray & eventType, void * message, long * result)
{
#ifdef Q_OS_WIN
if(eventType == "windows_generic_MSG") {
const MSG *msg = reinterpret_cast<MSG *>(message);
if(msg->message == WM_MOUSEACTIVATE) {
*result = MA_NOACTIVATE;
return true;
}
}
#else
#error Need platform-specific code to suppress click-activation
#endif
return false;
}
This should block the click from activating the window (MA_NOACTIVATE), and block Qt from processing it further (return true), while leaving other all events (including the the click, since we didn't use MA_NOACTIVATEANDEAT to block it too) to be processed into QEvents and Qt signals normally (return false at the end).
If you need further low-level access (though I don't think you will), see also QWidget::effectiveWinId() and QWidget::windowHandle
Thanks a lot to Martin Gräßlin for the answer!
My recommendation: check out the virtual keyboard code in KDE Plasma: http://quickgit.kde.org/?p=kdeplasma-addons.git&a=blob&h=5628d6325afe57f85917dad865a07d4116335726&hb=a658d1e257cfca2a43c12714d026ec26f1fdb755&f=applets%2Fplasmaboard%2Fwidget.cpp
Looks like the key is setWindowFlags(Qt::X11BypassWindowManagerHint) and setFocusPolicy(Qt::NoFocus)
MyVirtualKeyboard::MyVirtualKeyboard(QWidget *parent) :
QDockWidget(parent),
ui(new Ui::MyVirtualKeyboard)
{
ui->setupUi(this);
this->connect(this, SIGNAL(topLevelChanged(bool)), this, SLOT(topLevelChanged()));
}
void MyVirtualKeyboard::topLevelChanged()
{
if(this->isWindow())
{
this->setWindowFlags(Qt::Popup | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
this->setFocusPolicy(Qt::NoFocus);
this->show();
}
}
I think I've found a better way to do it!
Just use this->setAttribute(Qt::WA_X11DoNotAcceptFocus); and voila!
Example:
MyVirtualKeyboard::MyVirtualKeyboard(QWidget *parent) :
QDockWidget(parent),
ui(new Ui::MyVirtualKeyboard)
{
ui->setupUi(this);
this->setAttribute(Qt::WA_X11DoNotAcceptFocus);
}

Need QGraphicsScene signal or event for _after_ change

I use QGraphicsScene of the Qt framework. Inside the scene I have some QGraphicsItems which the user can select and move.
I would like to have an info label where the current x and y coordinate of the currently moved selection (can consist of many items) is displayed.
I have tried with the signal changed of QGraphicsScene. But it is fired before the x() and y() property of the items is set to the new values. So the labels always show the second-to-last coordinates. If one moves the mouse slowly, the display is not very wrong. But with fast moves and sudden stops, the labels are wrong. I need a signal that is fired after the scene hast changed.
I have also tried to override the itemChange method of QGraphicsItem. But it is the same. It is fired before the change. (The new coordinates are inside the parameters of this method, but I need the new coordinates of all selected items at once)
I have also tried to override the mouseMove events of QGraphicsScene and of QGraphicsView but they, too, are before the new coordinates are set.
I did a test: I used a oneshot timer so that the labels are updated 100 ms after the signals. Then everything works fine. But a timer is no solution for me.
What can I do?
Make all items un-moveable and handle everything by my own?
QGraphicsItem::itemChange() is the correct approach, you were probably just checking the wrong flag. Something like this should work fine:
QVariant::myGraphicsItem( GraphicsItemChange change, const QVariant &value )
{
if( change == QGraphicsItem::ItemPositionHasChanged )
{
// ...
}
}
Note the use of QGraphicsItem::ItemPositionHasChanged rather than QGraphicsItem::ItemPositionChange, the former is called after the position changes rather than before.
The solution is to combine various things that you're already doing. Instrument itemChange, looking for and count the items with updated geometry. Once you've counted as many items as there are in the current selection, fire off a signal that will have everything ready for updating your status. Make sure you've set the QGraphicsItem::ItemSendsGeometryChanges flag on all your items!
This code was edited to remove the lag inherent in using a zero-timer approach. Below is a sscce that demonstrates it.
You create circles of random radius by clicking in the window. The selection is toggled with Ctrl-click or ⌘-click. When you move the items, a centroid diamond follows the centroid of the selected group. This gives a visual confirmation that the code does indeed work. When the selection is empty, the centroid is not displayed.
I've gratuitously added code to show how to leverage Qt's property system so that the items can be generic and leverage the notifier property of a scene if it has one. In its absence, the items simply don't notify, and that's it.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scenemod-11232425
#include <QtWidgets>
const char kNotifier[] = "notifier";
class Notifier : public QObject
{
Q_OBJECT
int m_count = {};
public:
int count() const { return m_count; }
void inc() { m_count ++; }
void notify() { m_count = {}; emit notification(); }
Q_SIGNAL void notification();
};
typedef QPointer<Notifier> NotifierPointer;
Q_DECLARE_METATYPE(NotifierPointer)
template <typename T> class NotifyingItem : public T
{
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override {
QVariant v;
if (change == T::ItemPositionHasChanged &&
this->scene() &&
(v=this->scene()->property(kNotifier)).isValid())
{
auto notifier = v.value<NotifierPointer>();
notifier->inc();
if (notifier->count() >= this->scene()->selectedItems().count()) {
notifier->notify();
}
}
return T::itemChange(change, value);
}
};
// Note that all you need to make Circle a notifying item is to derive from
// NotifyingItem<basetype>.
class Circle : public NotifyingItem<QGraphicsEllipseItem>
{
QBrush m_brush;
public:
Circle(const QPointF & c) : m_brush(Qt::lightGray) {
const qreal r = 10.0 + (50.0*qrand())/RAND_MAX;
setRect({-r, -r, 2.0*r, 2.0*r});
setPos(c);
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges);
setPen({Qt::red});
setBrush(m_brush);
}
};
class View : public QGraphicsView
{
Q_OBJECT
QGraphicsScene scene;
QGraphicsSimpleTextItem text;
QGraphicsRectItem centroid{-5, -5, 10, 10};
Notifier notifier;
int deltaCounter = {};
public:
explicit View(QWidget *parent = {});
protected:
Q_SLOT void gotUpdates();
void mousePressEvent(QMouseEvent *event) override;
};
View::View(QWidget *parent) : QGraphicsView(parent)
{
centroid.hide();
centroid.setRotation(45.0);
centroid.setPen({Qt::blue});
centroid.setZValue(2);
scene.addItem(&centroid);
text.setPos(5, 470);
text.setZValue(1);
scene.addItem(&text);
setRenderHint(QPainter::Antialiasing);
setScene(&scene);
setSceneRect(0,0,500,500);
scene.setProperty(kNotifier, QVariant::fromValue(NotifierPointer(&notifier)));
connect(&notifier, &Notifier::notification, this, &View::gotUpdates);
connect(&scene, &QGraphicsScene::selectionChanged, &notifier, &Notifier::notification);
}
void View::gotUpdates()
{
if (scene.selectedItems().isEmpty()) {
centroid.hide();
return;
}
centroid.show();
QPointF centroid;
qreal area = {};
for (auto item : scene.selectedItems()) {
const QRectF r = item->boundingRect();
const qreal a = r.width() * r.height();
centroid += item->pos() * a;
area += a;
}
if (area > 0) centroid /= area;
auto st = QStringLiteral("delta #%1 with %2 items, centroid at %3, %4")
.arg(deltaCounter++).arg(scene.selectedItems().count())
.arg(centroid.x(), 0, 'f', 1).arg(centroid.y(), 0, 'f', 1);
this->centroid.setPos(centroid);
text.setText(st);
}
void View::mousePressEvent(QMouseEvent *event)
{
const auto center = mapToScene(event->pos());
if (! scene.itemAt(center, {})) scene.addItem(new Circle{center});
QGraphicsView::mousePressEvent(event);
}
int main(int argc, char *argv[])
{
QApplication app{argc, argv};
View v;
v.show();
return app.exec();
}
#include "main.moc"

Resources