001package ball.swing; 002/*- 003 * ########################################################################## 004 * Utilities 005 * %% 006 * Copyright (C) 2008 - 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import java.awt.event.WindowAdapter; 022import java.awt.event.WindowEvent; 023import java.util.concurrent.ExecutionException; 024import java.util.concurrent.Future; 025import java.util.concurrent.TimeUnit; 026import java.util.concurrent.TimeoutException; 027import javax.swing.JFrame; 028import lombok.ToString; 029 030/** 031 * {@link JFrame} implementation that provides a {@link Future} indicating 032 * when the {@link JFrame} is closed (or made invisible). 033 * 034 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 035 */ 036public class ClosedFutureJFrame extends JFrame { 037 private static final long serialVersionUID = -1292559417822522869L; 038 039 /** @serial */ private final Object lock = new Object(); 040 /** @serial */ private boolean isStarted = false; 041 /** @serial */ private boolean isCancelled = false; 042 /** @serial */ private final FutureImpl future = new FutureImpl(); 043 044 /** 045 * Sole constructor. 046 * 047 * @param title The frame title. 048 */ 049 public ClosedFutureJFrame(String title) { 050 super(title); 051 052 setDefaultCloseOperation(HIDE_ON_CLOSE); 053 addWindowListener(new WindowListenerImpl()); 054 } 055 056 @Override 057 public void setVisible(boolean visible) { 058 synchronized (lock) { 059 isStarted |= (visible && (! isCancelled)); 060 super.setVisible(visible && (! isCancelled)); 061 062 if (! isVisible()) { 063 lock.notifyAll(); 064 } 065 } 066 } 067 068 /** 069 * Method to get {@link Future} indicating when {@link.this} 070 * {@link JFrame} is closed. 071 * 072 * @return The closed {@link Future}. 073 */ 074 public Future<Boolean> closedFuture() { return future; } 075 076 @ToString 077 private class WindowListenerImpl extends WindowAdapter { 078 public WindowListenerImpl() { super(); } 079 080 @Override 081 public void windowClosing(WindowEvent event) { setVisible(false); } 082 } 083 084 @ToString 085 private class FutureImpl implements Future<Boolean> { 086 public FutureImpl() { } 087 088 @Override 089 public boolean cancel(boolean mayInterruptIfRunning) { 090 boolean cancelled = false; 091 092 if (isStarted) { 093 if (mayInterruptIfRunning) { 094 cancelled = isVisible(); 095 isCancelled |= true; 096 setVisible(false); 097 } 098 } else { 099 cancelled = true; 100 isCancelled |= true; 101 } 102 103 return cancelled; 104 } 105 106 @Override 107 public boolean isCancelled() { return isCancelled; } 108 109 @Override 110 public boolean isDone() { return (isCancelled() || (! isVisible())); } 111 112 @Override 113 public Boolean get() throws InterruptedException, ExecutionException { 114 Boolean result = null; 115 116 try { 117 result = get(0, TimeUnit.MILLISECONDS); 118 } catch (TimeoutException exception) { 119 throw new IllegalStateException(exception); 120 } 121 122 return result; 123 } 124 125 @Override 126 public Boolean get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 127 Boolean result = null; 128 129 synchronized (lock) { 130 if (! isDone()) { 131 lock.wait(unit.toMillis(timeout), (int) (unit.toNanos(timeout) % 1000000)); 132 } 133 134 result = isDone() ? true : null; 135 } 136 137 return result; 138 } 139 } 140}