How to erase a QGraphicsItem from QGraphicsView using QPushButton - sqlite

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

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 get info from clicked QGraphicsItem?

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

Move QGraphicsItem only when pressing inside the shape. Otherwise, drag scene

I have a QGraphicsView with a bigger QGraphicsScene that can be dragged.
In the QGraphicsScene I have a subclassed QGraphicsItem (TestItem) that displays a QGraphicsPixmapItem, which can have random shapes.
(I don't use QGraphicsPixmapItem directly because of extra functionality to be implemented in the future)
I want this item to be movable, but only if the user presses within the shape of the item. If outside the shape, but still inside the boundingRectangle, I want the scene behind it to be dragged. This because the boundingRectangle can be much bigger than the shape and the user doesn't see it, so it would be weird trying to drag the scene near the Pixmap and it not working.
This is my subclassed item:
TestItem::TestItem(QPointF position, QPixmap testImage, double width,
double length, QGraphicsItem * parent):
QGraphicsItem(parent),
m_boundingRect(QRectF(0,0,5, 5)),
m_dragValid(false),
m_path(QPainterPath()),
mp_image(new QGraphicsPixmapItem(this))
{
setBoundingRect(QRectF(0,0,width,length));
setPos(position - boundingRect().center());
setFlag(QGraphicsItem::ItemIsMovable);
mp_image->setPixmap(testImage.scaled(width, length));
m_path = mp_image->shape();
}
QPainterPath TestItem::shape()
{
return m_path;
}
QRectF TestItem::boundingRect() const
{
return m_boundingRect;
}
void TestItem::setBoundingRect(QRectF newRect)
{
prepareGeometryChange();
m_boundingRect = newRect;
}
I've tried overriding the mouse events like this, but all it brings me is no functionality at all when outside the shape but inside the bounding rectangle
void TestItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(shape().contains(event->pos()))
{
QGraphicsItem::mousePressEvent(event);
m_dragValid = true;
}
}
void TestItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseMoveEvent(event);
}
void TestItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseReleaseEvent(event);
m_dragValid = false;
}
which of course makes sense, but I wouldn't know how to implement the dragging of the scene, since it's the scene itself that sends the mouse events to the graphics item.
(My QGraphicsView is setup to DragMode QGraphicsView::ScrollHandDrag)
Anyone have ideas?
I figured it out. I only needed to add a event->ignore(); to my mouse events.
void TestItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(shape().contains(event->pos()))
{
QGraphicsItem::mousePressEvent(event);
m_dragValid = true;
}
else
event->ignore();
}
void TestItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseMoveEvent(event);
else
event->ignore();
}
void TestItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(m_dragValid)
QGraphicsItem::mouseReleaseEvent(event);
else
event->ignore();
m_dragValid = false;
}
You just need to enable QGraphicsItem::ItemClipsToShape flag:
The item clips to its own shape. The item cannot draw or receive mouse, tablet, drag and drop or hover events outside its shape. It is disabled by default.

How to save an image in a QGraphicsView into a bmp/jpg

I am a newbie for Qt.
The question is that: After turning an image into the QGraphicsView, I use a qrubberband to select the cropping area of the image. It is successful to select the croppping region at the moment.But I don't know how to save that cropping region into a jpg/bmp afterwards. Note that I made an ui component for the GraphicsView called CGraphicsView.
void CGraphicsView::mousePressEvent
( QMouseEvent* event)
{
mypoint = event->pos();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);//new rectangle band
rubberBand->setGeometry(QRect(mypoint, QSize()));
rubberBand->show();
}
void CGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
if (rubberBand)
{
rubberBand->setGeometry(QRect(mypoint, event->pos()).normalized());//Area Bounding
}
}
void CGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
if (rubberBand)
{
QRect myRect(mypoint, event->pos());
rubberBand->hide();// hide on mouse Release
QImage copyImage; //<= this Qimage hold nothing
copyImage = copyImage.copy(myRect);
}
}
There is special method in Qt. It allows get screenshot of view.
QString fileName = "path";
QPixmap pixMap = QPixmap::grabWidget(graphicsView, rectRegion);
pixMap.save(fileName);
Save() method can save picture in different formats and with compression.
Also with grabWidget() method you can grab another widgets too. Moreover, this method take QRect as argument so you can create screenshot of region what you need.
You can save a part of your scene to an image like :
QPixmap pixmap=QPixmap(myRect.size());
QString filename = QFileDialog::getSaveFileName( this->parentWidget(), tr("Save As"), tr("image.png"));
if( !filename.isEmpty() )
{
QPainter painter( &pixmap );
painter.setRenderHint(QPainter::Antialiasing);
scene->render( &painter, pixmap.rect(),myRect, Qt::KeepAspectRatio );
painter.end();
pixmap.save(filename,"PNG");
}

Prevent QGraphicsItem from moving outside of QGraphicsScene

I have a scene which has fixed dimensions from (0;0) to (481;270):
scene->setSceneRect(0, 0, 481, 270);
Inside of it, I have a custom GraphicsItem and I can move it thanks to the flag ItemisMovable, but I would like it to stay within the Scene; I actually mean I don't want it to have coordinates neither under (0;0) nor over (481;270).
I tried several solutions like overriding QGraphicsItem::itemChange() or even QGraphicsItem::mouseMoveEvent() but I still cannot manage to reach what I want to do.
What is the suitable solution for my needs? Do I use QGraphicsItem::itemChange() badly?
Thanks in advance.
You can override QGraphicsItem::mouseMoveEvent() like this:
YourItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem::mouseMoveEvent(event); // move the item...
// ...then check the bounds
if (x() < 0)
setPos(0, y());
else if (x() > 481)
setPos(481, y());
if (y() < 0)
setPos(x(), 0);
else if (y() > 270)
setPos(x(), 270);
}
This code keeps your complete item in scene. Not only the upper left pixel of your item.
void YourItem::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
{
QGraphicsItem::mouseMoveEvent(event);
if (x() < 0)
{
setPos(0, y());
}
else if (x() + boundingRect().right() > scene()->width())
{
setPos(scene()->width() - boundingRect().width(), y());
}
if (y() < 0)
{
setPos(x(), 0);
}
else if ( y()+ boundingRect().bottom() > scene()->height())
{
setPos(x(), scene()->height() - boundingRect().height());
}
}
Warning: The suggested solutions won't work for multiply selected items. The problem is, only one of the items receives a mouse move event in that case.
Actually, the Qt Documentation on QGraphicsItem provides an example which exactly solves the problem of limiting the movement of items to the scene rect:
QVariant Component::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
// value is the new position.
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
// Keep the item inside the scene rect.
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
return newPos;
}
}
return QGraphicsItem::itemChange(change, value);
}
Note I: You'd have to enable the QGraphicsItem::ItemSendsScenePositionChanges flag:
item->setFlags(QGraphicsItem::ItemIsMovable
| QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemSendsScenePositionChanges);
Note II: If you only want to react on finished movement, consider using the GraphicsItemChange flag ItemPositionHasChanged

Resources