Skip to content

Conversation

arunjose696
Copy link
Contributor

@arunjose696 arunjose696 commented Aug 22, 2025

When a zoom change occurs, the OS automatically scales the window based on the scale factor. However, since fonts do not always scale linearly, this can cause text in dialogues to be cut off.

This change adds a ZoomChanged listener to MessageDialog. When a ZoomChanged event is triggered, the shell size is recomputed so that the contents are fit properly and text is not clipped.

Steps to reproduce

Run the below snippet on setup with 100% primary monitor zoom and move it to 150% monitor

Use the VM arguments

-Dswt.autoScale=quarter
-Dswt.autoScale.updateOnRuntime=true
package org.eclipse.swt.snippets;

import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.layout.*;
import org.eclipse.jface.preference.*;
import org.eclipse.jface.widgets.*;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;

public class SnippetTextOnlyDialog {

    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);

        // Main dialog
        MessageDialog dialog = new MessageDialog(shell,
            "Status Check",
            null,
            "the numbers one two three\nfour five six seven eight nine ten\nfour five six seven eight nine ten",
            MessageDialog.INFORMATION,
            0,
            IDialogConstants.PROCEED_LABEL,
            IDialogConstants.CANCEL_LABEL) {

            private Button proceedButton;

            @Override
            protected Control createCustomArea(Composite parent) {
                var radioButtonFactory = WidgetFactory.button(SWT.RADIO | SWT.WRAP);

                // First radio button
                Button option1 = radioButtonFactory
                    .text("the numbers one two three\nfour five six seven eight nine ten\nfour five six seven eight nine ten")
                    .create(parent);
                GridDataFactory.swtDefaults()
                    .hint(250, SWT.DEFAULT)
                    .indent(0, 5)
                    .applyTo(option1);

                // Second radio button
                Button option2 = radioButtonFactory
                		.text("the numbers one two three\nfour five six seven eight nine ten\nfour five six seven eight nine ten")
                    .create(parent);
                GridDataFactory.swtDefaults()
                .hint(250, SWT.DEFAULT)
                    .indent(0, 5)
                    .applyTo(option2);



                LinkFactory.newLink(SWT.WRAP)
                .text("the numbers one two three\nfour five six seven eight nine ten\nfour five six seven eight nine ten") // hardcoded text
                .onSelect(e -> {
                    PreferenceDialog dialog = new PreferenceDialog(
                            parent.getShell(),
                            new org.eclipse.jface.preference.PreferenceManager()
                    );
                    dialog.setBlockOnOpen(false);
                    dialog.open();
                    this.close();
                })
                .create(parent);
//

                return parent;
            }

            @Override
            protected int getShellStyle() {
                // Add RESIZE and MAX style bits
                return super.getShellStyle() | SWT.RESIZE | SWT.MAX;
            }

            @Override
            protected void createButtonsForButtonBar(Composite parent) {
                super.createButtonsForButtonBar(parent);
                proceedButton = getButton(0);
                proceedButton.setEnabled(false); // disabled by default
                getButton(1).forceFocus(); // focus Cancel instead of auto-selecting radio
            }
        };

        dialog.open();

        // Event loop
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        display.dispose();
    }
}

Expected Behavior:
With the changes in this PR and in PR #2446 , the dialog text should scale correctly and not be cut off when moving between monitors with different DPI settings.

Actual Behavior (without changes):
Text in the dialog is cut off on high-DPI monitors.

Without Changes With Changes
Without changes With changes

Depends on eclipse-platform/eclipse.platform.swt#2446

arunjose696 added a commit to vi-eclipse/eclipse.platform.swt that referenced this pull request Aug 22, 2025
zoomChangedEvent

This change ensures that setBounds is applied before the
zoomChangedEvent is dispatched.
By doing so, event listeners of ZoomChanged can modify the shell size
additionally if required.

For example, this is useful in
eclipse-platform/eclipse.platform.ui#3212
, where the shell size needs to be set by the ZoomChanged listener.
Copy link
Contributor

github-actions bot commented Aug 22, 2025

Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit 08e8683.

♻️ This comment has been updated with latest results.

arunjose696 added a commit to vi-eclipse/eclipse.platform.swt that referenced this pull request Aug 25, 2025
zoomChangedEvent

This change ensures that setBounds is applied before the
zoomChangedEvent is dispatched.
By doing so, event listeners of ZoomChanged can modify the shell size
additionally if required.

For example, this is useful in
eclipse-platform/eclipse.platform.ui#3212
, where the shell size needs to be set by the ZoomChanged listener.
@arunjose696 arunjose696 force-pushed the arunjose696/400/MessageDialogue branch from dc91692 to bda4ceb Compare August 25, 2025 07:20
@arunjose696 arunjose696 marked this pull request as ready for review August 25, 2025 14:21
Copy link
Contributor

@fedejeanne fedejeanne left a comment

Choose a reason for hiding this comment

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

Thank you for the proposed solution, @arunjose696 . I was thinking this issue might also occur in other subclasses of IconAndMessageDialog (maybe even subclasses of org.eclipse.jface.dialogs.Dialog) so could you please try to reproduce the error with other classes ( e.g. with org.eclipse.jface.dialogs.PlainMessageDialog, which has practically the same implementation of the method configureShell) and move the solution higher up in the hierarchy?

Comment on lines 289 to 295
shell.addListener(SWT.ZoomChanged, new Listener() {
@Override
public void handleEvent(Event e) {
Point newSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
shell.setBounds(shell.getBounds().x, shell.getBounds().y, newSize.x, newSize.y);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: Listener is a functional interface, use a lambda (instead of instantiating a new anonymous class) for better readability.

shell.addListener(SWT.ZoomChanged, new Listener() {
@Override
public void handleEvent(Event e) {
Point newSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: Not that it matters much (the parameter is ignored in all 3 OSes) but technically the text didn't change so the 3rd parameter should be false (for better readability).

@laeubi
Copy link
Contributor

laeubi commented Sep 8, 2025

@fedejeanne I'm not sure if this can work in general, and must confess I'm a bit concerned that this will lead to strange effects. e.g assume I have a dialog that is resizable and I set a size, now move it and my size will be reset to a completely different one. Even if we accept this should I assume moving it back to the original monitor change it back to my previous size?

@fedejeanne
Copy link
Contributor

That's a good point, @laeubi, thank you 👍 .

@arunjose696 then only go as high as IconAndMessageDialog in the hierarchy since those are not resizable and therefore their size should always match their content.

@fedejeanne
Copy link
Contributor

@arunjose696 then only go as high as IconAndMessageDialog in the hierarchy since those are not resizable and therefore their size should always match their content.

Wait, scratch that. Some subclasses are resizable...

image

... which means that the size that the user set manually should be honored when moving back and forth between monitors.

Is there any API that is able to do that (only make it bigger when necessary and reset it back to the last requested size when it's big enough to hold all the content)?

@arunjose696
Copy link
Contributor Author

arunjose696 commented Sep 8, 2025

Is there any API that is able to do that (only make it bigger when necessary and reset it back to the last requested size when it's big enough to hold all the content)?

I think it should be possible to make the dialog only grow when necessary, but I’m not sure how to restore the user’s last requested size across multiple monitors. For example, if we have three monitors and move the dialog from the first to the second, then third, and back to the first, we would need to track the size the user set on each monitor. Would it make sense to store a per-monitor user-set size and, when switching monitors, reset only if the calculated size is smaller than the user-set size stored for the monitor? @fedejeanne @laeubi

@laeubi
Copy link
Contributor

laeubi commented Sep 8, 2025

@fedejeanne I don't think that this can be done reliable, that's why I suggested:

the main problem is that we are computing values that are loosing semantic/precision. So if the computed (and set) shell size would take original values into account they can be adjusted e.g. with respect to an actual font size.

For the short term I only see the option to have some kind of protected void updateShellSize(Shell shell) that concrete dialogs can override if they want to recompute values in case of monitor changes, this would actually also be useful when moving between monitors of the same scaling e.g. a dialog might want to use a different size on a 800x600 monitor than it would on on one with 3840x2160 for example.

@fedejeanne
Copy link
Contributor

JFace is actually already capable of remembering the location and size of a dialog so that they are restored when the dialog is opened again. Look at these methods (and their usage):

  • org.eclipse.jface.dialogs.Dialog.getDialogBoundsStrategy()
  • org.eclipse.jface.dialogs.Dialog.getDialogBoundsSettings()

Maybe it is possible to expand the dialog only when necessary (i.e. if the content is cut-off) and snap back to the last known preferred size if it's big enough to hold all the content.

@laeubi
Copy link
Contributor

laeubi commented Sep 9, 2025

@fedejeanne this is as far as I know not on by default and only saved/restore on closing and reopen the dialog.

@fedejeanne
Copy link
Contributor

@laeubi that's correct. One would have to save on resize and (if the strategy allows it) restore it on reopen and when switching monitors.

@arunjose696 arunjose696 force-pushed the arunjose696/400/MessageDialogue branch 3 times, most recently from 9c325ad to 74109fd Compare September 9, 2025 16:19
@arunjose696
Copy link
Contributor Author

arunjose696 commented Sep 9, 2025

One would have to save on resize

To save DialogBoundsSettings (height and width) changes on resize in JFace, it would require a ControlListener to the shell using shell.addControlListener. However, this approach is unreliable because the listener would also be triggered by resizes caused by DPI or zoom changes.

However with setBoundsInPixels called before sending zoomChangedEvent as in the PR the shell would already be having the resized size in points if resizing had occured before. This allows a simpler solution: compare the newly calculated width and height with the current shell size, and only increase the shell size if the new calculated size is smaller than the current size.

The limitation of this approach is that it cannot handle cases where the user intentionally resizes the dialog to a smaller size. In such cases, the dialog would default to the size from shell.getBounds(), based on the assumption that resizing below the minimum needed to display all content is likely a user error.

@arunjose696 arunjose696 force-pushed the arunjose696/400/MessageDialogue branch from 74109fd to f82b982 Compare September 9, 2025 16:27
@laeubi
Copy link
Contributor

laeubi commented Sep 9, 2025

In such cases, the dialog would default to the size from shell.getBounds(), based on the assumption that resizing below the minimum needed to display all content is likely a user error.

I'm sorry to insist here, but this assumption is wrong. Assume a dialog shows a very large table with scrollbars, and one need to scroll right/left and even up/down. Then the table (if asked) will of course say if there are no bounds (hint = -1) it wants to be as large as showing the full content (what might be even larger than the screen) so now the logic will resize the dialog very large, maybe in a way that makes it impossible for the user to resize it to a useful size.

That is sadly another limitation of SWT (incontrast to AWT/Swing) that there is no way for a control to distinguish between maximum size, preferred size and minimum size.

By the way this sizing issue reminds me more about the case that currently all sizes on DPI changes are rounded (possibly down) instead of assumed to be the largest value that would match. So I would suggest to carefully check why the dialog is being too small at the first place.

@arunjose696
Copy link
Contributor Author

By the way this sizing issue reminds me more about the case that currently all sizes on DPI changes are rounded (possibly down) instead of assumed to be the largest value that would match. So I would suggest to carefully check why the dialog is being too small at the first place.

The issue here is how the system scales fonts. When printing the height from the text metrics returned by the system, it becomes clear that font heights are not linearly scaled. For example, on my system, a fonts text metrics returned by OS shows a height of 15px at 100% scaling and 32px at 200% scaling. Meanwhile, dialog heights are scaled linearly when moving between monitors. This mismatch causes widgets with text to appear larger relative to the dialog.

Even if we introduce type safe units like Width, Height, or Size in SWT as mentioned here eclipse-platform/eclipse.platform.swt#2488 to perform arithmetic and unit conversions, it cannot change the fact that the OS reports font metrics nonlinearly . Since there is no rounding involved, the new API may not solve this issue.

Here’s a minimal example demonstrating the behavior with a single button in a GridLayout. Using the followwing VM arguments:

-Dswt.autoScale=quarter
-Dswt.autoScale.updateOnRuntime=true

If I run the below snippet with mentioned VM arguments and print the text metrics height below line (ie In Button::computeSizeInPixels), the lptm.tmHeight reported by the system is 15px on a 100% scaled monitor and 32px on a 200% scaled monitor when moving the running shell between monitors. As a result, widgets containing text (e.g., Button, Link, etc.) exhibit non-linear scaling, because the system provides font sizes that are not scaled linearly.

package org.eclipse.swt.snippets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Button;

public class SnippetDemo {
    public static void main(String[] args) {
        // Create display and shell
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("Snippet316 - GridLayout with Button");
        shell.setSize(300, 200);

        // Set a GridLayout with 1 column
        shell.setLayout(new GridLayout(1, false));

        // Create a button with text "abcd"
        Button button = new Button(shell, SWT.PUSH);
        button.setText("abcd");

        // Open the shell
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        display.dispose();
    }
}

To summarize , when moving a dialog across monitors, even if the dialog’s size is correctly computed for 200% zoom, it may still appear incorrectly sized because font scaling is non-linear. Fonts can scale by more than 2×, while the containing shell may only scale 2×, causing text to appear disproportionately large relative to the dialog. The missmatch is not caused due to any rounding in SWT

@arunjose696 arunjose696 force-pushed the arunjose696/400/MessageDialogue branch from f82b982 to 079de1a Compare September 11, 2025 15:08
@arunjose696
Copy link
Contributor Author

In such cases, the dialog would default to the size from shell.getBounds(), based on the assumption that resizing below the minimum needed to display all content is likely a user error.

I'm sorry to insist here, but this assumption is wrong. Assume a dialog shows a very large table with scrollbars, and one need to scroll right/left and even up/down. Then the table (if asked) will of course say if there are no bounds (hint = -1) it wants to be as large as showing the full content (what might be even larger than the screen) so now the logic will resize the dialog very large, maybe in a way that makes it impossible for the user to resize it to a useful size.

I tried this out and realize this is indeed true, The only solution I have currently is to make the listener opt in with a flag so concrete dialogues can set the flag to true if required. I have updated my changes to create an opt-in flag that can be used to recompute dialogue size on dpi change.

When a zoom change occurs, the OS automatically scales the window based
on the scale factor. However, since fonts do not always scale linearly,
this can cause text in dialogues to be cut off.

This change adds a flag shouldRecomputeSizeOnDpiChange if true would add a  ZoomChanged listener to IconAndMessageDialogue . With this listener When a
zoomChanged event is triggered, the shell size is recomputed so that the contents are fit properly and text is not clipped.
The concrete dialogues can set this flag to true if they would need to recompute bounds on DPI change
@arunjose696 arunjose696 force-pushed the arunjose696/400/MessageDialogue branch from 079de1a to 08e8683 Compare September 11, 2025 15:16
@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

Even if we introduce type safe units like Width, Height, or Size in SWT as mentioned here eclipse-platform/eclipse.platform.swt#2488 to perform arithmetic and unit conversions, it cannot change the fact that the OS reports font metrics nonlinearly . Since there is no rounding involved, the new API may not solve this issue.

Even though not the primary intend, but I added a little neat thing to the proposal:

Instead one would want to use relative units (e.g. percent, or em) what is quite common in CSS and responsive designs.

So the main problem is that SWT uses "fixed" units always but as in your case you want the height (and width) relative to the (real) font-size. You will see the same problem when e.g. using table headers where one width will be sufficient for a given font size but will be to large/small for another.

I know this can become quite frustrating, the only workaround I could think of here is that the Dialog is made smarter in the way that it query all Monitors and compute the size of the components for each of them then choosing the largest, that way the Dialog would be a litte bit larger than it needs but could show on all Monitors the full contents.

That would possibly still require a new API e.g a Point computeSize (int wHint, int hHint, Monitor monitor) I created this now

@arunjose696
Copy link
Contributor Author

, the only workaround I could think of here is that the Dialog is made smarter in the way that it query all Monitors and compute the size of the components for each of them then choosing the largest, that way the Dialog would be a litte bit larger than it needs but could show on all Monitors the full contents.

The idea of computing the dialog size for all monitors and then picking the largest to ensure it fits everywhere is theoretically appealing. However, in practice, atleast I feel it is extremely difficult to implement reliably. Different widgets compute their sizes in different ways for example:

  • Button: uses OS.DrawText to calculate the rectangle needed for its text.
  • Link: uses OS.SendMessage(handle, OS.LM_GETIDEALSIZE, maxWidth, size) to determine height.
  • Other controls may have their own mechanisms tied to the current font, DPI, or OS metrics.

To calculate the correct size for a monitor, one would need to temporarily reset fonts for the dialog and all nested children, trigger layout recalculation for every monitor, and ensure consistency across shells and layouts. This would essentially require simulating multiple full relayouts for all zooms, which is complex, and computationally expensive. Also I feel this would be a lot of refactoring to fix an issue that is not so often reported.

@laeubi do you think if there is a simpler way by creating a new API, would you be able to provide a sample implementation or POC how this could be done?

@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

@laeubi do you think if there is a simpler way by creating a new API, would you be able to provide a sample implementation or POC how this could be done?

As said I think having a Dialog#resizeShell method seem the less intrusive here and most flexible. It won't solve it for all, but at least give a unified entry point for a dialog implementation to be aware something has changed. I would then not only trigger that on zoom changes but also if I move a dialog from one screen to another (what possibly has a another screen size or is Vertical/Horizontal aligned or different DPI).

If that's in place one has to decide for all subclasses if we can offer some default (e.g. dialog is not resizable). Given we have the target Monitor at that point one can even maybe store the current size of that monitor in the data of the Dialog shell...

Then if a particular user is unhappy with the new behavior it would still be possible to override that method to get old behavior back.

@arunjose696
Copy link
Contributor Author

@laeubi do you think if there is a simpler way by creating a new API, would you be able to provide a sample implementation or POC how this could be done?

As said I think having a Dialog#resizeShell method seem the less intrusive here and most flexible. It won't solve it for all, but at least give a unified entry point for a dialog implementation to be aware something has changed. I would then not only trigger that on zoom changes but also if I move a dialog from one screen to another (what possibly has a another screen size or is Vertical/Horizontal aligned or different DPI).

Sorry if I’m not fully following , would this approach be similar to my current change in this PR, where there’s a shouldRecomputeSizeOnDpiChange flag that dialog implementations can opt into to enable resizing on DPI change? In that case, we could extend it to also trigger when a listener detects that the dialog has moved to a different monitor. Or are you suggesting that the Dialog#resizeShell logic should be entirely the responsibility of the concrete dialog implementation?

I added this flag and opt-in mechanism specifically so that all consumers don’t have to rewrite the resizing logic themselves.

@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

Or are you suggesting that the Dialog#resizeShell logic should be entirely the responsibility of the concrete dialog implementation?

Correct. A dialog might want to do other things as well but should be automatically called on DPi change / monitor move.

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

Successfully merging this pull request may close these issues.

(Some) Dialogs are smaller than expected
3 participants