stop event propagation from QGraphicsItem to QGraphicsScene QT - c++

I have an Qgraphicsscene implemented as a class, then i use QGraphicsScene::mousePressEvent to add a QGraphicsRectItem, this item have also an implementation for QGraphicsRectItem::mousePressEvent, the problem is that the event in the rect item is propagated to the scene and when i click it is added a new rect item, but i want is that the events inside this item do not propagate to the scene, i try event->accept but the event is propagated, how i cant do that? thanks for any help.
here is my qgraphicsscene code:
#include "imageview.h"
ImageView::ImageView(QWidget *parent){
scene = new ImageScene(this);
setScene(scene);
//this->setMouseTracking(true);
this->setInteractive(true);
}
ImageScene::ImageScene(QWidget *parent){
current = NULL;
selection = new QRubberBand(QRubberBand::Rectangle,parent);
selection->setGeometry(QRect(10,10,20,20));
setSceneRect(0,0,500,500);
}
void ImageScene::mousePressEvent(QGraphicsSceneMouseEvent *event){
QGraphicsScene::mousePressEvent(event);
/*IGNORING THIS EVENT FROM QGRAPHICSRECTITEM*/
cout<<"image view"<<endl;
if(this->selectedItems().length() == 0){ /*WORKS BUT IN SOME IMPLEMENTATION IS A PROBLEM (WHEN I DELETE THE ITEM WITH A DELETE BUTTON THE EVENT IS FIRED AND ADD A NEW ITEM .)*/
origin = event->scenePos();
selection->show();
}
}
void ImageScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
if(selection->isVisible() && selection->rect().width() >= 20 && selection->rect().height() >= 20){
QGraphicsScene::mouseReleaseEvent(event);
ResizableRect * rselection = new ResizableRect();
//selection->origin = event->scenePos();
//selection->grabMouse();
cout<<"add"<<endl;
this->addItem(rselection);
rselection->setPos(selection->pos());
rselection->setRect(0,0,selection->rect().width(),selection->rect().height());
}
selection->hide();
}
void ImageScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
QGraphicsScene::mouseMoveEvent(event);
if(selection->isVisible()){
QPoint rorigin(origin.x(),origin.y());
int xdes = event->scenePos().x();
int ydes = event->scenePos().y();
xdes = xdes > 0? xdes:0;
ydes = ydes > 0? ydes:0;
xdes = xdes < this->width()?xdes:this->width();
ydes = ydes < this->height()?ydes:this->height();
QPoint rdest(xdes,ydes);
selection->setGeometry(QRect(rorigin,rdest).normalized());
}
}

Opposite to QWidgets, QGraphicsScene catches events before child items. It is described on Qt documentation.
For correctly work with that, use reimplementation of QGraphicsView instead of QGraphcisScene.
Reimplement mousePressEvent there.
At that moment you can determine item under mouse pointer.
it it is there - you can just call QGraphicsView::mousePressEvent();
It it is not - use your implementation for add new item.
It also allows you to separate behavior of different views.

Related

QListWidgetItem is not drawn correctly

I build up a ruse of CustomWidgets in a QListWidget. So far, it works well.
A problem occurs when I move the vertical scroll bar downwards and want to display items that were not previously visible.
Then they are inserted as transparent rectangles, I can click them and respond to the clicks, but they are not drawn. They stay transparent!
I insert a picture to get a rough idea of the problem:
In my QListWidget I try to repaint it, but it doesn't work:
QListWidget::verticalScrollbarValueChanged(value);
auto item = this->itemAt(QPoint(24, value));
if (!item)
{
return;
}
auto widget = this->itemWidget(item);
if (!widget)
{
return;
}
// widget->resize(widget->size());
// widget->repaint();
widget->update();
What can I do? Thanks for help!
EDIT:
According to the request of Martin, I insert here a routine, which shows the addition of items:
void ListControl::AddCustomWidget(QWidget* customWidget, const QSize& size, bool forceSize)
{
if (forceSize)
{
customWidget->adjustSize();
}
auto displaySize = customWidget->size();
auto width = size.width();
auto height = size.height();
auto item = new QListWidgetItem(this);
this->addItem(item);
if (width >= 0)
{
displaySize.setWidth(width);
}
else
{
displaySize.setWidth(displaySize.width() - (this->verticalScrollBar()->width() + this->rightSpace));
}
if (height >= 0)
{
displaySize.setHeight(height);
}
item->setSizeHint(displaySize);
this->setItemWidget(item, customWidget);
}

QT QGraphicsView rotation

Disclaimer: I am pretty much a beginner with QT.
I've been struggling for some time to rotate a QGraphicsView (no 3D rotation) but, despite what i do, it doesn't work. I have tried:
QTransform transform;
transform.rotate(45);
ui->graphicsView->setTransform(transform);
or more simply:
ui->graphicsView->rotate(45);
These seem like very straightforward ways to do it that should work, but for some reason, whenever i run it, the QGraphicsView doesn't rotate at all. If possible, i'd like some direct and easy to understand code snippets, and/or what i'm doing wrong.
EDIT: This is the code in the widget cpp file i have problems with. It should be a simple timer with an animated hourglass icon. It gets repeated every .5 seconds.
void Widget::timerEvent(QTimerEvent *event)
{
++timeFlag;
++timerFlag;
if (timerFlag < 115){
animateTimer = QString("\":/new/100/timerFrames/timerIconFrame%1.png\"").arg(timerFlag);
QPixmap pix(animateTimer);
pixmapitem.setPixmap(pix);
scene.addItem(&pixmapitem);
ui->graphicsView_2->setScene(&scene);
}
if (timerFlag >= 115 && timerFlag < 119){
//
}
if(timerFlag == 119){
ui->graphicsView_2->setStyleSheet("border-image:url(:/new/100/timerIconPix.PNG);border:0px;}");
}
if(timerFlag == 120){
timerFlag = 0;
}
if (timeFlag==2){
timeFlag = 0;
if(sec>=10){
ui->label_2->setText(QString("%1:%2").arg(min).arg(sec));
} else {
ui->label_2->setText(QString("%1:0%2").arg(min).arg(sec));
}
++sec;
if (sec == 60) {
sec = 0;
++min;
}
}
}
You're merely decorating the QGraphicsView using the style mechanism. You could have used a plain QWidget instead, since you don't use any graphics view functionality. None of the images in the stylesheet are what the view actually displays. The image must be on the scene displayed by the view.
Set the image on a QGraphicsPixmapItem, add that item to a scene, set the scene on the view, and then the transformations will work. You can then keep replacing the pixmap in the timer handler.
Finally, you must also check the timer id in the timerEvent. I assume that you're using a QBasicTimer, say called m_timer, you'd then check as follows:
void Widget::timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
... // rest of the code
}
As you can see, the code that you've not included in the original question was absolutely essential! Without it, the question was wholly off-topic.
You need to implement a QGraphicsView, a QGraphicsScene and then add something that inherits from QGraphicsItem to that scene to rotate.
Here is an example that rotates a QWidget in a QGraphicsView:
QGraphicsView* view = new QGraphicsView(parent);
QGraphicsScene* scene = new QGraphicsScene(view);
view->setScene(scene);
// Widget to rotate - important to not parent it
QWidget* widget = new QWidget();
QProxyWidget proxy_widget = scene_->addWidget(widget);
QPropertyAnimation* animation = new QPropertyAnimation(proxy_widget, "rotation");
animation->setDuration(5000);
animation->setStartValue(0);
animation->setEndValue(360);
animation->setEasingCurve(QEasingCurve::Linear);
animation->start(QAbstractAnimation::DeleteWhenStopped);

Fill function in basic graphics app in Qt

I have been creating a basic graphics program(like MS Paint) with simple GUI. I have two classes where one is a MainWindow which holds all buttons, sliders etc and a second class is a custom widget called drawingArea on which a user is able to draw.
Basicly I've implemented most of functions, but unfortunetly I stucked at filling function, which should work just like one in MS Paint. I decided to use so called floodFill algorithm and after few hours of fighting(I am a beginner in Qt) I have managed to make it work. But not at all. The problem is I am only able to fill black-colored regions(shapes, lines, points etc) with the color I choose. But when it comes to filling different colors, it just puts one pixel in chosen color. In funcion fill(...) below I am passing two arguments - point(mouseClicked) and a color of that point. Here:
void drawingArea::fill(const QPoint &point, QColor act)
{
QPainter painter(&image);
QPen myPen(actualColor);
myPen.setWidth(1);
painter.setPen(myPen);
QQueue<QPoint> pixels;
pixels.enqueue(point);
while(pixels.isEmpty() == 0)
{
QPoint newPoint = pixels.dequeue();
QColor actual;
actual.fromRgb(image.pixel(newPoint));
if(actual == act)
{
painter.drawPoint(newPoint);
update();
QPoint left((newPoint.x()-1), newPoint.y());
if(left.x() >0 && left.x() < image.width() && image.pixel(left) == act.rgb())
{
pixels.enqueue(left);
painter.drawPoint(left);
update();
}
QPoint right((newPoint.x()+1), newPoint.y());
if(right.x() > 0 && right.x() < image.width() && image.pixel(right) == act.rgb())
{
pixels.enqueue(right);
painter.drawPoint(right);
update();
}
QPoint up((newPoint.x()), (newPoint.y()-1));
if(up.y() > 0 && up.y() < image.height() && image.pixel(up) == act.rgb())
{
pixels.enqueue(up);
painter.drawPoint(up);
update();
}
QPoint down((newPoint.x()), (newPoint.y()+1));
if(down.y() > 0 && down.y() < image.height() && image.pixel(down) == act.rgb())
{
pixels.enqueue(down);
painter.drawPoint(down);
update();
}
}
}
}
I call this fill(...) function inside mousePressEvent here:
void drawingArea::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
lastPoint = event->pos();
QColor clr;
clr.fromRgb(image.pixel(lastPoint));
firstPoint = event->pos();
isDrawing = true;
if(isColorcheck) colorcheck(lastPoint);
if(isFill) fill(lastPoint,clr);
}
}
The really don't understand how it is working only on black color, when passing argument QColor of different colors.
I would really appreciate any help or ideas.
There is a fundamental problem with your code. Don't do the drawing from just any method called in your class. The drawing needs to be done in paint event callback that you define. QWidget::paintEvent worth exploring.
void MyClass::paintEvent(QPaintEvent *event)
{
// do the drawing depending on the context and you may
// define that context in other event handlers, etc.
}
Other than that it is hard to say what else is wrong. BTW, you can trigger drawing event by repaint() or update(). You need to consider how the exact area of redraw is defined in your code.

How to access a variable from another class in Qt?

I am trying to implement in Qt a main window which has 2 widgets: one area where I draw some points and one list box where I write all the points with their respective coordinates. And I would like to implement the function "delete point" of a button on the main window, i.e. when I press the button then the point selected from the list box should disappear from my area where I am drawing. So I was thinking of doing this with signals/slots, but when I try to gain access to my list of points from my drawing area it just doesn't find any containing data. This is my code until now:
paintwidget.cpp (my main window):
PaintWidget::PaintWidget(QWidget parent) :
QWidget(parent),
ui(new Ui::PaintWidget)
{
area = new RenderArea(this);
ui->setupUi(this);
connect(ui->displayWidget, SIGNAL(listUpdated(QList)), ui->pointsListWidget,
SLOT(onListUpdated(QList*)));
connect(ui->deletePoints, SIGNAL(clicked()), this, SLOT(deleteItem()));
}
void PaintWidget::deleteItem()
{
area->deletePoint(ui->pointsListWidget->currentItem());
}
renderarea.cpp (my drawing area):
void RenderArea::mousePressEvent(QMouseEvent *e)
{
point = e->pos();
updateList(point);
this->update();
}
void RenderArea::updateList(const QPoint& p)
{
list.append(p);
if (list.count()>1)
lineAdded(p);
emit listUpdated(&list);
}
void RenderArea::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black,2));
for (int i = 0; i < list.size(); ++i)
painter.drawPoint(list[i]);
if (list.size()>1)
for(int j = 0; j < list.size()-1; ++j)
painter.drawLine(list[j], list[j+1]);
}
void RenderArea::deletePoint(QListWidgetItem *item)
{
bool ok1;
bool ok2;
int index = item->text().indexOf(",");
int x = item->text().left(index).toInt(&ok1, 10);
int y = item->text().mid(index + 1).toInt(&ok2, 10);
for (int i = 0; i < list.size(); ++i)
//find the point with x and y as coordinates and delete it
}
listbox.cpp:
void ListBox::onListUpdated(QList *list)
{
clear();
for (int i = 0; i < list->size(); ++i)
addItem(new QListWidgetItem(QString::number(list->at(i).x()) + ", " +
QString::number(list->at(i).y())));
}
The list from the render area is a QList of QPoints. The problem is that in the FOR-loop the size of the list is 0 so I cannot see any of the points that it should contain. I think that I am failing to initialize it somewhere but I am not sure where.
The points are drawn with QPainter so when I delete the point from the list is there any possibility to delete them from my drawing area also?
I'm suspecting you've got two RenderArea widgets hanging around for some reason.
You're connecting ui->displayWidget's signal, but acting on the area widget for the delete.
Shouldn't you be calling ui->displayWidget->deletePoint or connecting area's signal?
As for the repaint, you should call the widget's update() method to have it repaint itself.

Qt: How to force a hidden widget to calculate its layout?

What I am trying to do is render a qwidget onto a different window (manually using a QPainter)
I have a QWidget (w) with a layout and a bunch of child controls. w is hidden. Until w is shown, there is no layout calculations happening, which is expected.
When I call w->render(painter, w->mapToGlobal(QPoint(0,0)), I get a bunch of controls all overlapping each other.
w->layout()->activate();w->layout()->update() doesn't seem to do anything.
Is there a way to force the layout to happen without showing w?
Forcing a layout calculation on a widget without showing it on the screen:
widget->setAttribute(Qt::WA_DontShowOnScreen);
widget->show();
The show() call will force the layout calculation, and Qt::WA_DontShowOnScreen ensures that the widget is not explicitly shown.
The layout calculation of a widget can be forced by calling invalidate() followed by activate() on its layout, even if the widget is hidden. This also causes the widget's size() and sizeHint() functions to return correct and updated values, even if show() has not yet been called on that widget.
It is however necessary to care about all child widgets and layouts recursively, as a layout recalculation request doesn't automatically propagate to the childs.
The following code shows how to do this.
/**
* Forces the given widget to update, even if it's hidden.
*/
void forceUpdate(QWidget *widget) {
// Update all child widgets.
for (int i = 0; i < widget->children().size(); i++) {
QObject *child = widget->children()[i];
if (child->isWidgetType()) {
forceUpdate((QWidget *)child);
}
}
// Invalidate the layout of the widget.
if (widget->layout()) {
invalidateLayout(widget->layout());
}
}
/**
* Helper function for forceUpdate(). Not self-sufficient!
*/
void invalidateLayout(QLayout *layout) {
// Recompute the given layout and all its child layouts.
for (int i = 0; i < layout->count(); i++) {
QLayoutItem *item = layout->itemAt(i);
if (item->layout()) {
invalidateLayout(item->layout());
} else {
item->invalidate();
}
}
layout->invalidate();
layout->activate();
}
Try with the QWidget::sizeHint() method, which is supposed to return the size of the widget once laid out.
I had some succes in a similar problem by first calling w->layout()->update() before w->layout()->activate(). That seems to force the activate() to actually do something rather than think it is fine because the window isn't being shown anyway.
When going through QWidget::grab() I noticed this part:
if (r.width() < 0 || r.height() < 0) {
// For grabbing widgets that haven't been shown yet,
// we trigger the layouting mechanism to determine the widget's size.
r = d->prepareToRender(QRegion(), renderFlags).boundingRect();
r.setTopLeft(rectangle.topLeft());
}
QWidget::grab() has been introduced in Qt 5.0, and this test function with a QDialog containing a layout seems to work on Qt 5.5.1
int layoutTest_2(QApplication& a)
{
CLayoutTestDlg dlg; // initial dlg size can also be set in the constructor
// https://stackoverflow.com/questions/21635427
QPixmap pixmap = dlg.grab(); // must be called with default/negative-size QRect
bool savedOK = pixmap.save("E:/temp/dlg_img.png");
// saving is not necessary, but by now the layout should be done
dlg.show();
return a.exec();
}
This worked for me when using the sizeHint() plus translating the painter, however I do this inside the paint() method.
void ParentWidget::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->save();
painter->translate(option.rect.topLeft());
w->render(painter);
painter->restore();
}
In this case, option.rect.topLeft() gives me the correct placement. You should try a more sensible coordinate instead of w->mapToGlobal(QPoint(0,0).