Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
package org.eclipse.swt.widgets;

import java.time.*;
import java.util.concurrent.atomic.*;

import org.eclipse.swt.internal.*;
import org.eclipse.swt.widgets.Control.*;
Expand All @@ -30,19 +29,24 @@ public static void changeDPIZoom (Shell shell, int nativeZoom) {
DPIUtil.setDeviceZoom(nativeZoom);
Event event = shell.createZoomChangedEvent(nativeZoom, true);
shell.sendZoomChangedEvent(event, shell);
DPIChangeExecution data = (DPIChangeExecution) event.data;
waitForDPIChange(shell, TIMEOUT_MILLIS, data.taskCount);
waitForDPIChange(shell, (DPIChangeExecution) event.data);
}

private static void waitForDPIChange(Shell shell, int timeout, AtomicInteger scalingCounter) {
waitForPassCondition(shell, timeout, scalingCounter);
/**
* Performs a zoom change like the operating system does when the shell is moved
* to a monitor with a different scaling, i.e. by processing the zoom change for
* the shell instead of sending the zoom changed event to it.
*/
public static void changeDPIZoomOnMonitorChange (Shell shell, int nativeZoom) {
shell.handleMonitorSpecificDpiChange(nativeZoom, shell.getBoundsInPixels());
waitForDPIChange(shell, (DPIChangeExecution) shell.lastDpiChangeEvent.data);
}

private static void waitForPassCondition(Shell shell, int timeout, AtomicInteger scalingCounter) {
final Instant timeOut = Instant.now().plusMillis(timeout);
private static void waitForDPIChange(Shell shell, DPIChangeExecution execution) {
final Instant timeOut = Instant.now().plusMillis(TIMEOUT_MILLIS);
final Display display = shell == null ? Display.getDefault() : shell.getDisplay();

while (Instant.now().isBefore(timeOut) && scalingCounter.get() != 0) {
while (Instant.now().isBefore(timeOut) && !execution.isComplete()) {
if (!display.isDisposed()) {
display.readAndDispatch();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,4 +331,38 @@ public void testCaretInStyledTextAfterZooming() {

}

@Test
public void testShellIsLaidOutOnceWhenZoomChangeIsProcessedSynchronously() {
Display display = Display.getDefault();
// A child shell processes the zoom change synchronously, i.e. all its children
// are adapted within the propagation of the zoom changed event
Shell parentShell = new Shell(display);
Shell shell = new Shell(parentShell, SWT.SHELL_TRIM);
CountingLayout layout = new CountingLayout();
shell.setLayout(layout);
for (int i = 0; i < 3; i++) {
new Button(shell, SWT.PUSH);
}
int scaledZoom = shell.nativeZoom * 2;

layout.layoutCount = 0;
DPITestUtil.changeDPIZoomOnMonitorChange(shell, scaledZoom);

assertEquals("The shell must be laid out exactly once for a single zoom change", 1, layout.layoutCount);
}

private static final class CountingLayout extends Layout {
int layoutCount;

@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
return new Point(wHint == SWT.DEFAULT ? 100 : wHint, hHint == SWT.DEFAULT ? 100 : hHint);
}

@Override
protected void layout(Composite composite, boolean flushCache) {
layoutCount++;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3397,7 +3397,12 @@ public boolean setAutoscalingMode(AutoscalingMode autoscalingMode) {
if (nativeZoom != newZoom) {
nativeZoom = newZoom;
Event zoomChangedEvent = createZoomChangedEvent(newZoom, false);
notifyListeners(SWT.ZoomChanged, zoomChangedEvent);
startZoomChangeTask(zoomChangedEvent);
try {
notifyListeners(SWT.ZoomChanged, zoomChangedEvent);
} finally {
completeZoomChangeTask(zoomChangedEvent, getShell());
}
}
return true;
}
Expand Down Expand Up @@ -5141,9 +5146,7 @@ Event createZoomChangedEvent(int zoom, boolean asyncExec) {
event.widget = this;
event.detail = zoom;
event.doit = true;
DPIChangeExecution dpiChangeExecution = new DPIChangeExecution();
dpiChangeExecution.asyncExec = asyncExec;
event.data = dpiChangeExecution;
event.data = new DPIChangeExecution(event, asyncExec);
return event;
}

Expand Down Expand Up @@ -6023,9 +6026,51 @@ LRESULT wmScrollChild (long wParam, long lParam) {
return null;
}

/**
* The processing of a single zoom change, which is performed in tasks that may be
* executed asynchronously. The zoom change is complete, and the shell is laid out,
* once all its tasks have been completed.
*/
static class DPIChangeExecution {
AtomicInteger taskCount = new AtomicInteger();
private boolean asyncExec = true;
private final Event event;
private final AtomicInteger taskCount = new AtomicInteger();
private boolean asyncExec;

private DPIChangeExecution(Event event, boolean asyncExec) {
this.event = event;
this.asyncExec = asyncExec;
}

/**
* Registers a task of this zoom change, which must be completed with
* {@link #completeTask(Shell)}.
* <p>
* Everything propagating the zoom changed event to widgets that are adapted in
* a task of their own must hold a task itself, as the zoom change would
* otherwise be considered complete as soon as the first of those widgets has
* been adapted.
* </p>
*/
private void startTask() {
taskCount.incrementAndGet();
}

/**
* Completes a task registered with {@link #startTask()} and lays out the given
* shell if it was the last outstanding task of this zoom change.
*/
private void completeTask(Shell shell) {
if (taskCount.decrementAndGet() <= 0 && event.doit && !shell.isDisposed()) {
shell.layout(true, true);
}
}
Comment on lines +6062 to +6066

/**
* Returns whether all tasks of this zoom change have been completed.
*/
boolean isComplete() {
return taskCount.get() <= 0;
}

private void process(Control control, Runnable operation) {
boolean currentAsyncExec = asyncExec;
Expand All @@ -6044,14 +6089,6 @@ private void process(Control control, Runnable operation) {
// DPI change handling is finished
asyncExec = currentAsyncExec;
}

private void increment() {
taskCount.incrementAndGet();
}

private boolean decrement() {
return taskCount.decrementAndGet() <= 0;
}
}

private static class DPIChangeProcessingCallback {
Expand Down Expand Up @@ -6099,26 +6136,39 @@ static void schedule(Control control, Runnable runnable) {

void sendZoomChangedEvent(Event event, Shell shell) {
if (event.data instanceof DPIChangeExecution dpiExecData) {
dpiExecData.increment();
startZoomChangeTask(event);
dpiExecData.process(this, () -> {
try {
if (!this.isDisposed() && event.doit) {
notifyListeners(SWT.ZoomChanged, event);
}
} finally {
if (shell.isDisposed()) {
return;
}
if (dpiExecData.decrement()) {
if (event.doit) {
shell.layout(true, true);
}
}
completeZoomChangeTask(event, shell);
}
});
}
}

/**
* Registers a task of the zoom change the given event belongs to, see
* {@link DPIChangeExecution#startTask()}.
*/
static void startZoomChangeTask(Event event) {
if (event.data instanceof DPIChangeExecution execution) {
execution.startTask();
}
}

/**
* Completes a task of the zoom change the given event belongs to, see
* {@link DPIChangeExecution#completeTask(Shell)}.
*/
static void completeZoomChangeTask(Event event, Shell shell) {
if (event.data instanceof DPIChangeExecution execution) {
execution.completeTask(shell);
}
}

private boolean isAutoscalingDisabled() {
return autoscalingMode != AutoscalingMode.ENABLED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2535,7 +2535,7 @@ LRESULT WM_DPICHANGED (long wParam, long lParam) {
return LRESULT.ONE;
}

private void handleMonitorSpecificDpiChange(int newNativeZoom, Rectangle newBoundsInPixels) {
void handleMonitorSpecificDpiChange(int newNativeZoom, Rectangle newBoundsInPixels) {
DPIUtil.setDeviceZoom (newNativeZoom);
// Do not process DPI change for child shells asynchronous to avoid relayouting when
// repositioning the child shell to a different monitor upon opening
Expand All @@ -2545,8 +2545,13 @@ private void handleMonitorSpecificDpiChange(int newNativeZoom, Rectangle newBoun
lastDpiChangeEvent.doit = false;
}
lastDpiChangeEvent = zoomChangedEvent;
notifyListeners(SWT.ZoomChanged, zoomChangedEvent);
this.setBoundsInPixels(newBoundsInPixels.x, newBoundsInPixels.y, newBoundsInPixels.width, newBoundsInPixels.height);
startZoomChangeTask(zoomChangedEvent);
try {
notifyListeners(SWT.ZoomChanged, zoomChangedEvent);
this.setBoundsInPixels(newBoundsInPixels.x, newBoundsInPixels.y, newBoundsInPixels.width, newBoundsInPixels.height);
} finally {
completeZoomChangeTask(zoomChangedEvent, this);
}
}

@Override
Expand Down
Loading