Unwanted margin inside QGraphicsView with Scrollbars - c++

I am developing a video player, using a QGraphicsView to display the video. The QGraphicsView is displaying a QGraphicsScene with a single QGraphicsPixmapItem which contains the current video frame. The background of the view is black.
As long as the frame is smaller than the view, everything is alright, the video frame is displayed in the center of the view and the rest of the view is black. When the view has the same size as the frame, only the frame is shown, (obviously) no background. When the video frame is greater than the view, scrollbars are shown so the user can scroll to see the other parts of the frame.
The problem: When the scrollbars are shown, it is possible to scroll past the video frame. There is a margin of 8 pixels on the bottom and on the right where the background is visible. If the video frame is greater than the view, there should be no background visible and it should not be possible to scroll past the video frame.
I reduced the problem to a short source code that demonstrates the problem, showing a 200x200 px red QPixmap in a QGraphicsView with green background.
#include <QtGui/QApplication>
#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow window;
QPixmap pixmap(200, 200);
pixmap.fill(QColor(255, 0, 0));
QGraphicsScene scene(&window);
scene.addPixmap(pixmap);
QGraphicsView view(&window);
view.setBackgroundBrush(QColor(0, 255, 0));
view.setScene(&scene);
window.setCentralWidget(&view);
window.show();
window.resize(199, 199);
return app.exec();
}
I've also made an image of the problem (the black border isn't included in the example code): http://imgur.com/4X5eyhC
On the left window, the QGraphicsView has the same size as the rectangle, on the right window it is a little bit smaller, so scrollbars are shown. And also the background is visible (it should not be visible).
I already tried setting the sceneRect and various other attributes of QWidget, QGraphicsView and QGraphicsScene but found nothing that changed anything with the problem.
I also tried running the example problem in a virtual machine to exclude the possibility that my Qt/KDE version has a bug.
I have no idea why the background is suddenly shown when there are scrollbars. How can I get rid of that? If that is not possible, do you have an idea how I could work around that?
Thanks in advance.

Problem is a bug with QGraphicsView::fitInView() they don't care about: https://bugreports.qt.io/browse/QTBUG-11945
Instead of reimplementing your own fitInView, just remove the margins from the view when you create it.
view.setViewportMargins(-2, -2, -2, -2)
view.setFrameStyle(QFrame.NoFrame)

What you're seeing is the area of the Window beyond the QGraphicsView. You'll probably also find that you can resize the window and display more of the border.
To fix it, constrain the size of the window to the size of the QGraphicsView. As you've not set this in your example code, it would be the size of the pixmap.
So add these lines after declaring the window: -
window.setMaximumWidth(200);
window.setMaximumHeight(200);
Doing this will restrict the window from being resized greater than those values, so if you need to resize it beyond that, you'll need a larger QGraphicsView and QGraphicsScene.

I tried the snippet from the question but under Python using the PySide wrapper around Qt. The code is almost identical.
import sys
from PySide import QtGui
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
pixmap = QtGui.QPixmap(200, 200)
pixmap.fill(QtGui.QColor(255, 0, 0))
scene = QtGui.QGraphicsScene(window)
scene.addPixmap(pixmap)
view = QtGui.QGraphicsView(window)
view.setBackgroundBrush(QtGui.QColor(0, 255, 0))
view.setScene(scene)
window.setCentralWidget(view)
window.show()
window.resize(199, 199)
sys.exit(app.exec_())
The scroll bars appear but I don't see any green area!
I guess since it is only a wrapper around the QT library it might be a version or system dependent flaw/ not really intended behavior. It would be interesting to try it again with the current version of QT.
(My specs: Windows 7 64bit, Python 2.7.2 64bit, PySide 1.2.1 wrapping Qt 4.8)

This is really just this question in disguise - take a look and vote on https://bugreports.qt.io/browse/QTBUG-42331
In short, fitInView has hardcoded margins and this can cause all kinds of havoc - the least of which is that now you lose a few pixels of display area and might also force unnecessary rescaling.
ps. Make sure to select an answer

I fixed it creating my own fitInView() method. It is basically the same as the original QGraphicView method, except for the margins:
void MyClass::fitInView_fixed(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode)
{
if (!scene() || rect.isNull())
return;
auto unity = transform().mapRect(QRectF(0, 0, 1, 1));
if (unity.isEmpty())
return;
scale(1/unity.width(), 1/unity.height());
auto viewRect = viewport()->rect();
if (viewRect.isEmpty())
return;
auto sceneRect = transform().mapRect(rect);
if (sceneRect.isEmpty())
return;
qreal xratio = viewRect.width() / sceneRect.width();
qreal yratio = viewRect.height() / sceneRect.height();
// Respect the aspect ratio mode.
switch (aspectRatioMode) {
case Qt::KeepAspectRatio:
xratio = yratio = qMin(xratio, yratio);
break;
case Qt::KeepAspectRatioByExpanding:
xratio = yratio = qMax(xratio, yratio);
break;
case Qt::IgnoreAspectRatio:
break;
}
scale(xratio, yratio);
centerOn(rect.center());
}

Related

Video not fitting in properly in QGraphicsView

I am trying a play a video(640 * 360) through rtsp in QGraphicsView. But the issue is that it is not fitting completely within the view and scroll bar is appearing, which should not happen. And moreover, I am able to get the same peace of code working properly in Linux environment but I am getting the issue in windows.
Please find the code snippet below, If anyone can point out the error I am making will be helpful.
scene = new QGraphicsScene(this);
view= new graphicsView();
view->setScene(scene);
videoItem = new QGraphicsVideoItem;
player= new QMediaPlayer;
player->setVideoOutput(videoItem);
view->scene()->addItem(videoItem);
controlLayout = new QHBoxLayout;
controlLayout->setMargin(0);
controlLayout->addWidget(view);
view->setSceneRect(scene->sceneRect());
view->scale(1.97,1.97);
ui.m_pframePlay->setLayout(controlLayout);
ui.m_pframePlay->show();
player->setMedia(QUrl("rtsp:..."));
player->play();
The documentation for QGraphicsView sais about setSceneRect
The scene rectangle defines the extent of the scene, and in the view's case, this means the area of the scene that you can navigate using the scroll bars.
This means, setSceneRect does not resize the visible area of the view but only which area of the scene is visible in the view. So I guess you simply have to resize your view, e.g.
view->resize(scene->width()*1.97, scene->height()*1.97)
(I scaled width/height with 1.97 because you scale your view using factor 1.97 for some reason).

Clear Transparent Background for QWidget

I have a widget as on overlay for another widget. The transparency works fine, as long as I don't clear the background of the overlay.
But I have to clear the widget to acutalize the displayed "effects". I tried to solve the inital problem (the background getting the default color) like described in 20848594, but except changing the color to black it had no effect...
Has anyone an idea why the widget which should be transparent does not display the underlaying widget(s)?
Here the code:
SudokuMarkerOverlayWidget::SudokuMarkerOverlayWidget(QWidget* parent, uint const fieldSize) : QWidget(parent)
{
// Translucent should be the correct one
setAttribute(Qt::WA_TranslucentBackground);
//setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
...
}
void SudokuMarkerOverlayWidget::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint( QPainter::Antialiasing );
// Tried this too, no difference
// painter.setCompositionMode(QPainter::CompositionMode_Source);
// painter.fillRect( this->rect(), Qt::transparent );
painter.setCompositionMode(QPainter::CompositionMode_Clear);
painter.eraseRect( this->rect() );
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
...
}
EDIT:
Just noticed, using CompositionMode Source instead of SourceOver for my semi-transparent painting (gradient seen in first image) also causes them to be a red-to-black-gradient instead of red-to-transparent.
That means every transparency except the inital by WA_TranslucentBackground or WA_NoSystemBackground isn't working.
Widgets in Qt can be either a 'native' or 'alien' type. In one case they're a separate operating system window. In some operating systems a transparent window isn't supported.
You might want to consider using QML if you're after fancy visual effects. That's what it was designed for.

Qt - QGraphicsView without ScrollBar

I am trying to show a picture in it's full view using QGraphicsScene. But when ever I put the QgraphicsScene inside the QGraphicsView, I am getting a scroll bar. I tried so many ways But all are went to veins. So can anybody tell me how to obtain the full view without the scrollbar.
You might be getting scrollbars because the scene is larger than the usable area within the graphics view. By default, a QGraphicsView comes with a 1-pixel margin. To fix this, you can try:
QRect rcontent = graphicsView.contentsRect();
graphicsView.setSceneRect(0, 0, rcontent.width(), rcontent.height());
I had been getting scrollbars because I was manually setting the scene rect to the size of the graphics item I was adding -- which was as large as the QGraphicsView widget. I wasn't taking into account the margin.
QGraphicsView v;
v.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
v.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
To adjust the scrolling programmatically once these have been hidden, use one of the overloads of v.ensureVisible().

Setting QMainWindow at the center of screen

My QMainWindow contains a QGraphicsView, which should've minimum width and height. So, I've used the following code in the QMainWindow constructor:
ui.graphicsView->setMinimumHeight(VIEWWIDTH);
ui.graphicsView->setMinimumWidth(VIEWWIDTH);
Then I used following code to set QMainWindow at the center of the screen:
QRect available_geom = QDesktopWidget().availableGeometry();
QRect current_geom = frameGeometry();
setGeometry(available_geom.width() / 2 - current_geom.width() / 2,
available_geom.height() / 2 - current_geom.height() / 2,
current_geom.width(),
current_geom.height());
But it is not set at the center of the screen. If I omit setMinimumHeight() and setMinimumWidth() from QGraphicsView, then the main window is set at the center of the screen. How to overcome this problem? I'm using Qt 4.5.2.
Thanks.
The problem you are encountering is that Qt will delay many calculations as long as it can. When you set the minimum width and height of your graphics view, it sets a flag somewhere that the window the graphics view is in needs re-layed out. However, it won't do that until it has to... a few milliseconds before it is actually shown on screen. So, when you call rect() on your main window, you are getting the old rectangle, and not the new one.
My best recommendation is to extend the size change event in your main window, and adjust the positioning in that event. You may also have to flag when the window has actually been shown, so that you don't reposition the window if the user resizes it after it has been shown.
Alternately, you could try repositioning the window by extending the show event function and doing it there. It would probably happen before the user actually sees the window, but might flicker on occasion.

How to set an initial size of a QScrollArea?

I know that this is a very specific C++ and Qt related question, but maybe someone can help me, anyway ...
See the code below: I want to display an image within a scroll area. The view port of the scroll area shall have a defined initial size. That means, if the image's size is bigger than the initial size of the view port, scroll bars will be visible, otherwise not.
// create label for displaying an image
QImage image( ":/test.png" );
QLabel *label = new QLabel( this );
label->setPixmap( image.toPixmap() );
// put label into scroll area
QScollArea *area = new QScrollArea( this );
area->setWidget( label );
// set the initial size of the view port
// NOTE: This is what I'd like to do, but this method does not exist :(
area->setViewPortSize( QSize( 300, 300 ) );
It shall be possible to resize the whole application so that the view port will get another size than the initial one.
Unfortunatelly I was not able to find out, how to set the size of the view port. Qt's layout mechanism seems to set a default size for the view port, but up to now I was not able to change it. Setting a new size with
area->setMinimumSize( QSize( 300, 300 ) );
will actually set the demanded size, but then the scroll area looses the ability to get resized to a size smaller than 300x300.
Any ideas?
I think that you are looking at the problem the wrong way. The QScrollArea is just a widget that you put in a frame or QMainWindow. The size of the widget is controlled by the layout of the widget that contains it.
Take a look at this example from Trolltech: Image Viewer Example
You can try:
class MyScrollArea : public QScrollArea
{
virtual QSize sizeHint() const { return QSize( 300, 300 ); }
};
// create label for displaying an image
QImage image( ":/test.png" );
Label *label = new QLabel;
label->setPixmap( image.toPixmap() );
// put label into scroll area
QScollArea *area = new MyScrollArea( this );
area->setWidget( label );
However layout and Qt is amazingly Voodoo. It is IMO its least functional part.
if that doesn't work, try calling QWidget::resize() on various widgets.
Is the scroll area the top level widget? If so, simply call
area->resize(300,300);
If it's inside a hierarchy you need to resize the toplevel appropriately (complex), or set the minimumSize of the area. You could also try to experiment with the LayoutPolicy - assuming the sizeHint is QSize(300,300) you can give it the appropriate size policy according to what's defined in https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum
I don't think you can do exactly that very easily, which is (if I'm reading correctly), size the widget so that the internal area is 300x300. You might be able to fudge it, however, since a scroll area is a type of frame, which inherits from QWidget. This means you could just call area->resize( 300 + fudge, 300 + fudge ), where your fudge values account for the extra bit taken up by the frame's drawing.
I'm not sure this would work in a dynamically resizable dialog, however. I haven't ever done anything quite like this.
If you're trying to display an image inside a scroll area, your best bet isn't going with a label.
You should try using a QGraphicsView/QGraphicsScene/QGraphicPixmapItem (instead of the Scroll Area and label). The performance is far better when displaying images. The scroll area and label will re-draw the image very poorly as you move around using the scroll bars.
For example, you have a ".ui" file with a QGraphicsView on the gui called "qgvImageView" and a QImage called "image"...
QGraphicsScene *scene = new QGraphicsScene(qgvImageView);
QPixmap pixTmp(QPixmap::fromImage(image));
QGraphicsPixmapItem * ppixItem = scene->addPixmap( pixTmp );
ppixItem->setPos(0,0);
Check out the QT Documentation. BTW: This was introduced in Qt 4.2
I'm not sure if this will specifically fix the problem, but there is a chance that the QGraphicsView will react better to what you're trying to do.
How about using
area->setGeometry(int x, int y, int w, int h);