Qt QGraphicsScene does not fitInView() with scrollbars - c++

QGraphicsView::fitInView() seems to ignore the presence of scrollbars, that apparently are overlaid. (It also uses a hardcoded 2 pixel margin.)
There is a related bug report (https://bugreports.qt-project.org/browse/QTBUG-1047) stating that calling fitInView() twice would resolve the problem.
In my case, it does not. Only triggering it twice manually fits regarding the scrollbars. This does not work:
void myGraphicsView::mousePressEvent(QMouseEvent *event) {
if( event->button() == Qt::LeftButton ) {
QGraphicsItem* clicked = scene()->itemAt( mapToScene( event->pos() ) );
qDebug() << clicked << clicked->boundingRect();
fitInView( clicked, Qt::KeepAspectRatio);
fitInView( clicked, Qt::KeepAspectRatio); // doesn't work for me
QGraphicsView::mousePressEvent(event);
return;
}
}
Is there another workaround?
Qt 4.8.1 with MSVC2010

Calling fitInView() twice does work, but you have to let Qt process its events between the two calls.
This also means you end up redrawing the graphicsview twice.
To avoid this, what I do is:
Disable updates
Call fitInView
Call QApplication::processEvents()
Call fitInView again
Enable updates
In your code it would look like this:
bool updateState = updatesEnabled();
setUpdatesEnabled(false);
fitInView(clicked, Qt::KeepAspectRatio);
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
fitInView(clicked, Qt::KeepAspectRatio);
setUpdatesEnabled(updateState);

The cause could be this (at least it looks like it is in the bug report): https://bugreports.qt.io/browse/QTBUG-42331 - please vote on it on the qt bug tracker to up its priority.
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. It can cause runaway resizing and weird instabilities such as what the QTBUG-1047 bug submitter described.
You can fix the problem by reimplementing fitInView, based on the existing implementation but removing it's ridiculous margins. An example of that is available here in python, based on the original C++ version:
https://github.com/nevion/pyqimageview/blob/master/qimageview/widget.py#L276
Let me know if it fixes your problem! That way we can close out that since-2007 bug too.

Related

How to set a custom font size on a widget that adapts accordingly after an application font size change?

I am using Qt4 (I know), and have a custom widget that sets a bold font, with a point size that is 1.5 times the point size of the application font. This works without problems.
The problem now is that in case the application font size is changed, the widget's font size is not updated accordingly (as expected). My initial though was to do something along the following lines:
void MyCustomWidget::changeEvent(QEvent* e)
{
if (e->type() == QEvent::FontChange)
{
setFont(boldAndBigFont());
}
return QWidget::changeEvent(e);
}
This does not work, since the setFont() call will trigger a FontChange event resulting in endless calls to the change event handler. I noticed there is also ApplicationFontChange, which is promising, however that event is not delivered to my custom widget (I verified using an event listener). Looking at the Qt code, the code responsible for propagating the ApplicationFontChange event will only deliver this event to a few select widgets (the main window for example).
So my question is; how to solve this in a nice way? One limitation I have is that I cannot use style sheets.
My current solution leans towards a custom font change event, fired from the main window after receiving ApplicationFontChange, but surely, I cannot be the first person to have this problem...?
Update: I found that calling QApplication::setFont(bigAndBoldFont(), "MyCustomWidget"); works as well. I do not particularly like it since I would rather keep this styling behavior tied to the implementation of the custom widget.
I can't vouch for Qt4 but the following changeEvent implementation appears to work as expected with Qt5...
virtual void changeEvent (QEvent *event) override
{
if (event->type() == QEvent::FontChange) {
/*
* Run the default handler for the event.
*/
super::changeEvent(event);
/*
* Now get the application font and create the desired font based on
* that.
*/
auto app_font = qApp->font();
auto desired_font = QFont(app_font.family(), 1.5 * app_font.pointSize(), QFont::Bold);
/*
* If the font we now have is the desired font then fine, otherwise
* set it.
*/
if (font() != desired_font) {
setFont(desired_font);
}
event->accept();
}
super::changeEvent(event);
}

ListView not initially showing data

My QML ListView doesn't show my data until I perturb it with the mouse (e.g. just drag it up and down.) After this the view shows the model without issue until it empties, and then I once again need to perturb it to get it working again. Is there way to kick this ListView into working?
I'm using Qt 5.8 on Linux 14.04. My model is a subclass of QAbstractListModel. I build it by following the AbstractItemModel Example. The main difference is that my list model is a property of an entity, rather than being set with setContextProperty in main.cpp.
There are a few similar issues here on SO about the ListViews not updating, but none seem to only have an issue at the start. Most of them relate to the OP calling dataChanged manually instead of beforeInsertRows() & endInsertRows() - both methods I'm calling (see below.)
My ListView is in an item loaded with a SceneLoader.
I posted all the relevant code here, because I'm a little suspicious of how I use the Layouts on my ListView (maybe that's causing it? Maybe my hierarchy is broken? I haven't been able to prove that though.)
In short though,
ListView:
ListView {
anchors.fill: parent
model: sceneGraph.blobs
delegate: delegate
}
BlobModel.cpp:
auto BlobModel::addBlob(const BlobPointDataPtr& data) -> void
{
// ...
// Each blob has a uuid
const auto idx = Contains(uuid);
if (-1 != idx)
{
blobs_[idx]->Update(data);
Q_EMIT dataChanged(createIndex(idx, 0), createIndex(idx, 0));
}
else
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
blobs_ << new Blob{data, id_count_}; id_count_++;
endInsertRows(); // responsible for the QQmlChangeSet
Q_EMIT dataChanged(createIndex(rowCount(), 0), createIndex(rowCount(), 0));
}
}
Also, on my terminal, I receive the message:
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
This seems to be emitted by endInsertRows(), but I'm not sure why. In the past the solution has been to register the missing type, e.g. qRegisterMetaType<QQmlChangeSet*>("QQmlChangeSet"); but this seems not to be a public type with Qt, and because everything mostly works without it, I'm not sure missing that is the exact issue.
The problem, as pointed out in the comments, was that I was modifying my model outside of the main thread.
My code was set up so that another thread would trigger additions to my model by directly calling addData. The reason my minimal example wasn't able to replicate this was because in it I used a QTimer to simulate the other thread, however QTimer also runs on the main thread.
The solution was to change my direct call to addData(data) to emitted a signal to do the addition, thus moving the actual work back to the main thread.

Disable splitter cursor for QDockWidget

I have the following widget structure. CDockWidgetInfoBar is just a QDockWidget derived class
When I move over the QDockWidget (CDockWidgetInfoBar), I see this splitter cursor.
Where is it coming from? Can I disable it? Is it part of QDockWidgetLayout? However, QDockWidgetLayout is Qt private and shall not be used.
Any ideas?
It seems to be impossible or extremely painful for system.
I tried to do this as I did this here: How can I prevent transform cursor to SplitHCursor when it's under border between QHeaderView sections
But the main problem, that resize cursor appears before QEvent::Enter event occurs. (if you run next code, you will see resize cursor first, but you will not see "added" word). As I know, there is no any event which can catch cursor when it moves near edge of widget. So it is very difficult to catch this event. There is another way. In mouseMoveEvent every time check is cursor near the dock widget. But I think that it is extremely inefficient.
I wrote this code:
if (obj == ui->dockWidget && event->type() == QEvent::Enter)
{
qApp->setOverrideCursor(QCursor(Qt::ArrowCursor));
qDebug() << "added";
}
if (obj == ui->dockWidget && event->type() == QEvent::Leave)
{
qApp->restoreOverrideCursor();
}
But it really works after user trying to float dock.
I know that it is not full answer, but maybe it helps or prove that it is very difficult. Anyways, if someone will find efficient solution of this problem, it will be extremely great.

qt resize window after widget remove

I'm adding widget in layout
ui->horizontalLayout->addWidget(tabwidget);
and qmainwindow resizes itself. But then I do
tabwidget->setVisible(false);
qs = sizeHint();
resize(qs);
I get the size like tabwidget was not removed from window.
I've made new button
void MainWindow::on_pushButton_2_clicked()
{
qs = sizeHint();
resize(qs);
}
and it gives correct size.
Seems I need some update function but I can't find it. Please advice
This is caused by a long-time internal Qt issue (I remember experiencing it first with Qt3). The top widget needs to receive an event to truly update its geometry, and know its boundaries. This event seems to be always received after the resize event generated by the layout, and therefore it is always too late to shrink the widget.
The solution posted in the accepted answer works. However a simpler solution posted below also works, when added after the layout :
layout()->removeWidget( widget );
QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
resize( sizeHint() );
Basically all we need is to let the event loop run and deliver the necessary events so the top widget geometry is updated before resize() is run.
Note that this code might have side effects if you have multiple threads running, or there are events delivered to your slots. Hence it is safer not to have any code in this function after resize().
If the button slot gives you the correct result then you can always call the sizeHint() and subsequent resize() in a slot which is called by a single shot timer:
void MainWindow::fixSize()
{
QSize size = sizeHint();
resize(size);
}
void MainWindow::methodWhereIHideTheTabWidget()
{
tabwidget->setVisible(false);
QTimer::singleShot(0, this, SLOT(fixSize()));
}
This timer is set to zero delay. This means that the slot will be called immediatelly when the program returns to the main loop and hopefully after the internal widget state gets updated. If this doesn't resolve your problem you may try replacing zero with 1.

Weird bug in Qt application

In my application, I have my re-implemented QGraphicsView checking for a mouseReleaseEvent(), and then telling the item at the position the mouse is at to handle the event.
The QGraphicsItem for my view is made up of two other QGraphicsItems, and I check which one of the two is being clicked on (or rather having the button released on), and handle the respective events.
In my Widget's constructor, I set one of the items as selected by default, using the same methods I used when the items detect a release.
When I debugged, I found that for the LabelItem, select is called without a problem from the constructor (and the result is clear when I first start the application). But, when I click on the items, the application terminates. I saw that I was getting into the select function, but not leaving it. So the problem is here.
Which is very weird, because the select function is just a single line setter.
void LabelItem::select()
{
selected = true;
}
This is the mouseReleaseEvent;
void LayerView::mouseReleaseEvent(QMouseEvent *event)
{
LayerItem *l;
if(event->button() == Qt::LeftButton)
{
l = (LayerItem *) itemAt(event->pos());
if(l->inLabel(event->pos()))
{ //No problem upto this point, if label is clicked on
l->setSelection(true); //in setSelection, I call select() or unselect() of LabelItem,
//which is a child of LayerItem, and the problem is there.
//In the constructor for my main widget, I use setSelection
//for the bottom most LayerItem, and have no issues.
emit selected(l->getId());
}
else if(l->inCheckBox(event->pos()))
{
bool t = l->toggleCheckState();
emit toggled(l->getId(), t);
}
}
}
When I commented the line out in the function, I had no errors. I have not debugged for the other QGraphicsItem, CheckBoxItem, but the application terminates for its events as well. I think the problem might be related, so I'm concentrating on select, for now.
I have absolutely no clue as to what could have caused this and why this is happening. From my past experience, I'm pretty sure it's something simple which I'm stupidly not thinking of, but I can't figure out what.
Help would really be appreciated.
If the LabelItem is on top of the LayerItem, itemAt will most likely return the LabelItem because it is the topmost item under the mouse. Unless the LabelItem is set to not accept any mouse button with l->setAcceptedMouseButtons(0).
Try to use qgraphicsitem_cast to test the type of the item. Each derived class must redefine QGraphicsItem::type() to return a distinct value for the cast function to be able to identify the type.
You also could handle the clicks in the items themselves by redefining their QGraphicsItem::mouseReleaseEvent() method, it would remove the need for the evil cast, but you have to remove the function LayerView::mouseReleaseEvent() or at least recall the base class implementation, QGraphicsView::mouseReleaseEvent(), to allow the item(s) to receive the event.
I have seen these odd behaviours: It was mostly binary incompatibility - the c++ side looks correct, and the crash just does not make sense. As you stated: In your code the "selected" variable cannot be the cause. Do you might have changed the declaration and forgot the recompile all linked objects. Just clean and recompile all object files. Worked for me in 99% of the cases.