Repository: vispy/vispy

Issue:
SceneCanvas central_widget display area is wrong (PyQt5 & SDL2)
Hi,

I tried this code, but plot area is wrong because SceneCanvas central_widget is not placed at correct area.
https://github.com/vispy/vispy/issues/1189#issuecomment-198597473

I think the bug is because of PyQt5/SDL2, but I'm not familiar with Qt...
<img width="764" alt="2017-07-23 1 24 02" src="https://user-images.githubusercontent.com/5352478/28493057-442f397e-6f4a-11e7-8cc9-2f5a0e2d9b2b.png">

I tried PyQt5, PyGlet, Glfw and SDL2 as backend app.
In PyQt5 and SDL2, display area is wrong.
In PyGlet and Glfw, display area is correct.
PyGlet is a little slow, so I'll use Glfw.

My environment is...
OS: macOS Sierra
vispy: 0.4.0
Python: 3.6.2
Qt: 5.9.1
PyQt: 5.9
SLD2: 2.0.5
PySDL2: 0.9.5
Glfw: 3.2.1
PyGlet: 1.2.4

vispy, PyGlet and PySDL2 are installed by pip.
Python, Qt, PyQt, SLD2 are installed by Homebrew.


Discussion:
@hayashikun Thanks for the report. Can you try your code with PyQt4 and let me know how it goes? In the past I had noticed some issues with PyQt5 but assumed it was my fault. I haven't tried anything recently.
This is most likely related to OSX HiDPI

@Eric89GXL Very possible. I am on a Mac too. Wasn't this addressed by a PR a while ago (can't find it now)?

@hayashikun Could you try installing vispy from the github `master` branch and trying PyQt5 again? If so and it still fails, what about PyQt4?
Thank you for your quick response.
I tried https://github.com/vispy/vispy/issues/1189#issuecomment-198597473 code in vispy master (0.5.0.dev0) with PyQt5, it still fails and it behavior is different from 0.4.0.
![output](https://user-images.githubusercontent.com/5352478/28493902-8183aec6-6f5a-11e7-85a7-9eb47f5e27a6.gif)

And, I need some time to try with PyQt4 because Qt4 and PyQt4 can't install via Homebrew. 😭 
@hayashikun Anaconda might be a good place to start and force `conda install pyqt<5`.
Hi,
I installed Anaconda and tried.
It succeeded with PyQt4!
PyQt5 installed by Anaconda still fail.
Thanks. I'll have to look in to this later. Not sure I'll have any more time this weekend.
Hello,
I read source code and search the cause of this bug.

First,  I've compared PyQt5 and Glfw running and check variables by putting breakpoints.

See gif I attached in my before comment, initially displayed view is correct even with PyQt5.
A mouse click cause this invalid displaying.

In my environments I told before, `pixel_scale` of Canvas is different between Glfw (correct) and PyQt5 (incorrect) **after mouse clicking**.
https://github.com/vispy/vispy/blob/master/vispy/scene/canvas.py#L237
https://github.com/vispy/vispy/blob/master/vispy/app/canvas.py#L351

Just after `app.run()`, `pixel_scale = 2` both PyQt5 and Glfw. So, both display is correct.
But after click some point,  `pixel_scale = 1` in PyQt5 so display is incorrect. (in Glfw, `pixel_scale = 2` so display is correct)

In conclusion, the reason why pixel_scale is changed is `QtBaseCanvasBackend._physical_size` is changed in `__init__`.
https://github.com/vispy/vispy/blob/master/vispy/app/backends/_qt.py#L290

Correct `_physical_size` setting is done in this below.
https://github.com/vispy/vispy/blob/master/vispy/app/backends/_qt.py#L694

By deleting L290, it run correctly.

I'd like to submit PR for this bug fixing and discuss about detail in the PR.
I'm not sure I understand your solution. Does `resizeGL` get called by the Qt system when the Qt Window is first generated? It isn't called from within vispy from what I can tell so it makes sense to use that as an initialization step. However, I also notice that `self._vispy_set_size(*p.size)` is called in the `__init__` method. Also removing that line may have consequences because it is used in `_vispy_get_physical_size()`. Any instance attribute should be initialized in the `__init__` method.

Regardless, the biggest problem I have with this change is that it changes something in the `_qt.py` module that is shared between PyQt4 and PyQt5 without showing why it works in one and not the other. If I'm completely missing something about your solution, please let me know.
I haven't researched it too much, but it looks like Qt5 has the method `devicePixelRatio()` that we could use in cases like this. If we used something like this we'd have to be careful that mouse events get treated properly too.

Resolving patch:
diff --git a/vispy/app/backends/_qt.py b/vispy/app/backends/_qt.py
--- a/vispy/app/backends/_qt.py
+++ b/vispy/app/backends/_qt.py
@@ -287,7 +287,12 @@ def __init__(self, *args, **kwargs):
         # Qt supports OS double-click events, so we set this here to
         # avoid double events
         self._double_click_supported = True
-        self._physical_size = p.size
+        if hasattr(self, 'devicePixelRatio'):
+            # handle high DPI displays in PyQt5
+            ratio = self.devicePixelRatio()
+        else:
+            ratio = 1
+        self._physical_size = (p.size[0] * ratio, p.size[1] * ratio)
 
         # Activate touch and gesture.
         # NOTE: we only activate touch on OS X because there seems to be


Test patch: