On Sun, 4 Oct 2020 06:22:45 GMT, Sergey Bylokhov <s...@openjdk.org> wrote:

>> Hello.
>> Please review the fix for jdk.
>> 
>> Old review request:
>> https://mail.openjdk.java.net/pipermail/awt-dev/2020-July/015991.html
>> 
>> 
>> (Note: the fix use API available since Windows 8.1: WM_DPICHANGED, but it 
>> should be fine for
>> Windows 7, because it does not support different DPI for different monitors)
>> 
>> ========================================================
>> Short description:
>>      In the multi-screen configurations when each screen have different DPI
>>      we calculate the bounds of each monitor by these formulas:
>> 
>>          userSpaceBounds = deviceX / scaleX, deviceY / scaleY, deviceW / 
>> scaleX, deviceH / scaleY
>>          devSpaceBounds  = userX * scaleX, userY * scaleY, userW * scaleX, 
>> userH * scaleY
>> 
>>   This formula makes the next configuration completely broken:
>>     - The main screen on the left and 100% DPI
>>     - The second screen on the right and 200% DPI
>>   When we translate the bounds of the config from the device space to the 
>> user's space,
>>      the bounds of both screen overlap in the user's space, because we use 
>> bounds of
>>      the main screen as-is, and the X/Y of the second screen are divided by 
>> the scaleX/Y.
>> 
>>   Since the screens are overlapped we cannot be sure how to translate the 
>> user's space
>>    coordinates to device space in the overlapped zone.
>>   As a result => we use the wrong screen
>>                    => got wrong coordinates in the device space
>>                      => show top-level windows/popups/tooltips/menus/etc on 
>> the wrong screen
>> 
>>    The proposed solution for this bug is to change the formulas to these:
>> 
>>          userSpaceBounds = deviceX, deviceY, deviceW / scaleX, deviceH / 
>> scaleY
>>          devSpaceBounds  = userX, userY, userW * scaleX, userH * scaleY
>> 
>>    In other words, we should not transform the X and Y coordinates of the 
>> screen(the top/left corner). This will
>>    complicate the way of how we transform coordinates on the screen: user's 
>> <--> device spaces:
>> 
>>    Before the fix:
>> 
>>          userX = deviceX * scaleX;
>>          deviceX = userX / scaleX;
>> 
>>    After the fix(only the part appeared on this screen should be scaled)
>> 
>>          userX = screenX + (deviceX - screenX) * scaleX
>>          deviceX = screenX + (userX - screenX) / scaleX
>> 
>>    Note that these new formulas are applicable only for the coordinates on 
>> the screen such as X and Y.
>>    If we will need to calculate the "size" such as W and H then the old 
>> formula should be used.
>> 
>>    The main changes for the problem above are:
>>    - Skip transformation of X and Y of the screen bounds:
>>        
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsConfig.cpp.sdiff.html
>>    - A number of utility methods in java and native:
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp.sdiff.html
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/share/classes/sun/java2d/SunGraphicsEnvironment.java.sdiff.html
>>    
>> 
>> ========================================================
>> Long description:
>>      Unfortunately, the changes above are not enough to fix the problem when 
>> different monitors
>>      have different DPI, even if the bounds are *NOT* overlapped.
>> 
>>    - Currently, when we try to set the bounds of the window, we manually 
>> convert it in "java" to the
>>      expected device position based on the current GraphicsConfiguration of 
>> the peer, and then
>>      additionally, tweak it in native using the device scale stored in 
>> native code. Unfortunately
>>      this two scale might not be in sync:(after we use the 
>> GraphicsConfiguration scale in peer,
>>      the config might be changed and the tweak in native will use a 
>> different screen).
>> 
>>      As a fix I have moved all transformation from the peer to the native 
>> code, it will be executed
>>      on the toolkit thread:
>>      See the change in WWindowPeer.setBounds() and awt_Window.cpp 
>> AwtWindow::Reshape
>>          
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java.sdiff.html
>>          
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/native/libawt/windows/awt_Window.cpp.sdiff.html
>>      I think at some point we should delete all transformation in java, and 
>> apply it in the native code only.
>>      
>> 
>>    - We had a special code that tracked the dragging of the window by the 
>> user from one screen to another,
>>      and change the size of the window only when the user stops the drag 
>> operation. I've proposed to use standard Windows
>>      machinery for that via WM_DPICHANGED: 
>> https://docs.microsoft.com/en-us/windows/win32/hidpi/wm-dpichanged
>>      As a result, Windows will provide the "best" windows bounds on the new 
>> screen. Note that the code has a
>>      workaround for https://bugs.openjdk.java.net/browse/JDK-8249164
>>   
>>    - I've also fix a variation of the bug previously fixed on macOS 
>> https://hg.openjdk.java.net/jdk/jdk/rev/b5cdba232fca
>>      If we try to change the position of the window, and Windows ignores 
>> this request then we need to call all "callbacks" manually, otherwise, the 
>> shared code will use the bounds ignored by the Windows.
>>      See the end of void AwtWindow::Reshape():
>>      
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/native/libawt/windows/awt_Window.cpp.sdiff.html
>> 
>>   -  Currently the HW componets such as Canvas scales everything based on 
>> such code:
>> 
>>           770 int AwtComponent::ScaleUpX(int x) {
>>           4771     int screen = 
>> AwtWin32GraphicsDevice::DeviceIndexForWindow(GetHWnd());
>>           4772     Devices::InstanceAccess devices;
>>           4773     AwtWin32GraphicsDevice* device = 
>> devices->GetDevice(screen);
>>           4774     return device == NULL ? x : device->ScaleUpX(x);
>>           4775 }
>> 
>>   But it does not work well if the smaller part of the top-level window is 
>> located on one screen1(DPI=100) but
>>      the biggest part is located on the screen2(DPI=200). If the Canvas is 
>> located on the "smaller" part of the
>>      window, then the canvas will use DPI=100 for scale, but the whole 
>> window will use DPI=200
>> 
>>    As a fix, all HW components will try to use the scale factor of the 
>> top-level window, see AwtComponent::GetScreenImOn:
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/native/libawt/windows/awt_Component.cpp.sdiff.html
>> 
>>    - Our current implementation does not take care of the case when the HW 
>> component bounds are updated when the top-level
>>      the window is moved to another screen. For example, if the window does 
>> not use LayoutManager and the user sets some specific bounds for the Canvas. 
>> It is expected that the size of the Canvas will be updated when the window 
>> will be moved to another screen, but only HW top-level window and Swing 
>> components will update its size.
>> 
>>      As a fix I suggest to resync the bounds of the HW components if the 
>> GraphicsCOnfiguration is changed, see WComponentPeer.syncBounds():
>>          
>> http://cr.openjdk.java.net/~serb/8211999/webrev.04/src/java.desktop/windows/classes/sun/awt/windows/WComponentPeer.java.sdiff.html
>> 
>>    - Note that the current fix is for Windows only, but the bug exists on 
>> Linux as well, so I have to create a kind of "ifdef" in the
>>      Robot class, on windows we will use the new logic, on other platforms 
>> the old logic will be used.
>> 
>>    - The win32GraphicsDevice incorrectly sets the size of the full-screen 
>> window. It uses the display mode width/height(which are stored in pixels), 
>> but the bounds of the graphics config(which are stored in user's space) 
>> should be used.
>> 
>> ========================================================
>> Some other bugs which are not fixed.
>> 
>>     - We have a lot of places where we mix(unintentionally) the coordinate 
>> in user's space and device space.
>>       For example when we create the component we read x/y/width/height by 
>> the JNI(of course in a user's space)
>>       but pass this coordinates as-is to the ::CreateWindowEx() which is 
>> incorrect. It works currently
>>       because later we reshape the component to the correct bounds. Will fix 
>> it later.
>> 
>>     - When the frame is iconized and we try to save a new bounds for the 
>> future use, we store the
>>       coordinates in the user's space to the field which use device space:
>>       
>> https://github.com/openjdk/jdk/blob/master/src/java.desktop/windows/native/libawt/windows/awt_Frame.cpp#L664
>> 
>> Probably there are some other bugs and possibility to cleanups, but I would 
>> like to do that in separate issues.
>>      
>> 
>> ========================================================
>> The tests
>>     - I have updated a couple of the tests to check multiple screens if 
>> possible, it helps to found some bugs
>>       in my initial implementation
>>     - Four new tests were added: SetComponentsBounds, SlowMotion, 
>> WindowSizeDifferentScreens, FullscreenWindowProps
>>     - One test is moved from the closed repo(I made a small cleanup of it): 
>> ListMultipleSelectTest
>> 
>> ========================================================
>> I have run jck, regression tests in different configurations, when the main 
>> screen is on the left, up, down,
>> right, and have different DPI such as 100, 125, 150, and 200. No new issues 
>> were found so far.
>> 
>> The mach5 is also green.
>> 
>> PS: hope I did not forget some important information, will send it later if 
>> any.
>
> Sergey Bylokhov has updated the pull request with a new target base due to a 
> merge or a rebase. The pull request now contains six commits:
> 
>  - Merge branch 'master' into JDK-8211999
>  - Update FullscreenWindowProps.java
>  - Merge branch 'master' into JDK-8211999
>  - Fix fullscreen in HiDPI mode
>  - self review
>  - Initial fix version

src/java.desktop/share/classes/sun/java2d/SunGraphicsEnvironment.java line 445:

> 443:      * @return the point which uses device space(pixels)
> 444:      */
> 445:     public static Point toDeviceSpace(int x, int y) {

Does this function converts _absolute_ coordinates?
I see it uses a different formula to convert: the coordinates are scaled as 
opposed to `-Abs` set of functions.

src/java.desktop/windows/classes/sun/awt/windows/WComponentPeer.java line 539:

> 537:         winGraphicsConfig = (Win32GraphicsConfig)gc;
> 538:         if (gc != null && !old.equals(gc.getDefaultTransform())) {
> 539:             syncBounds(); // the bound of the peer depends on the DPI

Suggestion:

            syncBounds(); // the bounds of the peer depend on the DPI

src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java line 311:

> 309: 
> 310:     final void syncBounds(){
> 311:         // The Windows will take care of the top-level 
> window/frame/dialog,

Suggestion:

    final void syncBounds() {
        // Windows will take care of the top-level window/frame/dialog,

Does it override another implementation of `syncBounds()`? Or does it implement 
a method in an interface?
Shall it have `@Override` annotation?

src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp line 
692:

> 690: }
> 691: 
> 692: void AwtWin32GraphicsDevice::InitDesktopScales() {

Suggestion:

void AwtWin32GraphicsDevice::InitDesktopScales()
{
Shall the opening brace remain on the next line as it was for consistency with 
other functions?

src/java.desktop/windows/native/libawt/windows/awt_Window.cpp line 1211:

> 1209:     int screen = AwtWin32GraphicsDevice::GetScreenFromHMONITOR(monitor);
> 1210:     AwtWin32GraphicsDevice *device = devices->GetDevice(screen);
> 1211:     // Try set the correct size and jump to the correct location, even 
> if it is

Suggestion:

    // Try to set the correct size and jump to the correct location, even if it 
is

src/java.desktop/windows/native/libawt/windows/awt_Window.cpp line 2197:

> 2195: // The shared code is not ready to the top-level window which crosses a 
> few
> 2196: // monitors with different DPI. Popup windows will start to use wrong 
> screen,
> 2197: // will be placed in the wrong place and will be use wrong size, see 
> 8249164

Suggestion:

// will be placed in the wrong place and will use the wrong size, see 8249164

src/java.desktop/windows/native/libawt/windows/awt_Window.cpp line 2217:

> 2215:                     y = y < bounds.top ? bounds.top : y;
> 2216:                     x = (x + w > bounds.right) ? bounds.right - w : x;
> 2217:                     y = (y + h > bounds.bottom) ? bounds.bottom - h : y;

Can't this adjustment cause `x`, `y` to become less than `bounds.left` and 
`bounds.top` correspondingly?
Shall it adjust the width and height?

src/java.desktop/windows/native/libawt/windows/awt_Window.cpp line 3987:

> 3985: }
> 3986: 
> 3987: } /* extern "C" */

Probably, it's better to preserve the line end at the end of the file.

test/jdk/java/awt/Component/SetComponentsBounds/SetComponentsBounds.java line 
102:

> 100:                 }
> 101:                 if (bounds.height > HEIGHT) {
> 102:                     // different check for HEIGHT, it depends from the 
> font

Suggestion:

                    // different check for HEIGHT, it depends on the font

test/jdk/java/awt/List/ListMultipleSelectTest/ListMultipleSelectTest.java line 
90:

> 88:             for (int i = 0; i < aList.getItemCount(); i++) {
> 89:                 //select all items in the List
> 90:                 mousePress(p.x + listSize.height / 2, p.y + stepY / 2 + 
> stepY * i);

x be based on width?
                mousePress(p.x + listSize.width / 2, p.y + stepY / 2 + stepY * 
i);

test/jdk/java/awt/Window/WindowSizeDifferentScreens/WindowSizeDifferentScreens.java
 line 110:

> 108:             case "dialog" -> new Dialog((Dialog) null);
> 109:             case "frame" -> new Frame();
> 110:             default -> throw new IllegalStateException("Unexpected: " + 
> top);

`IllegalArgumentException` more appropriate?
            default -> throw new IllegalArgumentException("Unexpected: " + top);

test/jdk/javax/swing/JTextArea/8149849/DNDTextToScaledArea.java line 81:

> 79:         robot.mousePress(InputEvent.BUTTON1_MASK);
> 80:         robot.mouseRelease(InputEvent.BUTTON1_MASK);
> 81:         robot.waitForIdle();

Why is clicking the `dstTextArea` required?

-------------

PR: https://git.openjdk.java.net/jdk/pull/375

Reply via email to