I'm trying to zoom in a picture using QT and C++.
I inherited QLabel objects in my classes to show pictures. And also put this QLabels in mdiarea.
zoom function is working fine however the qlabel does not update its size immediately. If I try to resize is manually (with cursor) program automatically handles it and resize the qlabel as I wish.
How can I update the sizes immediately.
Thanks for help. :)
bool MdiChild::event ( QEvent * e ){
//qDebug("asd1");
if(e->type() == QEvent::Wheel){
int numDegrees = ((QWheelEvent*) e)->delta() / 8;
double numSteps = (double)numDegrees/150;
int w = pix->width();
int h = pix->height();
ratio += numSteps;
qDebug("ratio = %f", ratio);
QPixmap* p = new QPixmap(pix->scaledToHeight ( (int)(h * ratio),Qt::FastTransformation ));
setPixmap(*p);
adjustSize();
adjustSize();
update();
}
return QWidget::event(e);
}
The problem is resolved but I think I cannot answer my own question.
When I add the same event to the parent window problem is solved. But when I maximize the window the inner object also got the event and crashes the maximized window.
bool ImageProcessor::event ( QEvent * e ){
if(e->type() == QEvent::Wheel){
QList<QMdiSubWindow *> childList = ui.mdiArea->subWindowList();
for(int i = 0; i<childList.count(); i++){
childList[i]->adjustSize();
}
}
return QWidget::event(e);
}
You need a QScrollArea to hold your QLabel. Otherwise when the window resizes your QLabel will not have scrollbars.
Look at the examples to see how to create an image viewer and how they resize.
ImageViewer example
Zoomable Picture Viewer
The problem is resolved but I think I cannot answer my own question. When I add the same event to the parent window problem is solved. But when I maximize the window the inner object also got the event and crashes the maximized window.
bool ImageProcessor::event ( QEvent * e ){
if(e->type() == QEvent::Wheel){
QList<QMdiSubWindow *> childList = ui.mdiArea->subWindowList();
for(int i = 0; i<childList.count(); i++){
childList[i]->adjustSize();
}
}
return QWidget::event(e);
}
Related
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
I have a QGraphicsView subclass, holding a QGraphicsScene subclass, inside a QWidget.
The QGraphicsScene has an actual drawing rectangle with top let corner at (0,0) (so the scene really has a QRectF(-x,-y,w,h) of values irrelevant to the problem).
On startup, actually in the showEvent of the widget, I set the size of the scene to fit the custom sized rectangle. That also centers it.
What I would like, is to match the (0,0) point of the scene to the top left of the view, on start.
Unlike other questions with the same title, I actually need to move the view, not the items inside the scene.
Image 1: actual start, image 2: desired
I am trying:
void MyWidget::showEvent(QShowEvent *event)
{
static bool startUp = true;
if(startUp)
{
QSizeF sz = m_scene->getActualSize();
ui->graphicsView->fitInView(QRectF(0, 0, sz.width(), sz.height()), Qt::KeepAspectRatio);
QPointF zero = ui->graphicsView->mapFromScene(QPointF(0,0));
ui->graphicsView->translate(-zero.x(), -zero.y()); // or positive
startUp = false;
}
QWidget::showEvent(event);
}
note: I use the fitInView to get the desired zoom - and it also brings the desired rectangle in focus - but centered in the view.
I also tried
void GraphicsView::alignToZero()
{
QPointF zero = this->mapFromScene(QPointF(0,0));
this->scrollContentsBy(-zero.x(), -zero.y());
}
void MyWidget::showEvent(QShowEvent *event)
{
static bool startUp = true;
if(startUp)
{
QSizeF sz = m_scene->getActualSize();
ui->graphicsView->fitInView(QRectF(0, 0, sz.width(), sz.height()), Qt::KeepAspectRatio);
ui->graphicsView->alignToZero();
startUp = false;
}
QWidget::showEvent(event);
}
Still no result.
Also tried
void MyWidget::showEvent(QShowEvent *event)
{
static bool startUp = true;
if(startUp)
{
QSizeF sz = m_scene->getActualSize();
ui->graphicsView->fitInView(QRectF(0, 0, sz.width(), sz.height()), Qt::KeepAspectRatio);
QPointF zero = ui->graphicsView->mapFromScene(QPointF(0,0));
//ui->graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
ui->graphicsView->horizontalScrollBar()->setValue(zero.x());
ui->graphicsView->verticalScrollBar()->setValue(zero.y());
startUp = false;
}
QWidget::showEvent(event);
}
This, with or without the alignment setting (which I think may have unwanted effects elsewhere), moves the view... too far.
If I understand your question correctly, to achieve desired effect you can use method
QGraphicsView::setAlignment(Qt::Alignment alignment):
ui->graphicsView->setAlignment( Qt::AlignTop | Qt::AlignLeft );
Which is commented in your code.
This method with those arguments aligns view top-left, you don't have to move or fit anything.
While there must be a better way... I figured out a way to get my 0's aligned by doing fitInView twice:
void MyWidget::showEvent(QShowEvent *event)
{
static bool startUp = true;
if(startUp)
{
QSizeF sz = m_scene->getActualSize();
// First I do a normal `fitInView` that centers the rectangle
ui->graphicsView->fitInView(QRectF(0, 0, sz.width(), sz.height()), Qt::KeepAspectRatio);
// Then, knowing the shift, I fit it again adding twice the shift to the size.
QPointF zero = ui->graphicsView->mapToScene(QPoint(0,0));
ui->graphicsView->fitInView(QRectF(0, 0, sz.width() - 2 * zero.x(), sz.height() - 2 * zero.y()), Qt::KeepAspectRatio);
startUp = false;
}
QWidget::showEvent(event);
}
I've tried absolutely everything I know, and I've come to the conclusion that this issue is over my head. I've tried running repaint(), update() and this->update(); and everything else that I could think of. Pixmap works outside of the function (in the constructor) but not inside a function. Here is code (only relevant is pasted, please indicate if you would like more):
myWidget.h
#define NUM_POINTERS 10
QLabel* pointerArray[NUM_POINTERS];
QPixmap circle;
QPixmap* triangle;
QPixmap* whitex;
int activePointer;
myWidget.cpp
activePointer = 0;
QPixmap circle (":/Resources/greencircle.png");
this->whitex = new QPixmap(":/Resources/white_x.png");
this->triangle = new QPixmap(":/Resources/redtriangle.png");
//create an array of pointers to the label1-10 objects
pointerArray[0] = ui->label1;
pointerArray[1] = ui->label2;
pointerArray[2] = ui->label3;
...
pointerArray[9] = ui->label10;
for (int i = 0;i < 10; i++)
{
pointerArray[i]->setPixmap(circle);
}
void myWidget::changeImage()
{
updatesEnabled();
if (activePointer < 10){
pointerArray[activePointer]->setPixmap(*this->whitex);
activePointer++;
update();
}
else{
printf("end of array\n");
fflush(stdout);
}
}
I get a row of circles printed where I want them, but I won't get any white Xs. The pixmap changes to the whitex, but it will not update. It does not crash, it continues adding to activePointer until the end of the array.
Thanks in advance.
Quick Edit: I have tried pointerArray[activePointer]->update(); with no luck.
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
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.