PDA

View Full Version : QGraphicsGridLayout & QGraphicsLayoutItem boundingRect struggles.



chuckshaw
10th March 2013, 00:20
I have a traditional QWidget based UI with a QGraphicsView (well inside a QVBoxLayout of a QWidget) set as the QMainWindows central widget.

I subclassed QGraphicsVideoItem and QGraphicsLayoutItem so that I could build a NxN grid of videos. When I start the application it looks like this:

Screenshot of app before Resizing (https://docs.google.com/file/d/0B-UerCFoCf3xYUN0NHE5eUZrREk/edit?usp=sharing)

For the most part it works, and once the first resize event triggers the videos correctly fill the available space.

Screenshot of app after Resizing (https://docs.google.com/file/d/0B-UerCFoCf3xTTRnMVdFNVFZU00/edit?usp=sharing)

At this point when I dynamically resize the application, they grow and shrink (keeping aspect ratio) as they should.

I realize that until the show event, geometry sizes will be 0. That would explain why my boundingRect() of each custom GraphicsLayoutItem returns the equivalent of QRectF(0, 0, QSize(0, 0)). However, this fact is causing multiple problems for me and I feel like i'm spinning my wheels trying to fix it. If you look at the first screenshot, you will see that the video sizes are set to a 400x300 size because i was forced to set the minimum row/col sizes in the QGraphicsGridLayout. Without those values being set the video items will be drawn as if they have zero size (because my boundingRect is zero) appearing as a point with my borders painted around them). In addition to that, all mouse events are not being propagated to the individual GraphicsItems since you cant click on something registered as having a size of zero.

I have tried multiple approaches. I have tried setting the boundingRect of the custom GraphicsItems to return a default size of 400x300. This sort of fixes the mouse event propagation issue, but the videos no longer resize larger than that, and when I resize the mouse area no longer sits over the top and is offset. I've tried manually calling setGeometry() on all custom Graphics Items within a resizeEvent() function in the QGraphicsView's parent QWidget. That caused other problems as I was trying to use the parents new size to calculate what an individual grid cell would be, which resulted in odd behavior as the setPos() call in setGeometry(x, y, w, h) would move the custom QGraphicsItem out of the grid and paint it at the (X,Y) point passed in.

The one area I'm not exactly sure of is the behavior of QGraphicView's fitInView() function. I'm using that to keep the scene centered and taking up all available space in the view. However I'm not sure if thats just artificially zooming in and out (without changing the actual sizes of the QGraphicsItems) or vice versa.

Code in comments.

viewwidget.h

#ifndef VIDEOWIDGET_H
#define VIDEOWIDGET_H

#include <QHash>
#include <QWidget>

class QGraphicsView;
class QGraphicsScene;
class QGraphicsGridLayout;
class VideoPlayer;
class ImagePlayer;

class VideoWidget : public QWidget
{
Q_OBJECT
public:
explicit VideoWidget(QWidget *parent = 0);
~VideoWidget();

QSize sizeHint() const { return QSize(800, 600); }

QGraphicsView* GetGraphicsView() { return _view; }
QGraphicsScene* GetGraphicsScene() { return _scene; }
VideoPlayer* GetActiveVideoPlayer() { return _activeVideoPlayer; }
ImagePlayer* GetActiveImagePlayer() { return _activeImagePlayer; }

signals:
void Signal_VideoActive(QString name, bool active);

protected:
void resizeEvent(QResizeEvent* event);

private:
void SetupUI();
void Test();

int _rows, _cols;

QGraphicsView* _view;
QGraphicsScene* _scene;
QGraphicsGridLayout *_grid;

VideoPlayer* _activeVideoPlayer;
ImagePlayer* _activeImagePlayer;

QHash<QString, VideoPlayer*> _videos;
QHash<QString, ImagePlayer*> _images;

};

#endif // VIDEOWIDGET_H


videowidget.cpp


#include <QtWidgets>
#include <QHash>
#include <QHashIterator>
#include <QGraphicsVideoItem>
#include <QGLWidget>
#include <QGraphicsView>
#include <QGraphicsWidget>

#include "videowidget.h"
#include "videoplayer.h"
#include "imageplayer.h"

VideoWidget::VideoWidget(QWidget *parent) :
QWidget(parent)
{
_rows = 2;
_cols = 2;

_activeVideoPlayer = NULL;
_activeImagePlayer = NULL;

SetupUI();
Test();

_view->fitInView(_scene->itemsBoundingRect(), Qt::KeepAspectRatio);
}

VideoWidget::~VideoWidget()
{
_videos.clear();
_images.clear();

_activeVideoPlayer = NULL;
_activeImagePlayer = NULL;
}

void VideoWidget::SetupUI()
{
_view = new QGraphicsView(this);
_view->setAcceptDrops(true);
_view->viewport()->setMouseTracking(true);
_view->setViewportUpdateMode(QGraphicsView::FullViewportU pdate);
_view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
_view->setResizeAnchor(QGraphicsView::AnchorViewCenter);

_scene = new QGraphicsScene(this);
_scene->setBackgroundBrush(QColor(39,39,39));

_view->setScene(_scene);

_grid = new QGraphicsGridLayout;
_grid->setContentsMargins(5, 5, 5, 5);

QGraphicsWidget *container = new QGraphicsWidget;
container->setAcceptHoverEvents(true);
container->setAcceptTouchEvents(true);
container->setLayout(_grid);
container->setContentsMargins(0, 0, 0, 0);
_scene->addItem(container);

QBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_view);
this->setLayout(layout);
}

void VideoWidget::Test()
{
VideoPlayer* videoPlayer;
QString video("/Users/chucks/test.mp4");
int count = 0;
for(int i=0; i<_rows; i++)
{
for(int j=0; j<_cols; j++)
{
QString name = "Video" + QString::number(++count);
videoPlayer = new VideoPlayer(name, video);
_scene->addItem(videoPlayer);
_videos.insert(name, videoPlayer);
_grid->addItem(videoPlayer, i, j);

_grid->setColumnAlignment(j, Qt::AlignHCenter);
_grid->setColumnMinimumWidth(j, 400);
_grid->setColumnSpacing(j, 10);
}
_grid->setRowAlignment(i, Qt::AlignCenter);
_grid->setRowMinimumHeight(i, 300);
_grid->setRowSpacing(i, 10);
}
}

void VideoWidget::resizeEvent(QResizeEvent* event)
{
_view->fitInView(_scene->itemsBoundingRect(), Qt::KeepAspectRatio);
QWidget::resizeEvent(event);
}


videoplayer.h

#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H

#include <QMediaPlayer>
#include <QMovie>
#include <QGraphicsLayoutItem>
#include <QGraphicsVideoItem>
#include <QPainter>
#include <QPixmap>
#include <QGraphicsSceneMouseEvent>

class VideoPlayer : public QGraphicsVideoItem, public QGraphicsLayoutItem
{
public:
VideoPlayer(QString name, QString fileName);

QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

QString name() { return _name; }
QString file() { return _fileName; }
bool isPlaying() { return _isPlaying; }

void setGeometry(const QRectF & rect);
void updateGeometry();

signals:

public slots:
void Slot_OpenFile();
void Slot_Play();
void Slot_Stop();
void Slot_Rewind();
void Slot_FastForward();

protected:
QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF()) const;
void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
void mousePressEvent(QGraphicsSceneMouseEvent * event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event);

private slots:
void mediaStateChanged(QMediaPlayer::State state);
void positionChanged(qint64 position);
void durationChanged(qint64 duration);
void setPosition(int position);

private:
QMediaPlayer* _mediaPlayer;
QPixmap *_pm;
QString _name;
QString _fileName;

bool _isPlaying;

};

#endif // VIDEOPLAYER_H


videoplayer.cpp

#include "videoplayer.h"

#include <QtWidgets>
#include <QVideoSurfaceFormat>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QPainter>
#include <QStyleOption>

VideoPlayer::VideoPlayer(QString name, QString fileName) :
QGraphicsVideoItem(),
QGraphicsLayoutItem()
{
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);

setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
setAcceptHoverEvents(true);
setAcceptDrops(true);

setAspectRatioMode(Qt::KeepAspectRatio);

_name = name;
_fileName = fileName;
_isPlaying = false;

_mediaPlayer = new QMediaPlayer(0, QMediaPlayer::VideoSurface);
_mediaPlayer->setVideoOutput(this);
connect(_mediaPlayer, &QMediaPlayer::stateChanged, this, &VideoPlayer::mediaStateChanged);
connect(_mediaPlayer, &QMediaPlayer::positionChanged, this, &VideoPlayer::positionChanged);
connect(_mediaPlayer, &QMediaPlayer::durationChanged, this, &VideoPlayer::durationChanged);

_pm = new QPixmap(QLatin1String(":/images/Resources/noise.png"));
setGraphicsItem(this);
}

QRectF VideoPlayer::boundingRect() const
{
return QRectF(QPointF(0, 0), geometry().size());
}

void VideoPlayer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget);
Q_UNUSED(option);

QRectF frame(QPointF(0,0), geometry().size());

qreal w = (parentItem()->boundingRect().width()/2) - 10;
qreal h = (parentItem()->boundingRect().height()/2) - 10;

// paint a rect around the pixmap
QPointF pixpos = frame.center() - (QPointF(w, h) / 2);
QRectF innerFrame(pixpos, QSizeF(w, h));
innerFrame.adjust(-4, -4, 4, 4);
painter->setBrush(QBrush(Qt::darkGreen));

if(isSelected())
{
painter->setBrush(QBrush(Qt::green));
painter->drawRoundedRect(innerFrame, 10.0, 10.0);
}
else
{
painter->setBrush(QBrush(Qt::darkGreen));
painter->drawRect(innerFrame);
}

// paint a title bar rect
QRect rectangle(-w/2, -h/2, w, 20);
painter->setPen(QPen(Qt::darkGreen));
painter->setBrush(QBrush(QColor(39,39,39)));
painter->drawRect(rectangle);

// paint video name in title bar
painter->drawText(rectangle, Qt::AlignCenter, _name);

// paint the default background pixmap
painter->drawPixmap(pixpos.x(), pixpos.y()+rectangle.height(), w, h-rectangle.height(), *_pm);
painter->setPen(QPen(QBrush(Qt::black), 10));
painter->drawText(innerFrame, Qt::AlignCenter, "No Video");

QGraphicsVideoItem::paint(painter, option, widget);
}

void VideoPlayer::Slot_OpenFile()
{
}

void VideoPlayer::Slot_Play()
{
}

void VideoPlayer::Slot_Stop()
{
_mediaPlayer->stop();
}

void VideoPlayer::Slot_Rewind()
{
}

void VideoPlayer::Slot_FastForward()
{
}

void VideoPlayer::mediaStateChanged(QMediaPlayer::State state)
{
}

void VideoPlayer::positionChanged(qint64 position)
{
}

void VideoPlayer::durationChanged(qint64 duration)
{
}

void VideoPlayer::setPosition(int position)
{
}

void VideoPlayer::setGeometry(const QRectF & rect)
{
prepareGeometryChange();
QGraphicsLayoutItem::setGeometry(rect);
setPos(rect.topLeft());
}

void VideoPlayer::updateGeometry()
{
QGraphicsLayoutItem::updateGeometry();
}

QSizeF VideoPlayer::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const
{
switch ( which )
{
case Qt::MinimumSize:
case Qt::PreferredSize:
return this->boundingRect().size();
case Qt::MaximumSize:
return QSizeF(parentItem()->boundingRect().width(), parentItem()->boundingRect().height());
default:
return this->boundingRect().size();
}

return constraint;
}

void VideoPlayer::mouseMoveEvent(QGraphicsSceneMouseEve nt * event)
{
QGraphicsVideoItem::mouseMoveEvent(event);
}

void VideoPlayer::mousePressEvent(QGraphicsSceneMouseEv ent * event)
{
QGraphicsVideoItem::mousePressEvent(event);
}

void VideoPlayer::mouseReleaseEvent(QGraphicsSceneMouse Event * event)
{
QGraphicsVideoItem::mouseReleaseEvent(event);
}

void VideoPlayer::mouseDoubleClickEvent(QGraphicsSceneM ouseEvent * event)
{
QGraphicsVideoItem::mouseDoubleClickEvent(event);
}

d_stranz
11th March 2013, 20:58
The one area I'm not exactly sure of is the behavior of QGraphicView's fitInView() function. I'm using that to keep the scene centered and taking up all available space in the view. However I'm not sure if thats just artificially zooming in and out (without changing the actual sizes of the QGraphicsItems) or vice versa.


fitInView() simply applies a scale factor (or factors, if ignoring aspect ratio) to the scene so that all elements in the scene are visible in the view. As far as I know, it is simply a transformation applied during rendering with no effect on the underlying items in the scene.

I'm not clear why your VideoWidget class inherits from QWidget. Why not simply make VideoWidget a QGraphicsView instead of pushing your actual graphics view down two layers by embedding it in an otherwise empty QVBoxLayout which is then set as the layout for VideoWidget? QGraphicsView *is* a QWidget, and it will happily live as the central widget for a QMainWindow.

You might also try playing around with the row and column stretch factors for your grid layout.

If what you want is that the "before resize" screen shot should instead look like the "after resize" screenshot, implement a showEvent() and call fitInView() there.

chuckshaw
31st March 2013, 09:53
Hey, thanks for the reply.
I had forgotten I posted this question here too, should have posted. My issue was related to the boundingRect() of my individual video items. And I did end up using showEvent to accomplish the correct scaling at app startup. As for the VideoWidget, i originally had a single video window as a GraphicsView, and video controls as a QWidget. Thats why VideoWidget inherits from QWidget and it is inside the VBoxLayout. I just have not ripped that out yet... though i'm glad you pointed that out to remind me.

So far fitInView() works but I'm going to just go though the motions and manually resize everything. I don't want everything to grow and shrink such as the video controls, just the video itself.