/*
 * SwingUtil.java  $Revision: 1.8 $  $Date: 2007/10/03 04:39:40 $
 *
 * Copyright (C) 2007 Meldcraft Corporation
 *
 * This file is part of Scaffold.
 * 
 * Scaffold is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Scaffold is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Scaffold.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.meldcraft.gui.swing;

import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.FocusManager;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.meldcraft.util.ReflectUtil;

/**
 * @version $Revision: 1.8 $ $Date: 2007/10/03 04:39:40 $
 * @author rob
 */
public class SwingUtil
{
// Static variables
    private static final MouseListener  sMouseListener =
                                            new DoNothingMouseAdapter();
    private static final KeyListener    sKeyListener =
                                             new DoNothingKeyAdapter();

// Member variables
// Constructors and initialization
    // prevent instantiation
    private SwingUtil()
    {
    }

// Interface implemenations
// Public methods
    public static void bringToFront(final Object ui)
    {
        invokeLater(new Runnable() {
            public void run()
            {
                Object cui = ui;
                if (cui instanceof Component)
                {
                    cui = getJFrame((Component)cui);
                    if (cui == null)
                    {
                        cui = ui;
                    }
                }

                if (cui instanceof JFrame)
                {
                    JFrame frame = (JFrame)cui;
                    if (frame.getState() == Frame.ICONIFIED)
                    {
                        frame.setState(Frame.NORMAL);
                    }
                    frame.setVisible(true);
                }
                else if (cui instanceof Window)
                {
                    ((Window)cui).setVisible(true);
                }
            }
        });
    }

    public static void centerOnScreen(Window window)
    {
        centerOnScreen(window, window.getWidth(), window.getHeight(), true);
    }

    public static void centerOnScreen(Window window, Dimension margin)
    {
        Dimension screenSize = getScreenSize(window);
        centerOnScreen(window, screenSize.width-margin.width*2,
                               screenSize.height-margin.height*2, false);
    }
    
    public static void centerOnScreen(Window window, int desiredWidth,
                                      int desiredHeight, boolean retainAspect)
    {
        int width = desiredWidth;
        int height = desiredHeight;

        // center window on the screen
        Dimension screenSize = getScreenSize(window);
        if (height > screenSize.height)
        {
            height = screenSize.height;
            if (retainAspect)
                width = (int)((double)height * desiredWidth / desiredHeight);
        }

        if (width > screenSize.width)
        {
            width = screenSize.width;
            if (retainAspect)
                height = (int)((double)width * desiredHeight / desiredWidth);
        }

        window.setBounds((screenSize.width - width) / 2,
                         (screenSize.height - height) / 2,
                         width, height);
    }

    public static void invokeLater(Runnable runner)
    {   
        if (SwingUtilities.isEventDispatchThread())
        {
            runner.run();
        }
        else
        {
            SwingUtilities.invokeLater(runner);
        }
    }

    public static void waitForInvokeLater(final Runnable runner)
    {
        if (SwingUtilities.isEventDispatchThread())
        {
            runner.run();
        }
        else
        {
            final Object[] lock = {null};
            Runnable wrun = new Runnable() {
                public void run()
                {
                    try
                    {
                        runner.run();
                        lock[0] = "";
                    }
                    catch (Throwable e)
                    {
                        lock[0] = e;
                    }
                    finally
                    {
                        synchronized(lock)
                        {
                            lock.notifyAll();
                        }
                    }
                }
            };

            synchronized(lock)
            {
                SwingUtilities.invokeLater(wrun);

                try
                {
                    lock.wait();
                }
                catch (InterruptedException e)
                {
                    // should be done with runner
                    if (lock[0] == null)
                    {
                        throw new RuntimeException(e);
                    }
                    else if (lock[0] instanceof Throwable)
                    {
                        throw new RuntimeException((Throwable)lock[0]);
                    }
                }
            }
        }
    }

    public static void invokeAndWait(Runnable runner)
        throws InterruptedException, InvocationTargetException
    {   
        if (SwingUtilities.isEventDispatchThread())
        {
            runner.run();
        }
        else
        {
            SwingUtilities.invokeAndWait(runner);
        }
    }

    public static void invokeLater(Object target, String method)
    {
        invokeLater(target, method, (Object[])null);
    }

    public static void invokeLater(Object target, String method, Object arg)
    {
        invokeLater(target, method, new Object[]{arg});
    }

    public static void invokeLater(Object target, String method, Object[] args)
    {
        Runnable run = new ReflectRunner(target, method, args);
        invokeLater(run);
    }

    public static void waitForInvokeLater(Object target, String method, Object[] args)
    {
        Runnable run = new ReflectRunner(target, method, args);
        waitForInvokeLater(run);
    }
    
    public static void invokeAndWait(Object target, String method, Object[] args)
        throws InterruptedException, InvocationTargetException
    {
        Runnable run = new ReflectRunner(target, method, args);
        invokeAndWait(run);
    }

    public static void setBusyCursor(Component comp)
    {
        setBusyCursor(comp, true);
    }       

    public static void setBusyCursor(final Component comp, final boolean busy)
    {
        final Runnable run = new Runnable() {
            public void run()
            {
                Component glassPane = null;

                Window window = (comp instanceof Window) ? (Window)comp : (Window)SwingUtilities.getAncestorOfClass(Window.class, comp);
                if (window != null)
                {
                    try
                    {
                        Object val = ReflectUtil.invokeMethod(window, "getGlassPane", null);
                        if (val instanceof Component)
                        {
                            glassPane = (Component)val;
                        }
                    }
                    catch (Throwable e)
                    {
                        // ignored
                    }
                }

                if (glassPane != null)
                {
                    if (busy)
                    {
                        glassPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));        
                        glassPane.addMouseListener(sMouseListener);
                        glassPane.addKeyListener(sKeyListener);
                        glassPane.setVisible(true);
                        glassPane.requestFocus();
                    }
                    else
                    {
                        glassPane.setVisible(false);
                        glassPane.removeMouseListener(sMouseListener);
                        glassPane.removeKeyListener(sKeyListener);
                        FocusManager mgr = FocusManager.getCurrentManager();
                        if (mgr != null)
                        {
                            mgr.focusPreviousComponent(glassPane);
                        }
                    }
                }
            }
        };

        invokeLater(run);
    }

    static public Window getWindow(Component comp)
    {
        Window ret;
        if (comp instanceof Window)
        {
            ret = (Window)comp;
        }
        else
        {
            ret = (Window)SwingUtilities.getAncestorOfClass(Window.class, comp);
        }
        
        return ret;
    }

    static public JFrame getJFrame(Component comp)
    {
        if (comp instanceof JFrame)
        {
            return (JFrame)comp;
        }
        else
        {
            return (JFrame)SwingUtilities.getAncestorOfClass(JFrame.class, comp);
        }
    }

    static public JDialog getJDialog(Component comp)
    {
        if (comp instanceof JDialog)
        {
            return (JDialog)comp;
        }
        else
        {
            return (JDialog)SwingUtilities.getAncestorOfClass(JDialog.class, comp);
        }
    }

    static public Window getJFrameOrJDialog(Component comp)
    {
        Window ret = null;

        while (comp != null)
        {
            if ((comp instanceof JFrame) || (comp instanceof JDialog))
            {
                ret = (Window)comp;
                break;
            }
            comp = comp.getParent();
        }

        return ret;
    }

    static public void prepareDialog(Dialog dialog)
    {
        prepareDialog(dialog, -1, -1);
    }

    static public void prepareDialog(Dialog dialog, int width, int height)
    {
        dialog.pack();

        if (width < 0)
        {
            width = dialog.getWidth();
        }

        if (height < 0)
        {
            height = dialog.getHeight();
        }

        Component parent = dialog.getParent();
        Rectangle rect = parent.getBounds();
        dialog.setBounds(rect.x + (rect.width - width) / 2,
                rect.y + (rect.height - height) / 2,
                width, height);
    }

    public static Dimension getScreenSize(Window window)
    {
        //Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Rectangle bounds = window.getGraphicsConfiguration().getBounds();
        Dimension screenSize = new Dimension(bounds.width, bounds.height);
        return screenSize;
    }

    public static ImageIcon loadImageIconURL(String urlstr)
    {
        ImageIcon img = null;
        if (urlstr != null)
        {
            try
            {
                URL url = new URL(urlstr);
                img = loadImageIcon(url);
            }
            catch (MalformedURLException e)
            {
                // ignored
            }
        }
        return img;
    }

    public static ImageIcon loadImageIcon(URL url)
    {
        ImageIcon img = new ImageIcon(url);

        // wait for image to load completely
        int status = img.getImageLoadStatus();
        while ((status != MediaTracker.COMPLETE) &&
               (status != MediaTracker.ABORTED) &&
               (status != MediaTracker.ERRORED))
        {
            try{Thread.sleep(100);} catch(Exception e){}
            status = img.getImageLoadStatus();
        }

        return img;
    }

    public static ImageIcon loadImageIcon(String fileName)
    {
        ImageIcon img = new ImageIcon(fileName);

        // wait for image to load completely
        int status = img.getImageLoadStatus();
        while ((status != MediaTracker.COMPLETE) &&
               (status != MediaTracker.ABORTED) &&
               (status != MediaTracker.ERRORED))
        {
            try{Thread.sleep(100);} catch(Exception e){}
            status = img.getImageLoadStatus();
        }

        return img;
    }

    public static ImageIcon loadResourceImage(Object resource, String suffix)
    {
        return loadResourceImage(resource, ReflectUtil.getSimpleClassName(resource.getClass()), suffix);
    }

    public static ImageIcon loadResourceImage(Object resource, String name, String suffix)
    {
        return loadResourceImage(resource.getClass().getClassLoader(), resource, name, suffix);
    }

    public static ImageIcon loadResourceImage(ClassLoader loader, Object resource, String name, String suffix)
    {
        ImageIcon ret = null;

        if (suffix == null)
        {
            suffix = "";
        }

        URL url = null;
        String[] suffixes = {".png", ".PNG", ".gif", ".GIF", ".jpg", ".JPG", ".jpeg", ".JPEG"};
        String rname = (resource instanceof String) ? (String)resource : resource.getClass().getName();
        String[] names = {rname.replace('.', '/').replaceAll("/[^/]+$", "/") + name,
                          name
                         };
        for (int i=0 ; (i < suffixes.length) && (url == null) ; i++)
        {
            for (int j=0 ; (j < names.length) && (url == null) ; j++)
            {
                String res = names[j] + suffix + suffixes[i];
                url = loader.getResource(res);
            }
        }

        if (url != null)
        {
            ret = loadImageIcon(url);
        }

        return ret;
    }

// Protected methods
// Private methods
// Inner classes
    public static class DoNothingMouseAdapter extends MouseAdapter
    {
        public void mouseClicked(MouseEvent e)
        {
        }
    }

    public static class DoNothingKeyAdapter extends KeyAdapter
    {
        public void keyPressed(KeyEvent event)
        {
            event.consume();
        }
        public void keyReleased(KeyEvent event)
        {
            event.consume();
        }
        public void keyTyped(KeyEvent event)
        {
            event.consume();
        }
    }

    public static class ReflectRunner implements Runnable
    {
        private Object      mTarget;
        private String      mMethod;
        private Object[]    mArgs;

        public ReflectRunner(Object target, String method, Object[] args)
        {
            mTarget = target;
            mMethod = method;
            mArgs = args;
        }

        public void run()
        {
            try
            {
                ReflectUtil.invokeMethod(mTarget, mMethod, mArgs);
            }
            catch (NoSuchMethodException e)
            {
                throw new RuntimeException("Error invoking target.", e);
            }
            catch (IllegalAccessException e)
            {
                throw new RuntimeException("Error invoking target.", e);
            }
            catch (InvocationTargetException e)
            {
                throw new RuntimeException("Error invoking target.", e);
            }
        }
    }
}
