import java.awt.*;
import java.awt.event.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.AbstractSet;
import javax.swing.*;

public class WindowTest {
    public static void main(String[] args) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                final JFrame main = new JFrame("Main");

                final JButton openFirst = new JButton("1. click here");
                openFirst.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {

                        final JDialog first = new MyDialog(main);

                        JButton openSecond = new JButton("2. click here");
                        openSecond.addActionListener(new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent e) {

                                final JDialog second = new MyDialog(first);
                                second.getContentPane().add(new JLabel("3. move this window and click in this label BEFORE 4. window opens"));
                                second.setLocationRelativeTo(null);
                                second.setSize(450, 100);


                                Timer firstTimer = new Timer(9000, new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent e) {
                                        showInfo(first, "4. try to close me");
                                        first.setVisible(false);

                                        Timer secondTimer = new Timer(3000, new ActionListener() {
                                            @Override
                                            public void actionPerformed(ActionEvent e) {
                                                showInfo(second, "Second option");
                                                second.setVisible(false);

                                            }
                                        });
                                        secondTimer.setRepeats(false);
                                        secondTimer.setInitialDelay(5000);
                                        secondTimer.start();

                                    }
                                });
                                firstTimer.setRepeats(false);
                                firstTimer.setInitialDelay(9000);
                                firstTimer.start();

                                second.setVisible(true);


                            }
                        });

                        first.setLocationRelativeTo(null);
                        first.getContentPane().add(openSecond);
                        first.setSize(300, 300);
                        first.setVisible(true);

                    }
                });


                main.setContentPane(openFirst);
                main.setLocationRelativeTo(null);
                main.setSize(400, 400);
                main.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
                main.setVisible(true);


            }
        });
    }

    private static void showInfo(Window owner, String msg) {
        JDialog dlg = new MyDialog(owner);
        dlg.getContentPane().add(new JLabel(msg));
        dlg.setSize(200, 100);
        dlg.setLocationRelativeTo(null);
        dlg.setVisible(true);
    }
    public static class MyDialog extends JDialog {
        WindowBlocker modalBlocker;

        public MyDialog(Window owner) {
            super(owner);
            setModal(true);
            modalBlocker = new WindowBlocker(owner, this);
        }

        @Override
        public void setVisible(boolean b) {
            if (b)
                modalBlocker.block();
            else
                modalBlocker.release(false);
            super.setVisible(b);
        }
    }
    public static class WindowBlocker implements WindowListener {
        private static final RefSet<Window> mGlobalBlockedWindows = new RefSet<Window>();

        private static HashSet<Window> computeAllWindowsToBeBlocked(Window blockedWindow, Window blockingWindow) {
            HashSet<Window> result = new HashSet<Window>();
            Window root = blockedWindow;
            while(!(root instanceof Frame)) {
                if(root instanceof Dialog) {
                    if(((Dialog) root).isModal()) {
                        break;
                    }
                }
                root = root.getOwner();
            }
            addWindowAndOwnedWindows(root, result);
            if(blockingWindow != null)
                result.remove(blockingWindow);
            return result;
        }

        private static void addWindowAndOwnedWindows(Window window, HashSet<Window> windowsToBeBlocked) {
            windowsToBeBlocked.add(window);
            for(Window child : window.getOwnedWindows()) {
                if(child.isVisible()) {
                    addWindowAndOwnedWindows(child, windowsToBeBlocked);
                }
            }
        }

        private HashSet<Window> mWindowsToBeBlocked;
        private final Window mBlockedWindow;
        private final Window mBlockingWindow;

        public WindowBlocker(Window blockedWindow, Window blockingWindow) {
            mBlockedWindow = blockedWindow;
            mBlockingWindow = blockingWindow;
        }

        public void block() {
            synchronized(mGlobalBlockedWindows) {
                mWindowsToBeBlocked = computeAllWindowsToBeBlocked(mBlockedWindow, mBlockingWindow);
                if(!mGlobalBlockedWindows.add(mBlockedWindow)) //this window is already blocked
                    return;
                for(Window window : mWindowsToBeBlocked) {
                    if(true /*OSUtil.isMac()*/) {
                        window.setEnabled(false);
                        window.addWindowListener(this);
                    } else {
                        window.setFocusableWindowState(false);   //setFocusable(false) has a very ugly effect in focus handling on windows
                        window.setEnabled(false);                //the window catches the focus after the last control so we use window.setFocusableWindowState(false)
                    }
                    mGlobalBlockedWindows.add(window);
                }
            }
        }

        public void release(boolean avoidToFront) {
            synchronized(mGlobalBlockedWindows) {
                if(mWindowsToBeBlocked == null)
                    return;
                if(mGlobalBlockedWindows.removeRefCount(mBlockedWindow) > 1)
                    return;
                for(Window window : mWindowsToBeBlocked) {
                    if(mGlobalBlockedWindows.removeRefCount(window) == 0) {
                        if(true/*OSUtil.isMac()*/) {
                            window.removeWindowListener(this);
                            window.setEnabled(true);
                        } else {
                            window.setEnabled(true);
                            window.setFocusableWindowState(true);
                        }
                    }
                }
                mWindowsToBeBlocked = null;
            }
            if(!avoidToFront)
                mBlockedWindow.toFront();
        }

        public static boolean isWindowBlocked(Window w) {
            synchronized(mGlobalBlockedWindows) {
                return mGlobalBlockedWindows.contains(w);
            }
        }

        public void windowOpened(WindowEvent e) {
        }

        public void windowClosing(WindowEvent e) {
        }

        public void windowClosed(WindowEvent e) {
        }

        public void windowIconified(WindowEvent e) {
        }

        public void windowDeiconified(WindowEvent e) {
        }

        public void windowActivated(WindowEvent e) {
            if (mBlockingWindow != null) {
                mBlockingWindow.toFront();
                mBlockingWindow.requestFocus();
            }
        }

        public void windowDeactivated(WindowEvent e) {
        }
    }





    public static class RefSet<E> extends AbstractSet<E> {
        private final HashMap<E,Integer> mItems = new HashMap<E,Integer>();

        public int size() {
            synchronized(mItems) {
                return mItems.size();
            }
        }
        public boolean isEmpty() {
            synchronized(mItems) {
                return mItems.isEmpty();
            }
        }
        public boolean contains(Object o) {
            synchronized(mItems) {
                return mItems.containsKey(o);
            }
        }
        public Iterator<E> iterator() {
            synchronized(mItems) {
                return mItems.keySet().iterator();
                /*return new ImmutableIterator<E>(mItems.keySet().iterator());*/
            }
        }
        public Object[] toArray() {
            synchronized(mItems) {
                return mItems.keySet().toArray();
            }
        }
        public <T> T[] toArray(T[] ts) {
            synchronized(mItems) {
                return mItems.keySet().toArray(ts);
            }
        }
        public boolean add(E o) {
            synchronized(mItems) {
                Integer i = mItems.get(o);
                if(i == null) {
                    mItems.put(o, 1);
                    return true;
                } else {
                    mItems.put(o, i + 1);
                    return false;
                }
            }
        }
        public boolean remove(Object o) {
            synchronized(mItems) {
                Integer i = mItems.get(o);
                if(i == null)
                    return false;
                if(i > 1)
                    mItems.put((E) o, i - 1);
                else
                    //noinspection SuspiciousMethodCalls
                    mItems.remove(o);
            }
            return true;
        }
        public int addRefCount(E o) {
            synchronized(mItems) {
                Integer i = mItems.get(o);
                if(i == null) {
                    mItems.put(o, 1);
                    return 1;
                } else {
                    mItems.put(o, i + 1);
                    return i + 1;
                }
            }
        }
        public int removeRefCount(E o) {
            synchronized(mItems) {
                Integer i = mItems.get(o);
                if(i == null)
                    return 0;
                if(i > 1) {
                    mItems.put(o, i - 1);
                    return i - 1;
                } else {
                    mItems.remove(o);
                    return 0;
                }
            }
        }
        /**
         * CAUTION all state info will be lost
         */
        public void clear() {
            synchronized(mItems) {
                mItems.clear();
            }
        }
        public int getRefCount(E o) {
            synchronized(mItems) {
                return mItems.get(o);
            }
        }
    }
}
