/* Copyright (C) 2007 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Nomad 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sf.nmedit.nomad.boot.splash;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.java.plugin.boot.SplashHandler;
import org.java.plugin.util.ExtendedProperties;
public class NomadSplash implements SplashHandler
{
public static final String KEY_PREFIX = "";
/**
* default false
*/
public static final String KEY_METER_ENABLED = KEY_PREFIX+"meter.enabled";
public static final String KEY_METER_BOUNDS = KEY_PREFIX+"meter.bounds";
public static final String KEY_METER_BOUNDS_ROUND_RECT = KEY_PREFIX+"meter.roundrect";
public static final String KEY_METER_FILL = KEY_PREFIX+"meter.fill";
public static final String KEY_TEXT = KEY_PREFIX+"text";
public static final String KEY_TEXT_BOUNDS = KEY_PREFIX+"text.bounds";
public static final String KEY_TEXT_ENABLED = KEY_PREFIX+"text.enabled";
public static final String KEY_TEXT_FILL = KEY_PREFIX+"text.fill";
public static final String KEY_TEXT_FONTNAME = KEY_PREFIX+"text.font.name";
public static final String KEY_TIMEOUT = KEY_PREFIX+"timeout";
public static final String KEY_CLOSE_ONE_CLICK = KEY_PREFIX+"closeonclick";
public static final String KEY_BLOCK_UNTIL_VISIBLE = KEY_PREFIX+"blockuntilvisible";
private static final int SP_IMAGE_LOADING = 0;
private static final int SP_IMAGE_AVAILABLE = 1;
private static final int SP_IMAGE_PAINTED_ONCE = 2;
private static final int SP_ABORTED = 3;
private static final int SP_VISIBLE = 4;
private static final int SP_CLOSE_ON_CLICK = 5;
private static final int SP_BLOCK_UNTIL_VISIBLE = 6;
private static final int SP_DISPOSING_WINDOW = 7;
private volatile int flags = 0;
private void setFlag(int aFlag, boolean aValue)
{
if(aValue) {
flags |= (1 << aFlag);
} else {
flags &= ~(1 << aFlag);
}
checkFlags();
}
private boolean getFlag(int aFlag)
{
int mask = (1 << aFlag);
return ((flags & mask) == mask);
}
// splash window properties
private volatile int spSplashTimeout = 3000;
private SplashWindow spSplashWindow;
// splash image properties
private URL spSplashImageURL;
private Image spSplashImage;
private Dimension spSplashImageSize;
private SplashImageObserver spImageObserver;
// meter properties
private volatile float spMeterProgress = 0f;
private boolean spMeterEnabled = false;
private Rectangle spMeterBounds;
private boolean spMeterRoundRect = false;
private Color spMeterFill;
// text properties
private boolean spTextEnabled = false;
private volatile String spText = null;
private Rectangle spTextBounds;
private Color spTextFill;
private Font spTextFont;
// locks
private final Object paintLock = new Object();
private final Object windowLock = new Object();
private volatile long splashPaintedTime = 0;
private Color getColorProperty(ExtendedProperties p, String propertyName, Color defaultValue)
{
String pValue = p.getProperty(propertyName);
if (pValue == null)
return defaultValue;
try
{
if (pValue.length() == 1+8)
{
int i = Integer.decode(pValue).intValue();
int a = (i>>24)&0xFF;
if (a == 0)
return null;
int r = (i>>16)&0xFF;
int g = (i>>8)&0xFF;
int b = (i)&0xFF;
return new Color(r, g, b, a);
}
return Color.decode(pValue);
}
catch (NumberFormatException e)
{
// ignore
}
return defaultValue;
}
private boolean getBooleanProperty(ExtendedProperties p, String propertyName, boolean defaultValue)
{
String pValue = p.getProperty(propertyName);
if (pValue == null)
return defaultValue;
try
{
return Boolean.parseBoolean(pValue);
}
catch (NumberFormatException e)
{
// ignore
}
return defaultValue;
}
private static final String spRectanglePropertyRegex = "\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*";
private Pattern spRectanglePropertyPattern;
private Rectangle getRectangleProperty(ExtendedProperties p, String propertyName)
{
String pValue = p.getProperty(propertyName);
if (pValue == null)
return null;
if (spRectanglePropertyPattern == null)
spRectanglePropertyPattern = Pattern.compile(spRectanglePropertyRegex);
Matcher matcher = spRectanglePropertyPattern.matcher(pValue);
if (!matcher.matches())
return null;
try
{
return new Rectangle( Integer.parseInt(matcher.group(1)),
Integer.parseInt(matcher.group(2)),
Integer.parseInt(matcher.group(3)),
Integer.parseInt(matcher.group(4)));
}
catch (NumberFormatException e)
{
// ignore
return null;
}
}
private int getIntegerProperty(ExtendedProperties p, String propertyName, int defaultValue)
{
try
{
return Integer.parseInt(p.getProperty(propertyName));
}
catch (RuntimeException e)
{
return defaultValue;
}
}
public void configure(ExtendedProperties p)
{
// meter
spMeterFill = getColorProperty(p, KEY_METER_FILL, null);
spMeterBounds = getRectangleProperty(p, KEY_METER_BOUNDS);
spMeterRoundRect = getBooleanProperty(p, KEY_METER_BOUNDS_ROUND_RECT, false);
spMeterEnabled =
getBooleanProperty(p, KEY_METER_ENABLED, false)
&& (spMeterFill != null)
&& (spMeterBounds != null)
&& (spMeterBounds.width>0)
&& (spMeterBounds.height>0);
// text
spText = p.getProperty(KEY_TEXT);
spTextFill = getColorProperty(p, KEY_TEXT_FILL, null);
spTextBounds = getRectangleProperty(p, KEY_TEXT_BOUNDS);
String fontname = p.getProperty(KEY_TEXT_FONTNAME);
if (fontname == null)
fontname = "sansserif";
spTextFont = createFont(fontname, spTextBounds.height);
spTextEnabled =
getBooleanProperty(p, KEY_TEXT_ENABLED, false)
&& (spTextFill != null)
&& (spTextBounds != null)
&& (spTextBounds.width>0)
&& (spTextBounds.height>0)
&& (spTextFont != null);
spSplashTimeout =
getIntegerProperty(p, KEY_TIMEOUT, 3000);
setFlag(SP_CLOSE_ON_CLICK, getBooleanProperty(p, KEY_CLOSE_ONE_CLICK, true));
setFlag(SP_BLOCK_UNTIL_VISIBLE, getBooleanProperty(p, KEY_BLOCK_UNTIL_VISIBLE, false));
}
private static int pixelsToPoint(int pixels, int dpi)
{
return (int) Math.floor((pixels * 72.0) / dpi);
}
private static Font createFont(String fontname, int heightInPixel)
{
int fontsize;
try
{
int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
fontsize = pixelsToPoint(heightInPixel, dpi);
}
catch (HeadlessException e)
{
return null;
}
return fontsize>0 ? new Font(fontname, Font.PLAIN, fontsize) : null;
}
public URL getImage()
{
return spSplashImageURL;
}
public Object getImplementation()
{
return this;
}
public float getProgress()
{
return spMeterProgress;
}
public String getText()
{
return spText;
}
public boolean isVisible()
{
return getFlag(SP_VISIBLE);
}
public void setImage(URL url)
{
if (!eq(spSplashImageURL, url))
{
this.spSplashImageURL = url;
}
}
public void setProgress(float progress)
{
if (progress<0) progress = 0;
else if (progress>1) progress = 1f;
if (this.spMeterProgress != progress)
{
this.spMeterProgress = progress;
if (spMeterEnabled)
repaint(spMeterBounds);
}
}
private void repaint(Rectangle r)
{
spSplashBufferDirty = true;
synchronized (windowLock)
{
if (spSplashWindow != null)
spSplashWindow.repaint(r.x, r.y, r.width, r.height);
}
}
private boolean spSplashBufferDirty = true;
private BufferedImage spSplashBuffer;
private void checkSplashBuffer()
{
boolean fullRepaint = false;
if (spSplashBuffer == null && spSplashImageSize != null)
{
spSplashBuffer = new BufferedImage(
spSplashImageSize.width,
spSplashImageSize.height,
BufferedImage.TYPE_INT_RGB);
spSplashBufferDirty = true;
fullRepaint = true;
}
if (!spSplashBufferDirty)
return;
Graphics2D g = spSplashBuffer.createGraphics();
try
{
if (fullRepaint)
g.drawImage(spSplashImage, 0, 0, null);
if (spMeterEnabled)
{
if (!fullRepaint)
paintRegion(g, spSplashImage, spMeterBounds);
g.setColor(spMeterFill);
if (spMeterRoundRect)
{
if (spMeterProgress>0)
{
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int arc = Math.min(spMeterBounds.width, spMeterBounds.height);
g.fillRoundRect(spMeterBounds.x, spMeterBounds.y,
(int) Math.round(spMeterBounds.width * spMeterProgress),
spMeterBounds.height, arc, arc);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
}
}
else
{
if (spMeterProgress>0)
{
g.fillRect(spMeterBounds.x, spMeterBounds.y,
(int) Math.round(spMeterBounds.width * spMeterProgress),
spMeterBounds.height);
}
}
}
if (spTextEnabled)
{
if (!fullRepaint)
paintRegion(g, spSplashImage, spTextBounds);
if (spText != null)
{
FontMetrics fm = g.getFontMetrics();
g.setFont(spTextFont);
g.setColor(spTextFill);
g.setClip(spTextBounds.x, spTextBounds.y, spTextBounds.width, spTextBounds.height);
g.drawString(spText, spTextBounds.x, spTextBounds.y+fm.getAscent()-fm.getDescent());
}
}
}
finally
{
g.dispose();
}
spSplashBufferDirty = false;
}
private void paintRegion(Graphics2D g, Image img, Rectangle rect)
{
int r = rect.x+rect.width;
int b = rect.y+rect.height;
g.drawImage(img, rect.x, rect.y, r, b, rect.x, rect.y, r, b, null);
}
public void setText(String text)
{
if (!eq(this.spText, text))
{
this.spText = text;
if (spTextEnabled)
repaint(spTextBounds);
}
}
public void setVisible(boolean visible)
{
if (getFlag(SP_VISIBLE) != visible)
{
setFlag(SP_VISIBLE, visible);
}
}
private void mouseClicked()
{
if (getFlag(SP_CLOSE_ON_CLICK))
{
disposeWindow();
setVisible(false);
}
}
private void checkFlags()
{
if (getFlag(SP_IMAGE_PAINTED_ONCE))
{
if (splashPaintedTime == 0)
splashPaintedTime = System.currentTimeMillis();
}
if (getFlag(SP_ABORTED))
{
setFlag(SP_VISIBLE, false);
return;
}
if (getFlag(SP_VISIBLE))
{
if (!getFlag(SP_IMAGE_LOADING))
{
setFlag(SP_IMAGE_LOADING, true);
spImageObserver = new SplashImageObserver();
spImageObserver.prepareImage();
return ;
}
if (getFlag(SP_IMAGE_AVAILABLE))
{
synchronized (windowLock)
{
if (spSplashWindow == null)
{
checkSplashBuffer(); // render buffer for the first time
spSplashWindow = new SplashWindow();
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
spSplashWindow.setBounds(
(screen.width-spSplashImageSize.width)/2,
(screen.height-spSplashImageSize.height)/2,
spSplashImageSize.width, spSplashImageSize.height);
spSplashWindow.setVisible(true);
if (getFlag(SP_BLOCK_UNTIL_VISIBLE))
{
while (getFlag(SP_VISIBLE)
&& (!(getFlag(SP_ABORTED)||getFlag(SP_IMAGE_PAINTED_ONCE))))
{
synchronized (paintLock)
{
try
{
paintLock.wait(250);
}
catch (InterruptedException e)
{
// ignore
}
}
}
}
}
}
}
}
else
{
synchronized (windowLock)
{
if (spSplashWindow == null || getFlag(SP_DISPOSING_WINDOW))
return ;
}
if (spSplashTimeout <= 0 || ((splashPaintedTime+
spSplashTimeout)<System.currentTimeMillis()))
disposeWindow();
else
{
setFlag(SP_DISPOSING_WINDOW, true);
(new Thread()
{
public void run()
{
while ((!Thread.interrupted())
&& (!getFlag(SP_ABORTED))
&& ((splashPaintedTime+
spSplashTimeout)>System.currentTimeMillis()))
{
synchronized(windowLock)
{
if (spSplashWindow == null)
break;
try
{
windowLock.wait(Math.max(0,spSplashTimeout-(System.currentTimeMillis()
-splashPaintedTime)));
}
catch (InterruptedException e)
{
// ignore
}
}
}
disposeWindow();
}
}
) .start();
}
}
}
private void disposeWindow()
{
Runnable r = new Runnable() { public void run()
{
synchronized (windowLock)
{
if (spSplashWindow != null)
{
spSplashWindow.dispose();
spSplashWindow = null;
setFlag(SP_VISIBLE, false);
}
setFlag(SP_DISPOSING_WINDOW, false);
windowLock.notifyAll();
}
}};
if (EventQueue.isDispatchThread())
EventQueue.invokeLater(r);
else
r.run();
}
private static boolean eq(Object a, Object b)
{
return a == b || (a != null && a.equals(b));
}
private class SplashImageObserver implements ImageObserver
{
public void prepareImage()
{
Toolkit toolkit = Toolkit.getDefaultToolkit();
URL url = spSplashImageURL;
if (url == null)
io_abort();
// get the image
spSplashImage = toolkit.getImage(url);
// monitor the image loading process
if (toolkit.prepareImage(spSplashImage, -1, -1, this))
{
io_complete();
}
}
private void io_complete()
{
spImageObserver = null;
int w = spSplashImage.getWidth(null);
int h = spSplashImage.getHeight(null);
if (w>0 && h>0)
spSplashImageSize = new Dimension(w, h);
setFlag(SP_IMAGE_AVAILABLE, true);
}
private boolean io_isAbortRequested()
{
return getFlag(SP_ABORTED);
}
private void io_abort()
{
spImageObserver = null;
setFlag(SP_ABORTED, true);
}
public boolean imageUpdate( Image image, int infoflags, int x, int y, int width, int height )
{
// return value in case we want to abort loading the image
final boolean IO_ABORT_LOADING = false;
// return value in case we still want to construct the splash image
final boolean IO_RETRIEVE_DATA = true;
// check for errors or if there is a request to abort loading
if (((infoflags & (ERROR|ABORT)) != 0) || io_isAbortRequested())
{
io_abort();
return IO_ABORT_LOADING;
}
// check if image is complete
if ((infoflags & ALLBITS) != 0)
{
io_complete();
// image is complete - we are done
return IO_ABORT_LOADING;
}
// not done yet - get more data
return IO_RETRIEVE_DATA;
}
}
private void paintSplash(Graphics g)
{
checkSplashBuffer();
Image img = spSplashBuffer == null ? spSplashImage : spSplashBuffer;
g.drawImage(img, 0, 0, null);
if (!getFlag(SP_IMAGE_PAINTED_ONCE))
{
synchronized (paintLock)
{
setFlag(SP_IMAGE_PAINTED_ONCE, true);
paintLock.notifyAll();
}
}
}
private class SplashWindow extends Window
{
/**
*
*/
private static final long serialVersionUID = -1330056937401666849L;
public SplashWindow()
{
super(new SplashFrame());
enableEvents(MouseEvent.MOUSE_EVENT_MASK);
}
public void update(Graphics g)
{
// do not erase background because we fill it completely with the image
paint(g);
}
public void paint(Graphics g)
{
paintSplash(g);
}
public void processMouseEvent(MouseEvent e)
{
if (e.getID() == MouseEvent.MOUSE_CLICKED)
mouseClicked();
super.processMouseEvent(e);
}
}
private static class SplashFrame extends Frame
{
/**
*
*/
private static final long serialVersionUID = -8574332776737247773L;
public void update(Graphics g)
{
// do not erase background because we fill it completely with the image
paint(g);
}
}
}