From dcaac217abc0031e7f626c8922f6fc0f297154fc Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Thu, 7 Aug 2025 11:30:45 +0200 Subject: [PATCH] Implement a monitor-aware lightweight system to handle HiDPI scaling This lightweight-system disables the native HiDPI scaling that is normally done by SWT and instead scales the figures via a ScalableLayeredPane. The main purpose of this class is to reduce the noise that is introduced by rounding, errors when dealing with fractional scaling. To test this functionality, simply pass the MonitorAwareLightweighSystem as an additional argument when constructing the FigureCanvas. See the AutoScaleExample for reference. --- .gitignore | 1 + .../guide-src/guide.adoc | 1 + .../guide-src/hidpi.adoc | 44 +++ .../guide-src/images/hidpi-lws.svg | 322 ++++++++++++++++++ .../guide/images/hidpi-lws.png | Bin 0 -> 15893 bytes org.eclipse.draw2d.doc.isv/topics_Guide.xml | 2 + .../draw2d/examples/AutoScaleExample.java | 83 +++++ .../eclipse/draw2d/test/Draw2dTestSuite.java | 3 +- .../org/eclipse/draw2d/test/HiDPITest.java | 151 ++++++++ org.eclipse.draw2d/META-INF/MANIFEST.MF | 2 +- .../draw2d/MonitorAwareLightweightSystem.java | 218 ++++++++++++ .../src/org/eclipse/draw2d/Viewport.java | 11 +- 12 files changed, 835 insertions(+), 3 deletions(-) create mode 100644 org.eclipse.draw2d.doc.isv/guide-src/hidpi.adoc create mode 100644 org.eclipse.draw2d.doc.isv/guide-src/images/hidpi-lws.svg create mode 100644 org.eclipse.draw2d.doc.isv/guide/images/hidpi-lws.png create mode 100644 org.eclipse.draw2d.examples/src/org/eclipse/draw2d/examples/AutoScaleExample.java create mode 100644 org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/HiDPITest.java create mode 100644 org.eclipse.draw2d/src/org/eclipse/draw2d/MonitorAwareLightweightSystem.java diff --git a/.gitignore b/.gitignore index 60a636e6c..fe56f1749 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/bin/ **/target/ **/guide/**/*.html +**/guide/**/*.svg /gef-updates *~ *.rej diff --git a/org.eclipse.draw2d.doc.isv/guide-src/guide.adoc b/org.eclipse.draw2d.doc.isv/guide-src/guide.adoc index d21ff9118..8c9c273b7 100644 --- a/org.eclipse.draw2d.doc.isv/guide-src/guide.adoc +++ b/org.eclipse.draw2d.doc.isv/guide-src/guide.adoc @@ -20,3 +20,4 @@ coordinates, working with absolute coordinates behavior * xref:migration-guide.adoc[Plug-in Migration Guide] - changes between individual releases +* xref:hidpi.adoc[High-DPI Support] - taming the native display zoom diff --git a/org.eclipse.draw2d.doc.isv/guide-src/hidpi.adoc b/org.eclipse.draw2d.doc.isv/guide-src/hidpi.adoc new file mode 100644 index 000000000..bafb80a21 --- /dev/null +++ b/org.eclipse.draw2d.doc.isv/guide-src/hidpi.adoc @@ -0,0 +1,44 @@ +ifdef::env-github[] +:imagesdir: ../guide/ +endif::[] + += High-DPI Support + +Modern versions of SWT automatically scale the Draw2D figures by the native display zoom. In Draw2D, this feature is +enabled by default. If clients require more control over the scaling behavior, this functionality may be configured by +passing a link:../reference/api/org/eclipse/draw2d/MonitorAwareLightweightSystem.html[`MonitorAwareLightweightSystem`], +when creating a new link:../reference/api/org/eclipse/draw2d/FigureCanvas.html[`FigureCanvas`] instance. + +[source,java] +---- +FigureCanvas figureCanvas = new FigureCanvas(shell, new MonitorAwareLightweightSystem()); +---- + +This custom lightweight-system performs three actions: + +1) It disables the auto-scaling that would normally be done by SWT, by setting the widget-defined `AUTOSCALE_DISABLED` + property. + +2) It creates a link:../reference/api/org/eclipse/draw2d/internal/MonitorAwareViewport.html[`MonitorAwareViewport`] that is used + to inject a link:../reference/api/org/eclipse/draw2d/ScalableLayeredPane.html[`ScalableLayeredPane`] between the root figure + and the contents of the viewport. + +3) It hooks a listener to the canvas that updates the scale of the scalable pane, whenever the native zoom of the widget + is changed. + +image:images/hidpi-lws.png[Figure Hierarchy of the MonitorAwareLightweightSystem] + +Clients may optionally set the `draw2d.autoScale` system property to define a scaling that is independent from the display zoom. +Similar to the SWT system property, the value must be a positive integer. + +Example: 100, 125, 200, which correlate to 100%, 125% and 200%. + +In order to use this custom LightweightSystem in a GEF view, override the `createLightweightSystem()` method of the +`GraphicalViewerImpl`. + +[source,java] +---- +protected LightweightSystem createLightweightSystem() { + return new MonitorAwareLightweightSystem(); +} +---- \ No newline at end of file diff --git a/org.eclipse.draw2d.doc.isv/guide-src/images/hidpi-lws.svg b/org.eclipse.draw2d.doc.isv/guide-src/images/hidpi-lws.svg new file mode 100644 index 000000000..68830d925 --- /dev/null +++ b/org.eclipse.draw2d.doc.isv/guide-src/images/hidpi-lws.svg @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Root Figure + + + contents + + + + MonitorAwareLightweightSystem + + + + FigureCanvas + + + + + + + MonitorAwareViewport + + + Scalable Pane + + + + Viewport + + + + + diff --git a/org.eclipse.draw2d.doc.isv/guide/images/hidpi-lws.png b/org.eclipse.draw2d.doc.isv/guide/images/hidpi-lws.png new file mode 100644 index 0000000000000000000000000000000000000000..1597627cf310cbe0038da2025f895d7ae1bd24ef GIT binary patch literal 15893 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 bSR~EZZRYBA!YR0=8zChw|2$9h)!Y9IP!yL9 literal 0 HcmV?d00001 diff --git a/org.eclipse.draw2d.doc.isv/topics_Guide.xml b/org.eclipse.draw2d.doc.isv/topics_Guide.xml index 0297c4c34..c99dc0b6a 100644 --- a/org.eclipse.draw2d.doc.isv/topics_Guide.xml +++ b/org.eclipse.draw2d.doc.isv/topics_Guide.xml @@ -15,6 +15,8 @@ + + diff --git a/org.eclipse.draw2d.examples/src/org/eclipse/draw2d/examples/AutoScaleExample.java b/org.eclipse.draw2d.examples/src/org/eclipse/draw2d/examples/AutoScaleExample.java new file mode 100644 index 000000000..040549bd4 --- /dev/null +++ b/org.eclipse.draw2d.examples/src/org/eclipse/draw2d/examples/AutoScaleExample.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2025 Patrick Ziegler and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + *******************************************************************************/ + +package org.eclipse.draw2d.examples; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.draw2d.ColorConstants; +import org.eclipse.draw2d.Figure; +import org.eclipse.draw2d.FigureCanvas; +import org.eclipse.draw2d.FlowLayout; +import org.eclipse.draw2d.Graphics; +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.MonitorAwareLightweightSystem; +import org.eclipse.draw2d.geometry.Dimension; + +public class AutoScaleExample { + public static void main(String[] args) { + System.setProperty("swt.autoScale.updateOnRuntime", Boolean.TRUE.toString()); //$NON-NLS-1$ + System.setProperty("draw2d.autoScale", "200"); //$NON-NLS-1$ //$NON-NLS-2$ + Shell shell = new Shell(); + shell.setSize(400, 400); + shell.setLayout(new FillLayout()); + + FigureCanvas canvas = new FigureCanvas(shell, SWT.NONE, new MonitorAwareLightweightSystem()); + Figure root = new Figure(); + root.setLayoutManager(new ListLayout()); + root.add(createLabel(ColorConstants.red)); + root.add(createLabel(ColorConstants.green)); + root.add(createLabel(ColorConstants.blue)); + root.add(createLabel(ColorConstants.yellow)); + root.add(createLabel(ColorConstants.cyan)); + root.add(createLabel(ColorConstants.white)); + root.add(createLabel(ColorConstants.gray)); + root.add(createLabel(ColorConstants.orange)); + canvas.getViewport().setContentsTracksHeight(true); + canvas.getViewport().setContentsTracksWidth(true); + canvas.getViewport().setContents(root); + + shell.open(); + + Display display = shell.getDisplay(); + while (!display.isDisposed()) { + display.readAndDispatch(); + } + } + + private static IFigure createLabel(Color bg) { + return new Figure() { + @Override + protected void paintFigure(Graphics graphics) { + super.paintFigure(graphics); + graphics.setBackgroundColor(bg); + graphics.fillRectangle(getClientArea()); + } + + @Override + public Dimension getPreferredSize(int wHint, int hHint) { + return new Dimension(wHint, 100); + } + }; + } + + private static class ListLayout extends FlowLayout { + public ListLayout() { + setMajorSpacing(0); + } + } +} diff --git a/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/Draw2dTestSuite.java b/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/Draw2dTestSuite.java index 40bf1af01..94784e728 100644 --- a/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/Draw2dTestSuite.java +++ b/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/Draw2dTestSuite.java @@ -60,7 +60,8 @@ InsetsTest.class, DirectedGraphLayoutTest.class, ScrollPaneTests.class, - LabelTest.class + LabelTest.class, + HiDPITest.class }) public class Draw2dTestSuite { } diff --git a/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/HiDPITest.java b/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/HiDPITest.java new file mode 100644 index 000000000..a49453343 --- /dev/null +++ b/org.eclipse.draw2d.tests/src/org/eclipse/draw2d/test/HiDPITest.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2025 Patrick Ziegler and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + *******************************************************************************/ + +package org.eclipse.draw2d.test; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.draw2d.Figure; +import org.eclipse.draw2d.FigureCanvas; +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.MonitorAwareLightweightSystem; +import org.eclipse.draw2d.ScalableLayeredPane; +import org.eclipse.draw2d.Viewport; +import org.eclipse.draw2d.XYLayout; +import org.eclipse.draw2d.geometry.Rectangle; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class HiDPITest extends BaseTestCase { + private Shell shell; + private FigureCanvas canvas; + + private Figure parent; + private IFigure figure1; + private IFigure figure2; + + @BeforeEach + public void setUp() { + shell = new Shell(SWT.NO_TRIM); + shell.setSize(400, 400); + shell.setLayout(new FillLayout()); + + figure1 = new Figure(); + figure1.setBounds(Rectangle.SINGLETON.setBounds(50, 50, 100, 100)); + figure2 = new Figure(); + figure2.setBounds(Rectangle.SINGLETON.setBounds(200, 200, 50, 50)); + + parent = new Figure(); + parent.setLayoutManager(new XYLayout()); + parent.add(figure1); + parent.add(figure2); + + canvas = new FigureCanvas(shell, new MonitorAwareLightweightSystem()); + canvas.setContents(parent); + } + + @AfterEach + public void tearDown() { + shell.dispose(); + } + + @Test + public void test_FigureCanvas_setViewport1() { + canvas.setViewport(new Viewport()); + // Only set the ScalableLayeredPane when there is something to set + assertNull(canvas.getContents()); + } + + @Test + public void test_FigureCanvas_setViewport2() { + IFigure figure = new Figure(); + canvas.setViewport(new Viewport()); + canvas.setContents(figure); + // Inject the ScalableLayeredPane when a new Viewport is set + IFigure contents = canvas.getContents(); + assertTrue(contents instanceof ScalableLayeredPane); + assertEquals(contents.getChildren().size(), 1); + assertEquals(contents.getChildren().get(0), figure); + } + + @Test + public void test_FigureCanvas_setContents() { + Figure figure = new Figure(); + canvas.setContents(figure); + // Update contents of ScalableLayeredPane together with Viewport + IFigure contents = canvas.getContents(); + assertTrue(contents instanceof ScalableLayeredPane); + assertEquals(contents.getChildren().size(), 1); + assertEquals(contents.getChildren().get(0), figure); + } + + @Test + public void test_Viewport_getContents() { + IFigure contents = canvas.getContents(); + assertTrue(contents instanceof ScalableLayeredPane); + assertEquals(contents.getChildren().size(), 1); + assertEquals(contents.getChildren().get(0), parent); + } + + @Test + public void test_Viewport_getContents_Scale() { + ScalableLayeredPane contents = (ScalableLayeredPane) canvas.getContents(); + assertEquals(contents.getScale(), 1.0); + canvas.notifyListeners(SWT.ZoomChanged, createZoomEvent(200)); + assertEquals(contents.getScale(), 2.0); + } + + @Test + public void test_Contents_NoZoom() { + Rectangle bounds1 = figure1.getBounds().getCopy(); + Rectangle bounds2 = figure2.getBounds().getCopy(); + + assertEquals(50, 50, 100, 100, bounds1); + assertEquals(200, 200, 50, 50, bounds2); + + figure1.translateToAbsolute(bounds1); + figure2.translateToAbsolute(bounds2); + + assertEquals(50, 50, 100, 100, bounds1); + assertEquals(200, 200, 50, 50, bounds2); + } + + @Test + public void test_Contents_200Zoom() { + canvas.notifyListeners(SWT.ZoomChanged, createZoomEvent(200)); + + Rectangle bounds1 = figure1.getBounds().getCopy(); + Rectangle bounds2 = figure2.getBounds().getCopy(); + + assertEquals(50, 50, 100, 100, bounds1); + assertEquals(200, 200, 50, 50, bounds2); + + figure1.translateToAbsolute(bounds1); + figure2.translateToAbsolute(bounds2); + + assertEquals(100, 100, 200, 200, bounds1); + assertEquals(400, 400, 100, 100, bounds2); + } + + private static Event createZoomEvent(int zoom) { + Event event = new Event(); + event.type = SWT.ZoomChanged; + event.detail = zoom; + return event; + } +} diff --git a/org.eclipse.draw2d/META-INF/MANIFEST.MF b/org.eclipse.draw2d/META-INF/MANIFEST.MF index fbc441744..403cc1ae5 100644 --- a/org.eclipse.draw2d/META-INF/MANIFEST.MF +++ b/org.eclipse.draw2d/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.draw2d;singleton:=true -Bundle-Version: 3.20.100.qualifier +Bundle-Version: 3.21.0.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: org.eclipse.draw2d, diff --git a/org.eclipse.draw2d/src/org/eclipse/draw2d/MonitorAwareLightweightSystem.java b/org.eclipse.draw2d/src/org/eclipse/draw2d/MonitorAwareLightweightSystem.java new file mode 100644 index 000000000..d3c1d4909 --- /dev/null +++ b/org.eclipse.draw2d/src/org/eclipse/draw2d/MonitorAwareLightweightSystem.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2025 Patrick Ziegler and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + *******************************************************************************/ + +package org.eclipse.draw2d; + +import java.beans.PropertyChangeListener; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Control; + +/** + * Subclass of the {@link LightweightSystem} to handle native HiDPI scaling. + * + *

+ * Primary purpose of this class is to take over the scaling that would normally + * be done by SWT. When this class is used, all figures are painted at 100% zoom + * and then up-scaled to match the display zoom, in an attempt to reduce the + * number of visual artifacts that would otherwise be introduced as a result of + * rounding errors that result from fractional scaling. + *

+ * + *

+ * Important: This class modifies the viewport that is set as its + * contents. Calling {@link Viewport#getContents()} returns a + * {@link ScalableLayeredPane} with the figure that is set via + * {@link Viewport#setContents(IFigure)} as its only child. + * + *

+ * + *

+ * EXPERIMENTAL. This class or interface has been added as part + * of a work in progress. There is no guarantee that this API will work nor that + * it will remain the same. Please do not use this API without consulting with + * the GEF-classic team. + *

+ * + * @since 3.21 + */ +public class MonitorAwareLightweightSystem extends LightweightSystem { + + /** + * Data set by SWT that describes the native device zoom used for this widget. + */ + private static final String DATA_NATIVE_ZOOM = "NATIVE_ZOOM"; //$NON-NLS-1$ + + /** + * Data that can be set to scale this widget at 100%. + */ + private static final String DATA_AUTOSCALE_DISABLED = "AUTOSCALE_DISABLED"; //$NON-NLS-1$ + + /** + * Environment variable that when enabled, takes over the native HiDPI scaling + * that would otherwise be done by SWT. The value needs to be a positive + * integer. + */ + private static final String PROP_DRAW2D_AUTO_SCALE = "draw2d.autoScale"; //$NON-NLS-1$ + + /** + * Set via the {@link #PROP_DRAW2D_AUTO_SCALE} property. If not {@code null}, + * the auto-scaling done by SWT is disabled and instead, the lightweight-system + * is scaled by this factor. + */ + private static final Double DRAW2D_AUTO_SCALE; + + static { + String autoScale = System.getProperty(PROP_DRAW2D_AUTO_SCALE); + if (autoScale != null) { + int zoom = parseIntUnchecked(autoScale); + DRAW2D_AUTO_SCALE = zoom / 100.0; + } else { + DRAW2D_AUTO_SCALE = null; + } + } + + /** + * Convenience method to convert the integer defined by + * {@link #PROP_DRAW2D_AUTO_SCALE} without throwing a checked exception. + * + * @param property The property to convert. + * @return The integer described by the given string. + */ + private static int parseIntUnchecked(String property) { + try { + return Integer.parseInt(property); + } catch (NumberFormatException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Returns the native device zoom for this widget. + * + * @return 1.0 at 100%, 1.25 at 125% etc. + */ + private static double getNativeZoom(Control c) { + Object displayZoom = c.getData(DATA_NATIVE_ZOOM); + if (displayZoom == null) { + return 1.0; + } + return Double.valueOf(displayZoom.toString()) / 100.0; + } + + /** + * The scalable pane that is injected between the root figure and the contents + * of this viewport. + */ + private ScalableLayeredPane scalablePane; + + /** + * This property-change listener is hooked to the viewport of this + * lightweight-system and will automatically inject the scalable pane when + * {@link Viewport#setContents(IFigure)} is called. + */ + private final PropertyChangeListener viewportListener; + + public MonitorAwareLightweightSystem() { + scalablePane = new ScalableLayeredPane(false); + scalablePane.setLayoutManager(new StackLayout()); + viewportListener = event -> { + if (Viewport.PROPERTY_CONTENTS.equals(event.getPropertyName())) { + Viewport viewport = (Viewport) event.getSource(); + // ignore event fired when calling viewport.setContents(scalablePane) + if (scalablePane == viewport.getContents()) { + return; + } + + injectScalablePane(viewport); + } + }; + } + + /** + * Updates the scale factor of the scalable pane to the given value. This value + * is ignored if {@link #PROP_DRAW2D_AUTO_SCALE} has been set. + * + * @param nativeZoom The new scale factor. + */ + private void setScale(double nativeZoom) { + if (DRAW2D_AUTO_SCALE != null) { + scalablePane.setScale(DRAW2D_AUTO_SCALE); + } else { + scalablePane.setScale(nativeZoom); + } + } + + @Override + public void setControl(Canvas c) { + if (c == null) { + return; + } + + c.setData(DATA_AUTOSCALE_DISABLED, true); + c.addListener(SWT.ZoomChanged, e -> setScale(e.detail / 100.0)); + setScale(getNativeZoom(c)); + + super.setControl(c); + } + + @Override + public void setContents(IFigure figure) { + if (contents instanceof Viewport vp) { + unhookViewport(vp); + } + super.setContents(figure); + if (figure instanceof Viewport vp) { + hookViewport(vp); + } + } + + /** + * Remove the property-change listener from the given viewport. Called from + * {@link #setContents(IFigure)}. + * + * @param viewport The old viewport of this lightweight-system. + */ + private void unhookViewport(Viewport viewport) { + viewport.removePropertyChangeListener(viewportListener); + } + + /** + * Add the property-change listener to the given viewport and inject the + * scalable pane. Called from {@link #setContents(IFigure)}. + * + * @param viewport The new viewport of this lightweight-system. + */ + private void hookViewport(Viewport viewport) { + injectScalablePane(viewport); + viewport.addPropertyChangeListener(viewportListener); + } + + /** + * Injects a scalable pane between the given viewport and its contents. The old + * contents is moved and becomes a child of the scalable pane. + * + * @param viewport The viewport of this lightweight-system. + */ + private void injectScalablePane(Viewport viewport) { + IFigure contents = viewport.getContents(); + + scalablePane.removeAll(); + + if (contents != null) { + viewport.setContents(scalablePane); + scalablePane.add(contents); + } + } +} diff --git a/org.eclipse.draw2d/src/org/eclipse/draw2d/Viewport.java b/org.eclipse.draw2d/src/org/eclipse/draw2d/Viewport.java index f66906fd5..9ef30f6c1 100644 --- a/org.eclipse.draw2d/src/org/eclipse/draw2d/Viewport.java +++ b/org.eclipse.draw2d/src/org/eclipse/draw2d/Viewport.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2010 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -27,6 +27,13 @@ public class Viewport extends Figure implements PropertyChangeListener { /** ID for the view location property */ public static final String PROPERTY_VIEW_LOCATION = "viewLocation"; //$NON-NLS-1$ + /** + * ID for the contents property. An event with this ID is fired whenever the + * content of this viewport is set. + * + * @see #setContents(IFigure) + */ + /* package */ static final String PROPERTY_CONTENTS = "contents"; //$NON-NLS-1$ private IFigure view; private boolean useTranslate = false; @@ -223,6 +230,7 @@ public void setContents(IFigure figure) { if (view == figure) { return; } + IFigure oldView = view; if (view != null) { remove(view); } @@ -230,6 +238,7 @@ public void setContents(IFigure figure) { if (view != null) { add(figure); } + firePropertyChange(PROPERTY_CONTENTS, oldView, view); } /**