Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a bug with the Qt5 backend with mixed resolution displays #8931

Merged
merged 5 commits into from
Jul 31, 2017

Conversation

astrofrog
Copy link
Contributor

When mixed-resolution displays are present, the _dpi_ratio attribute on the canvas may change between paintEvents, which means that we need to change the size of the canvas. However, the underlying canvas is only resized if a resizeEvent is emitted, so we check whether _dpi_ratio has changed between paintEvents, and if so we emit a fake resizeEvent for the widget.

Fixes #8061

Note that while this fixes the issue described in #8061 which was that one could end up with weird visual issues such as:

screenshot

the plot for the lower DPI display still has too large fonts/linewidths, but I think that is a separate bug, which I'll try and fix in another PR.

cc @tacaswell @efiring

When mixed-resolution displays are present, the _dpi_ratio attribute on the canvas may change between paintEvents, which means that we need to change the size of the canvas. However, the underlying canvas is only resized if a resizeEvent is emitted, so we check whether _dpi_ratio has changed between paintEvents, and if so we emit a fake resizeEvent for the widget.
# We use self.resizeEvent here instead of QApplication.postEvent
# since the latter doesn't guarantee that the event will be emitted
# straight away, and this causes visual delays in the changes.
self.resizeEvent(event)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also trigger a paint event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might do - I'm investigating

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently not - at least the paintEvent method does not get called again as a result.

@astrofrog
Copy link
Contributor Author

astrofrog commented Jul 24, 2017

@tacaswell @efiring - I think I've got things behaving as they should: with mixed resolution displays, the plots look the same on all displays!

There were fundamentally two issues here:

  • The figure.dpi should change when changing monitors
  • The canvas size and the figure dpi should be changed before the first paint event completes

My approach is to keep track in paintEvent of what the last dpi_ratio was when the canvas was drawn. If it has changed, the figure DPI is updated, then the canvas size.

However, note that changing the DPI normally causes the canvas to be resized, which we deal with ourselves already, so I've added a new forward argument to _set_dpi which is passed to set_size_inches.

One small worry I have is that testing this is difficult/impossible, so I'm not sure how we can prevent regressions? - update: regression test added

This is ready for review.

Similarly to resizeEvent, we tell _set_dpi to not change the canvas size since we are doing it ourselves.
@astrofrog
Copy link
Contributor Author

I think the AppVeyor failure is unrelated? (other PRs seem to suffer from it)

@tacaswell tacaswell added this to the 2.1 (next point release) milestone Jul 25, 2017
@@ -171,6 +198,10 @@ def __init__(self, figure):
self.figure._original_dpi = self.figure.dpi
self.figure.dpi = self._dpi_ratio * self.figure._original_dpi

def _update_figure_dpi(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use this in the __init__ above as well (and get rid of the hasattr)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! :)

@astrofrog
Copy link
Contributor Author

@tacaswell - change implemented, and all tests are passing

Copy link
Member

@dstansby dstansby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 can confirm the original bug, and that this fixes it (using a retina Macbook and non-retina external screen)

@dstansby dstansby merged commit 51cfbd8 into matplotlib:master Jul 31, 2017
@anntzer anntzer mentioned this pull request Aug 2, 2017
6 tasks
Copy link
Member

@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is not passing for me.

Plus a few minor tweaks.

# mixed resolution displays if dpi_ratio is changing between painting
# events.
if (self._dpi_ratio_prev is None or
self._dpi_ratio != self._dpi_ratio_prev):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under-indented.

assert size.width() == 200
assert size.height() == 80
assert_equal(qt_canvas.get_width_height(), (600, 240))
assert_equal(fig.get_size_inches(), (5, 2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A regular assert would be fine here; you didn't need to import assert_equal.

qApp.processEvents()

# The DPI and the renderer width/height change
assert fig.dpi == 240
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't passing for me here; I think you need to do a little more to trigger an update.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, this seems to pass on the CI though - or does the test not get run on the CI? What platform and exact Qt version are you using?

Copy link
Member

@QuLogic QuLogic Aug 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any obvious skips in the CI, but you can confirm with @anntzer that the tests should be running. The odd thing is I don't see any Qt4 skips and one would normally preclude the other; maybe they just luckily ran on different processes?

I am running on Fedora+conda using the latest defaults Qt package (conda-forge's Qt seems to be broken) 5.6.2-5 with Python 3.6.

Copy link
Contributor

@anntzer anntzer Aug 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost certainly related to #8659 (causing some of the tests not being run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Scaling issues with PyQt5 when using mixed resolution displays
5 participants