4242import javax .imageio .ImageIO ;
4343import javax .swing .JFrame ;
4444import javax .swing .JPanel ;
45+ import javax .swing .SwingUtilities ;
4546
4647import com .oracle .truffle .r .library .fastrGrid .device .DrawingContext ;
4748import com .oracle .truffle .r .library .fastrGrid .device .GridDevice ;
4849import com .oracle .truffle .r .library .fastrGrid .device .ImageSaver ;
4950
5051/**
5152 * This device paints everything into an image, which is painted into a AWT component in its
52- * {@code paint} method.
53+ * {@code paint} method. Note that this class is not thread safe.
5354 */
5455public final class JFrameDevice implements GridDevice , ImageSaver {
5556
@@ -64,14 +65,26 @@ public final class JFrameDevice implements GridDevice, ImageSaver {
6465 private Graphics2DDevice inner ;
6566 private boolean isOnHold = false ;
6667
67- private FastRFrame currentFrame ;
68+ /**
69+ * Value of this field is set from the AWT thread, any code using it in the main thread should
70+ * first check if it is not {@code null}.
71+ */
72+ private volatile FastRFrame currentFrame ;
73+ /**
74+ * The closing operation invokes {@code dev.off()} to close the device on the R side (remove it
75+ * from {@code .Devices} etc.), but that eventually calls into {@link #close()} where we need to
76+ * know that the AWT window is being closed by the user and so we do not need to close it.
77+ */
78+ private volatile boolean isClosing = false ;
6879 private Runnable onResize ;
6980 private Runnable onClose ;
7081
7182 public JFrameDevice (int width , int height ) {
72- currentFrame = new FastRFrame (new FastRPanel (width , height ));
73- openGraphics2DDevice (currentFrame .fastRComponent .getWidth (), currentFrame .fastRComponent .getHeight ());
83+ openGraphics2DDevice (width , height );
7484 componentImage = image ;
85+ SwingUtilities .invokeLater (() -> {
86+ currentFrame = new FastRFrame (new FastRPanel (width , height ));
87+ });
7588 }
7689
7790 @ Override
@@ -110,7 +123,9 @@ public void flush() {
110123 @ Override
111124 public void close () throws DeviceCloseException {
112125 disposeGraphics2DDevice ();
113- currentFrame .dispose ();
126+ if (!isClosing && currentFrame != null ) {
127+ currentFrame .dispose ();
128+ }
114129 componentImage = null ;
115130 }
116131
@@ -231,17 +246,21 @@ private void resize(int newWidth, int newHeight) {
231246 }
232247
233248 private void ensureOpen () {
234- if (!currentFrame .isVisible ()) {
249+ if (currentFrame != null && !currentFrame .isVisible ()) {
235250 int width = inner .getWidthAwt ();
236251 int height = inner .getHeightAwt ();
237- currentFrame = new FastRFrame ( new FastRPanel ( width , height ));
252+ // Note: the assumption is that this class is single threaded
238253 disposeGraphics2DDevice ();
239254 openGraphics2DDevice (width , height );
255+ currentFrame = null ;
256+ SwingUtilities .invokeLater (() -> {
257+ currentFrame = new FastRFrame (new FastRPanel (width , height ));
258+ });
240259 }
241260 }
242261
243262 private void repaint () {
244- if (!isOnHold ) {
263+ if (!isOnHold && currentFrame != null ) {
245264 currentFrame .repaint ();
246265 }
247266 }
@@ -312,6 +331,7 @@ class FastRFrame extends JFrame {
312331 FastRFrame (FastRPanel fastRComponent ) throws HeadlessException {
313332 super ("FastR" );
314333 this .fastRComponent = fastRComponent ;
334+ setDefaultCloseOperation (DISPOSE_ON_CLOSE );
315335 addListeners ();
316336 getContentPane ().add (fastRComponent );
317337 pack ();
@@ -320,10 +340,12 @@ class FastRFrame extends JFrame {
320340 }
321341
322342 private void addListeners () {
323- addWindowFocusListener (new WindowAdapter () {
343+ addWindowListener (new WindowAdapter () {
324344 @ Override
325345 public void windowClosing (WindowEvent e ) {
326- if (onClose != null ) {
346+ super .windowClosing (e );
347+ if (!isClosing && onClose != null ) {
348+ isClosing = true ;
327349 onClose .run ();
328350 }
329351 }
0 commit comments