Skip to content

Commit fbd40ef

Browse files
committed
[win32] Dynamic handle creation for transform
This commit adapts Transform in the win32 implementation to create handles only on demand. If a non-handle specific operation like isIdentity() is called, a temporary handle will be created and disposed afterwards if no handle exists already.
1 parent 54ca587 commit fbd40ef

File tree

2 files changed

+182
-47
lines changed
  • bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics
  • tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit

2 files changed

+182
-47
lines changed

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Transform.java

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.eclipse.swt.graphics;
1515

1616
import java.util.*;
17+
import java.util.function.*;
1718

1819
import org.eclipse.swt.*;
1920
import org.eclipse.swt.internal.*;
@@ -39,12 +40,12 @@
3940
*/
4041
public class Transform extends Resource {
4142

42-
private int initialZoom;
43-
4443
private Map<Integer, TransformHandle> zoomToHandle = new HashMap<>();
4544

4645
private List<Operation> operations = new ArrayList<>();
4746

47+
private boolean isDestroyed;
48+
4849
/**
4950
* Constructs a new identity Transform.
5051
* <p>
@@ -140,12 +141,8 @@ public Transform(Device device, float[] elements) {
140141
*/
141142
public Transform (Device device, float m11, float m12, float m21, float m22, float dx, float dy) {
142143
super(device);
143-
initialZoom = DPIUtil.getDeviceZoom();
144144
this.device.checkGDIP();
145-
long handle = Gdip.Matrix_new(m11, m12, m21, m22,
146-
DPIUtil.scaleUp(this.device, dx, initialZoom), DPIUtil.scaleUp(this.device, dy, initialZoom));
147-
if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
148-
zoomToHandle.put(initialZoom, new TransformHandle(handle, initialZoom));
145+
storeAndApplyOperationForAllHandles(new SetElementsOperation(m11, m12, m21, m22, dx, dy));
149146
init();
150147
this.device.registerResourceWithZoomSupport(this);
151148
}
@@ -161,19 +158,14 @@ void destroy() {
161158
device.deregisterResourceWithZoomSupport(this);
162159
zoomToHandle.values().forEach(TransformHandle::destroy);
163160
zoomToHandle.clear();
164-
operations.clear();
161+
this.isDestroyed = true;
165162
}
166163

167164
@Override
168165
void destroyHandlesExcept(Set<Integer> zoomLevels) {
169-
zoomToHandle.entrySet().removeIf(entry -> {
170-
final Integer zoom = entry.getKey();
171-
if (!zoomLevels.contains(zoom) && zoom != initialZoom) {
172-
entry.getValue().destroy();
173-
return true;
174-
}
175-
return false;
176-
});
166+
// As long as we keep the operations, we can cleanup all handles
167+
zoomToHandle.values().forEach(TransformHandle::destroy);
168+
zoomToHandle.clear();
177169
}
178170

179171
/**
@@ -194,12 +186,14 @@ public void getElements(float[] elements) {
194186
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
195187
if (elements == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
196188
if (elements.length < 6) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
197-
TransformHandle transformHandle = getTransformHandle(initialZoom);
198-
Gdip.Matrix_GetElements(transformHandle.handle, elements);
199-
Drawable drawable = getDevice();
200-
int zoom = transformHandle.zoom;
201-
elements[4] = DPIUtil.scaleDown(drawable, elements[4], zoom);
202-
elements[5] = DPIUtil.scaleDown(drawable, elements[5], zoom);
189+
applyOnAnyHandle(transformHandle -> {
190+
Gdip.Matrix_GetElements(transformHandle.handle, elements);
191+
Drawable drawable = getDevice();
192+
int zoom = transformHandle.zoom;
193+
elements[4] = DPIUtil.scaleDown(drawable, elements[4], zoom);
194+
elements[5] = DPIUtil.scaleDown(drawable, elements[5], zoom);
195+
return true;
196+
});
203197
}
204198

205199
/**
@@ -245,7 +239,7 @@ public void invert() {
245239
*/
246240
@Override
247241
public boolean isDisposed() {
248-
return zoomToHandle.isEmpty();
242+
return this.isDestroyed;
249243
}
250244

251245
/**
@@ -256,8 +250,9 @@ public boolean isDisposed() {
256250
*/
257251
public boolean isIdentity() {
258252
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
259-
TransformHandle transformHandle = getTransformHandle(initialZoom);
260-
return Gdip.Matrix_IsIdentity(transformHandle.handle);
253+
return applyOnAnyHandle(transformHandle -> {
254+
return Gdip.Matrix_IsIdentity(transformHandle.handle);
255+
});
261256
}
262257

263258
/**
@@ -374,15 +369,18 @@ public void transform(float[] pointArray) {
374369
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
375370
if (pointArray == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
376371
Drawable drawable = getDevice();
377-
TransformHandle transformHandle = getTransformHandle(initialZoom);
378-
int length = pointArray.length;
379-
for (int i = 0; i < length; i++) {
380-
pointArray[i] = DPIUtil.scaleUp(drawable, pointArray[i], transformHandle.zoom);
381-
}
382-
Gdip.Matrix_TransformPoints(transformHandle.handle, pointArray, length / 2);
383-
for (int i = 0; i < length; i++) {
384-
pointArray[i] = DPIUtil.scaleDown(drawable, pointArray[i], transformHandle.zoom);
385-
}
372+
applyOnAnyHandle(transformHandle -> {
373+
int length = pointArray.length;
374+
for (int i = 0; i < length; i++) {
375+
pointArray[i] = DPIUtil.scaleUp(drawable, pointArray[i], transformHandle.zoom);
376+
}
377+
Gdip.Matrix_TransformPoints(transformHandle.handle, pointArray, length / 2);
378+
for (int i = 0; i < length; i++) {
379+
pointArray[i] = DPIUtil.scaleDown(drawable, pointArray[i], transformHandle.zoom);
380+
}
381+
return pointArray;
382+
});
383+
386384
}
387385

388386
/**
@@ -415,12 +413,27 @@ public void apply(TransformHandle transformHandle) {
415413
}
416414
}
417415

418-
private record MultiplayOperation(Transform matrix) implements Operation {
416+
private class MultiplayOperation implements Operation {
417+
float[] elements;
418+
public MultiplayOperation(Transform matrix) {
419+
// As operation are executed on demand on new handles the passed matrix could
420+
// already be disposed. Therefore, we need to store the elements to restore
421+
// a temporary matrix for this operation on demand
422+
elements = new float[6];
423+
matrix.getElements(elements);
424+
}
425+
419426
@Override
420427
public void apply(TransformHandle transformHandle) {
421428
long handle = transformHandle.handle;
422429
int zoom = transformHandle.zoom;
423-
Gdip.Matrix_Multiply(handle, matrix.getHandle(zoom), Gdip.MatrixOrderPrepend);
430+
long newHandle = Gdip.Matrix_new(elements[0], elements[1], elements[2], elements[3], DPIUtil.scaleUp(elements[4], zoom), DPIUtil.scaleUp(elements[5], zoom));
431+
if (newHandle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
432+
try {
433+
Gdip.Matrix_Multiply(handle, newHandle, Gdip.MatrixOrderPrepend);
434+
} finally {
435+
Gdip.Matrix_delete(newHandle);
436+
}
424437
}
425438
}
426439

@@ -501,6 +514,19 @@ private void storeAndApplyOperationForAllHandles(Operation operation) {
501514
zoomToHandle.forEach((zoom, handle) -> operation.apply(handle));
502515
}
503516

517+
private <T> T applyOnAnyHandle(Function<TransformHandle, T> function) {
518+
if (zoomToHandle.isEmpty()) {
519+
TransformHandle temporaryHandle = newTransformHandle(DPIUtil.getDeviceZoom());
520+
try {
521+
return function.apply(temporaryHandle);
522+
} finally {
523+
temporaryHandle.destroy();
524+
}
525+
} else {
526+
return function.apply(zoomToHandle.values().iterator().next());
527+
}
528+
}
529+
504530
/**
505531
* Returns a string containing a concise, human-readable
506532
* description of the receiver.
@@ -514,21 +540,27 @@ public String toString() {
514540
getElements(elements);
515541
return "Transform {" + elements [0] + "," + elements [1] + "," +elements [2] + "," +elements [3] + "," +elements [4] + "," +elements [5] + "}";
516542
}
543+
544+
private TransformHandle newTransformHandle(int zoom) {
545+
long newHandle = Gdip.Matrix_new(0, 0, 0, 0, 0, 0);
546+
if (newHandle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
547+
TransformHandle newTransformHandle = new TransformHandle(newHandle, zoom);
548+
for(Operation operation : operations) {
549+
operation.apply(newTransformHandle);
550+
}
551+
return newTransformHandle;
552+
}
553+
517554
private TransformHandle getTransformHandle(int zoom) {
518-
if(zoomToHandle.get(zoom) == null) {
519-
float[] elements = new float[6];
520-
getElements(elements);
521-
elements[4] = DPIUtil.scaleUp(device, DPIUtil.scaleDown(device, elements[4], initialZoom), zoom);
522-
elements[5] = DPIUtil.scaleUp(device, DPIUtil.scaleDown(device, elements[5], initialZoom), zoom);
523-
long handle = Gdip.Matrix_new(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]);
524-
TransformHandle transformHandle = new TransformHandle(handle, zoom);
525-
zoomToHandle.put(zoom, transformHandle);
526-
return transformHandle;
555+
if (!zoomToHandle.containsKey(zoom)) {
556+
TransformHandle newHandle = newTransformHandle(zoom);
557+
zoomToHandle.put(zoom, newHandle);
558+
return newHandle;
527559
}
528560
return zoomToHandle.get(zoom);
529561
}
530562

531-
long getHandle(int zoomLevel) {
532-
return getTransformHandle(zoomLevel).handle;
563+
long getHandle(int zoom) {
564+
return getTransformHandle(zoom).handle;
533565
}
534566
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Yatta and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Yatta - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.swt.tests.junit;
15+
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.fail;
18+
19+
import org.eclipse.swt.graphics.Transform;
20+
import org.eclipse.swt.widgets.Display;
21+
import org.junit.Before;
22+
import org.junit.Test;
23+
24+
/**
25+
* Automated Test Suite for class org.eclipse.swt.graphics.Transform
26+
*
27+
* @see org.eclipse.swt.graphics.Transform
28+
*/
29+
public class TransformTests {
30+
31+
private Display display;
32+
33+
@Before
34+
public void setUp() {
35+
display = Display.getDefault();
36+
}
37+
38+
@Test
39+
public void createTransform() {
40+
Transform transform = new Transform(display);
41+
if (transform.isDisposed()) {
42+
fail("Constructor for Transform didn't initialize");
43+
}
44+
transform.dispose();
45+
}
46+
47+
48+
@Test
49+
public void multiplyWithDisposedTransform() {
50+
Transform transform = new Transform(display);
51+
transform.translate(10, 10);
52+
Transform transformDisposed = new Transform(display);
53+
transformDisposed.translate(20, 20);
54+
55+
transform.multiply(transformDisposed);
56+
transformDisposed.dispose();
57+
float [] elements = new float[6];
58+
transform.getElements(elements);
59+
assertEquals("Multiply of transforms failed.", 30, elements[4], 0.01);
60+
transform.dispose();
61+
}
62+
63+
@Test
64+
public void disposeTransform() {
65+
Transform transform = new Transform(display);
66+
transform.setElements(2, 3, 4, 5, 6, 7);
67+
if (transform.isDisposed()) {
68+
fail("Transform should not be in the disposed state");
69+
}
70+
71+
// dispose twice as this is allowed
72+
for (int i = 0; i < 2; i++) {
73+
transform.dispose();
74+
if (!transform.isDisposed()) {
75+
fail("Transform should be in the disposed state");
76+
}
77+
}
78+
}
79+
80+
@Test
81+
public void testToString() {
82+
Transform transform = new Transform(display);
83+
String s = transform.toString();
84+
85+
if (s == null || s.length() == 0) {
86+
fail("toString returns null or empty string");
87+
}
88+
89+
transform.setElements(2, 3, 4, 5, 6, 7);
90+
s = transform.toString();
91+
92+
if (s == null || s.length() == 0) {
93+
fail("toString returns null or empty string");
94+
}
95+
96+
transform.dispose();
97+
s = transform.toString();
98+
99+
if (s == null || s.length() == 0) {
100+
fail("toString returns null or empty string");
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)