How to get info from clicked QGraphicsItem? - qt

The idea is that the user clicks on a shape and the information of the shape is shown on a table. This works well if the user selects the shape (drag the mouse over the shape). I'm trying to modify this code to do that action, but not lucky. This is what I'm doing for the select mode:
I have a a call in the:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
When the mouse is released I will update the data:
//enter the mode for selecting
if(theMode == SelectObject){
if (this->items().isDetached()){
//we check if the object is selected
if (this->selectedItems().isEmpty()){
qDebug() << "not selected";
isSelected = false;
return;
}
//we get a list of the shapes
QList<QGraphicsItem*> stackOfShapes = this->items();
//we get index of selected shape
QGraphicsItem *selected = this->selectedItems().first();
int indexOfShape = stackOfShapes.indexOf(selected);
//we see which shape is (For a Rectangle)
switch (selected->type()) {
case 346:
{
updateDataOfRect();
}
break;
}
}
The problem is that:
//we get index of selected shape
QGraphicsItem *selected = this->selectedItems().first();
How to do this when the shape is clicked not selected?
I tried to modify the subclass of the shape in the mousePressEvent :
if (event->button() == Qt::MouseButton::LeftButton) {
this->setSelected(true);
}
Can any one help to find a solution?
Thanks.

QList<QGraphicsItem *> QGraphicsView::items(const QPoint &pos) const
Returns a list of all the items at the position pos in the view. The
items are listed in descending stacking order (i.e., the first item in
the list is the uppermost item, and the last item is the lowermost
item). pos is in viewport coordinates.
Example use by overloading QGraphicsView::mousePressEvent():
void CustomView::mousePressEvent(QMouseEvent *event) {
qDebug() << "There are" << items(event->pos()).size()
<< "items at position" << mapToScene(event->pos());
}

Related

Qt - QGraphicsScene MouseEvent is offset

I am trying to test a MouseEvent for a QGraphicsScene, but for some reason, the area at which the program registers the click is offset. Here is my code:
//var declaration
editorScene = new QGraphicsScene(this);
ui->spriteGraphicsEditor->setScene(editorScene);
...
//registers a click on the graphics editor
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
// Detect if the click is in the view.
QPoint remapped = ui->spriteGraphicsEditor->mapFromParent( event->pos() );
if ( ui->spriteGraphicsEditor->rect().contains( remapped ) )
{
QPointF mousePoint = ui->spriteGraphicsEditor->mapToScene( remapped );
qDebug() << mousePoint;
}
}
}
And here is a screenshot of the program(it is still a rough work-in-progress)
The blue box shows the approximate area you can click on
It appears that the gray bar on the top is causing the error, as if the area is being defined before the GraphicsScene is being put into place. Why might this happen?

How to erase a QGraphicsItem from QGraphicsView using QPushButton

I am building a major user interface but I am stuck on a problem and in order to shrink the problem I build a small working example that carries exactly the problem.
After the user creates a new .db file using the icon and saving for example to Desktop it is possible to load images(only in .png format for now) in the QGraphicsView and cliking on the checkbox to enable from Right: Drag to Right:Select it is possible to draw boxes on the image. With a right click inside the drawn box we can open a QDialog that asks to name the extracted image from the box. The image is stored in the QTableView as shown in the small working example below:
The problem:
I tried several ways to erase the box (and the related index on the QTableView of the related box) but none of them was successful.
With the use of the "Erase Square" QPushButton I am trying to erase the box. Every box can be re-activated by just left double-clicking within the interested box. See below what I am trying to achieve:
I draw the box on the QGraphicsView and right mouse click to capture the image from the box:
void MainWindow::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::RightButton)
{
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ShowContextMenu(const QPoint &)));
}
}
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
Q_UNUSED(event);
leftScene->clearSelection(); // Selections would also render to the file
leftScene->setSceneRect(leftScene->itemsBoundingRect()); // Re-shrink the scene to it's bounding contents
QImage image(leftScene->sceneRect().size().toSize(), QImage::Format_ARGB32); // Create the image with the exact size of the shrunk scene
image.fill(Qt::transparent); // Start all pixels transparent
QPainter painter(&image);
leftScene->render(&painter);
QImage subimage = image.copy(QRect(start.toPoint(), end.toPoint()));
clipSceneDialog d(this);
d.show();
d.setImage(subimage);
d.setHeaderImage(currentLeftImagePath);
d.setBoundingBox(QRect(start.toPoint(), end.toPoint()));
if(d.exec() == QDialog::Rejected) {
return;
} else {
//
}
Param result = d.getData();
Parameters* param = new Parameters(result);
mDatabaseLeftCamera->addItem(param);
mModelLeftCamera->select();
ui->tableView->show();
}
Draw as many boxes as the user wants:
void MainWindow::onRubberBandUpdate(const QRect &viewportRect,
const QPointF &fromScenePoint,
const QPointF &toScenePoint)
{
if(viewportRect.isNull() && fromScenePoint.isNull() && toScenePoint.isNull() && imageLoaded)
{
if(currentSelection >= 0)
selections[currentSelection]->setActiveState(false);
QRectF select;
select.setCoords(start.x(), start.y(), end.x(), end.y());
square = new Square(select);
square->setActiveState(true);
currentSelection = selections.size();
selections.append(square);
leftScene->addItem(square->getGraphics());
ui->graphicsView->show();
}
else
{
start = fromScenePoint;
end = toScenePoint;
}
}
After drawing several boxes the user decides to reactivate one of the boxes by double-cliking within the box:
void MainWindow::onSceneDoubleClick(QPointF point)
{
qDebug() << "Click!";
QList<QGraphicsItem*> foundItems = leftScene->items(point);
if(foundItems.size() > 0 && foundItems[0]->group() != nullptr)
{
qDebug() << "Found";
int i = 0;
for(i=0;i<selections.size();i++)
{
qDebug() << "Iterate";
if(selections[i]->getGraphics() == foundItems[0]->group())
{
qDebug() << "Target";
break;
}
}
if(currentSelection >= 0)
selections[currentSelection]->setActiveState(false);
currentSelection = i;
selections[currentSelection]->setActiveState(true);
}
}
The user decides to erase the re-activated square:
void MainWindow::on_eraseSquare_clicked()
{
clearSceneLeft();
}
void MainWindow::clearSceneLeft()
{
if (selections.size() > 0) {
qDeleteAll(selections);
selections.clear();
currentSelection = -1;
}
for(int p=0;p<shape.size();p++)
{
leftScene->removeItem(shape[p]);
delete shape[p];
}
shape.clear();
}
But this is not working and nothing happens. Additionally because the box corresponds to an SQL reference on the QTableView I dont know how that could be connected in the SQL class.
I have been trying for many days to solve this problem and am running out of ideas. Thanks for shedding light on this problem or point in the right direction.
If you would like to try the small example interface you can do it from here

QLabel OpenCV Image Coordinates in Qt

I would like to do manipulations on the image with OpenCV based on mouseClicks.
I am using QLabel to display cv::Mat images. Now my problem is with getting the mouse clicks positions with respect to the image. So, I would like (0,0) at topleft corner of the image.
Following is my mousePressEvent, but these are not correct co-ordinates.
void MainWindow::mousePressEvent( QMouseEvent* ev )
{
//This seems to work thanks to Pavel
QPoint P = ui->label->mapFrom(this, ev->pos())
//if( ui->label->underMouse() )
{
QMessageBox msgBox;
//m
sgBox.setText(QString("Click Detected X=")+QString::number(mFirstX)+QString(" Y=")+QString::number(mFirstY));
msgBox.setText("x ="+QString::number(P.x()) + " y= " + QString::number(P.y()));
msgBox.exec();
}
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseMove)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
///////
*/// This seem to still give wrong position, these values do not match to those I get when I /// click
///////
const QPoint P = ui->label->mapFrom(this, mouseEvent->pos());
//statusBar()->showMessage(QString("Mouse move (%1,%2)").arg(mouseEvent->pos().x()).arg(mo
useEvent->pos().y()));
statusBar()->showMessage(QString("Mouse move (%1,%2)").arg(P.x()).arg(P.y()));
}
return false;
}*
Please help.
You need to set QLabel's alignment to Qt::AlignTop | Qt::AlignLeft and ensure that its scaledContents property is false. You should use ui->label->mapFrom(this, ev->pos()) to convert MainWindow coordinates to label coordinates.

mousePressEvent() method is not working as expected when calling setPos() method

I have a class MenuItem which inherits from QGraphicsItem and reimplemented boundingRect(), shape(), paint(), outlineRect():
MenuItem::MenuItem(const QString& qsText, qreal qrYPos)
{
m_qsText = qsText;
m_BackgroundColor = Qt::white;
m_OutlineColor = Qt::darkBlue;
m_TextColor = Qt::darkGreen;
qDebug() << pos();
setPos(mapToParent(200,200)); //<-- when calling this method, mousePressEvent()
// behaves not as expected
qDebug() << pos();
}
QRectF MenuItem::boundingRect() const
{
const int iMargin = 1;
return outlineRect().adjusted(-iMargin, -iMargin, +iMargin, +iMargin);
}
QPainterPath MenuItem::shape() const
{
QRectF rect = outlineRect();
QPainterPath path;
path.addRect(rect);
return path;
}
void MenuItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(m_OutlineColor);
painter->setPen(pen);
painter->setBrush(m_BackgroundColor);
QRectF rect = outlineRect();
painter->drawRect(rect);
painter->setPen(m_TextColor);
painter->drawText(rect, Qt::AlignCenter, m_qsText);
}
void MenuItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Item Mouse Pressed";
}
QString MenuItem::getText() const
{
return m_qsText;
}
QRectF MenuItem::outlineRect() const
{
const int iPadding = 8;
QFontMetricsF metrics = QFontMetricsF(QApplication::font());
QRectF rect = metrics.boundingRect(m_qsText);
rect.adjust(-iPadding, -iPadding, +iPadding, +iPadding);
rect.translate(-rect.center());
return rect;
}
In another class, called Menu which inherits from QGraphicsScene, I added one instance of MenuItem:
Menu::Menu()
: QGraphicsScene()
{
setSceneRect(0, 0, 800, 600);
m_miNewGame = new MenuItem("New Game", 300);
this->addItem(m_miNewGame);
//m_miNewGame->setPos(200,200);
}
The Menu class reimplements mousePressEvent
void Menu::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
//qDebug() << "Menu Mouse Pressed";
MenuItem *gi = dynamic_cast<MenuItem*>(itemAt(event->pos(), QTransform()));
if (gi)
qDebug() << gi->getText();
QGraphicsScene::mousePressEvent(event); // this forwards the event to the item
if (itemAt(event->pos(), QTransform()))
{
qDebug() << "You Pressed an Item";
}
}
If I use setPos() method inside the MenuItem constructor the MenuItem gets positioned right but inside the Menu::mousePressEvent() method, MenuItem* returned from itemAt() is always NULL.
Omitting the setPos() method, the MenuItem stays in the top left corner (0,0) of the scene and mousePressEvents are handled as expected: returning the MenuItems Text with the getText() method.
Why is the MenuItem* NULL when calling setPos()?
Do I have to reimplement setPos() or what am I doing wrong?
Any help is welcome.
In MenuItem() constructor you use mapToParent. But your item doesn't have any parent item. So using mapToParent is pointless, it's equivalent to mapToScene in this case. And since your item's initial position is (0, 0) and no transformation has been applied, mapToScene will return its argument's value without changes. So it's equivalent to setPos(200, 200). It seems strange to use the result of mapToParent or mapToScene in setPos. I don't understand what you were trying to do.
QGraphicsSceneMouseEvent::pos returns coordinates of the event in target item's coordinates. Since you're using it in QGraphicsScene::keyPressEvent, the event has not been propagated to any item, and pos() always returns (0, 0). The documentation isn't clear about it, but I've checked it.
If you didn't use setPos, your item's position will be (0, 0) and itemAt(0, 0) will find your item (regardless of the point the user have actually clicked). But if you did use setPos, itemAt(0, 0) returns 0 because there is no item at this point. If you replace event->pos() with event->scenePos(), it will work correct.
However, it's unusual to reimplement QGraphicsScene::keyPressEvent to catch clicking on item. You should reimplement QGraphicsItem::mousePressEvent instead. It will be called only if the item has been clicked, and you don't have to check event's coordinates to determine that.

Qt tracing QGraphicsScene::itemAt() back to data model

I have a list of objects that I use to add objects into a QGraphicsScene:
for(int i = 0; i < levelObjects.length(); i++)
{
QRect objRect;
objRect = spriteSheetLocations.value(levelObjects.at(i).value("frame_name"));
//Q_ASSERT_X(objRect != QRect(0,0,0,0), "MainWindow::loadFile()", "Could not find sprite location!");
QImage img = spriteSheet.copy(objRect);
int height = levelObjects.at(i).value("height").toInt();
int width = levelObjects.at(i).value("width").toInt();
int x = levelObjects.at(i).value("x").toInt();
int y = levelObjects.at(i).value("y").toInt();
img = img.scaled(QSize(width, height), Qt::IgnoreAspectRatio);
item = scene->addPixmap(QPixmap::fromImage(img));
int xPos = x - width/2;
int yPos = levelPlist.value("level_height").toInt() - (y + height/2);
item->setPos(xPos, yPos);
}
Later on, in the GraphicsScene class, I detect when the user clicks on an item and drags it to move it:
void LevelGraphicsView::mousePressEvent(QMouseEvent *event)
{
if (QGraphicsItem *item = itemAt(event->pos())) {
qDebug() << "You clicked on item" << item;
draggedItem = item;
int mouseX = draggedItem->pos().x() - mapToScene(event->pos()).x();
int mouseY = draggedItem->pos().y() - mapToScene(event->pos()).y();
mouseOffset = QPointF(mouseX, mouseY);
} else {
qDebug() << "You didn't click on an item.";
draggedItem = NULL;
mouseOffset = QPointF(0,0);
}
}
void LevelGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
if(!draggedItem) // no item selected
return;
QPointF pos = mapToScene(event->pos()) + mouseOffset;
draggedItem->setPos(pos);
}
This works fine for moving the items in the graphics view, but I'm having trouble tracing the QGraphicsItem back to the list item that created it.
What's the best way to link the QGraphicsItem with the list item from which it was made so that the list item can be changed to reflect the change of position?
You could assign each item in your domain object a QUuid property and pass this along to a property in your QGraphicsItem. I have used this on a project and it works quite well. I added a QHash lookup table to my domain model to make it more efficient, but this would not be necessary for shorter lists.
The best way would be a way that does not require to manualy sync items from your list and items on the scene.
The best way to do that depends on your design - may be your items can become pointers to the items on the scene or they can hold ones.

Resources