/**
* \cond LICENSE
* ********************************************************************
* This is a conditional block for preventing the DoxyGen documentation
* tool to include this license header within the description of each
* source code file. If you want to include this block, please define
* the LICENSE parameter into the provided DoxyFile.
* ********************************************************************
*
* JCarrierPigeon - A notification library
* Copyright (c) 2010, Paulo Roberto Massa Cereda
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. Neither the name of the project's author nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ********************************************************************
* End of the LICENSE conditional block
* ********************************************************************
* \endcond
*
* <b>Notification.java</b>: provides the notification features to any
* <code>javax.swing.JFrame</code> or <code>javax.swing.JWindow</code>
* object. Please note that this new class introduced in version 1.3 is
* the new replacement for <b>net.sf.jcarrierpigeon.CarrierPigeon</b>,
* assigned as deprecated.
*/
package net.sf.jcarrierpigeon;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JWindow;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTarget;
/**
* Provides the notification features to any <code>javax.swing.JFrame</code>
* or <code>javax.swing.JWindow</code> object. In order to achieve a greater
* effect, it's highly recommended to remove the window border of the provided
* <code>javax.swing.JFrame</code>. You don't need to worry with that when
* using <code>javax.swing.JWindow</code>, since it has no borders at all.
* Please note that this new class introduced in version 1.3 is the new
* replacement for <b>net.sf.jcarrierpigeon.CarrierPigeon</b>, assigned
* as deprecated. If you are using <b>net.sf.jcarrierpigeon.CarrierPigeon</b>,
* please change your code to use this class together with the new
* <b>net.sf.jcarrierpigeon.NotificationQueue</b>. Check the following example:
* @code
* JWindow window = new JWindow();
* Notification note = new Notification(window, WindowPosition.BOTTOMRIGHT, 25, 25, 1000);
* NotificationQueue queue = new NotificationQueue();
* queue.add(note);
* @endcode
* Note that <b>net.sf.jcarrierpigeon.Notification</b> takes the very same
* parameters of the deprecated <b>net.sf.jcarrierpigeon.CarrierPigeon</b>
* class. Aside from the addition of <b>net.sf.jcarrierpigeon.NotificationQueue</b>,
* there is not so much change in code.
*
* @author Paulo Roberto Massa Cereda
* @version 1.3
* @since 1.3
*/
public class Notification implements TimingTarget {
private WindowPosition windowPosition;
private WindowType windowType;
private int thisHeight;
private int thisWidth;
// window object
private JFrame windowJFrame;
private JWindow windowJWindow;
// coordinates
private double borderX, borderY;
private double boundX, boundY;
private double positionX, positionY;
// animation control
private int duration;
private AnimationFrame animationFrame;
// animators, each one representing one state
// on AnimationFrame class
private Animator animatorHandlerOnShow;
private Animator animatorHandlerOnDisplay;
private Animator animatorHandlerOnClose;
// time in milliseconds to animate windows
// on show and close events
private int timeToAnimate = 500;
/**
* Constructor method for a basic <code>javax.swing.JFrame</code> object.
* It basically builds the notification model according to the provided
* parameters. Just keep in mind the provided <code>javax.swing.JFrame</code>
* is <b>disposed</b> after the notification being displayed. Check the
* following example:
* @code
* JFrame window = new JFrame();
* Notification note = new Notification(window, WindowPosition.BOTTOMRIGHT, 25, 25, 1000);
* NotificationQueue queue = new NotificationQueue();
* queue.add(note);
* @endcode
* @param window The window to act as a notification. Please, remove the borders
* in order to achieve a greater effect.
* @param windowPosition The window position on screen. You may choose one amongst
* four states, each one representing the screen corners.
* @param borderX The distance in pixels the window must keep from the X axis border. If
* the notification is right-aligned, this border will be from the right side, and so forth.
* Usually 50 pixels or less is an acceptable value for this parameter.
* @param borderY The distance in pixels the window must keep from the Y axis border. If
* the notification is aligned from the top, this border will be from the top itself, and so forth.
* Usually 50 pixels or less is an acceptable value for this parameter.
* @param duration The notification display duration in milliseconds. So if you want 2 seconds, you need
* to multiply it by 1000; 2 seconds times 1000 = 2000 milliseconds.
*/
public Notification(JFrame window, WindowPosition windowPosition, int borderX, int borderY, int duration) {
// JFrame object
this.windowType = WindowType.JFRAME;
this.windowJWindow = null;
// setting some attributes
this.windowPosition = windowPosition;
this.windowJFrame = window;
this.borderX = borderX;
this.borderY = borderY;
// window attributes
this.thisHeight = windowJFrame.getHeight();
this.thisWidth = windowJFrame.getWidth();
// set the animation duration
this.duration = duration;
{
// retrieve the screen resolution and set some attributes
Rectangle rect = getScreenResolution();
this.boundX = rect.getWidth();
this.boundY = rect.getHeight();
}
// second calculation: based on the window position and the provided
// values, calculate positions on screen and the global payload for
// that specific region
switch (this.windowPosition) {
case BOTTOMRIGHT:
this.positionX = this.boundX - (this.thisWidth + this.borderX);
this.positionY = this.boundY - (this.thisHeight + this.borderY);
break;
case BOTTOMLEFT:
this.positionX = this.borderX;
this.positionY = this.boundY - (this.thisHeight + this.borderY);
break;
case TOPRIGHT:
this.positionX = this.boundX - (this.thisWidth + this.borderX);
this.positionY = this.borderY;
break;
case TOPLEFT:
this.positionX = this.borderX;
this.positionY = this.borderY;
break;
}
}
/**
* Constructor method for a basic <code>javax.swing.JWindow</code> object.
* It basically builds the notification model according to the provided
* parameters. Just keep in mind the provided <code>javax.swing.JWindow</code>
* is <b>disposed</b> after the notification being displayed. Check the
* following example:
* @code
* JWindow window = new JWindow();
* Notification note = new Notification(window, WindowPosition.BOTTOMRIGHT, 25, 25, 1000);
* NotificationQueue queue = new NotificationQueue();
* queue.add(note);
* @endcode
* @param window The window to act as a notification. Note that a <code>javax.swing.JWindow</code>
* object has no borders by default, so there is no need of removing them.
* @param windowPosition The window position on screen. You may choose one amongst
* four states, each one representing the screen corners.
* @param borderX The distance in pixels the window must keep from the X axis border. If
* the notification is right-aligned, this border will be from the right side, and so forth.
* Usually 50 pixels or less is an acceptable value for this parameter.
* @param borderY The distance in pixels the window must keep from the Y axis border. If
* the notification is aligned from the top, this border will be from the top itself, and so forth.
* Usually 50 pixels or less is an acceptable value for this parameter.
* @param duration The notification display duration in milliseconds. So if you want 2 seconds, you need
* to multiply it by 1000; 2 seconds times 1000 = 2000 milliseconds.
*/
public Notification(JWindow window, WindowPosition windowPosition, int borderX, int borderY, int duration) {
// JFrame object
this.windowType = WindowType.JWINDOW;
this.windowJFrame = null;
// setting some attributes
this.windowPosition = windowPosition;
this.windowJWindow = window;
this.borderX = borderX;
this.borderY = borderY;
// window attributes
this.thisHeight = windowJWindow.getHeight();
this.thisWidth = windowJWindow.getWidth();
// set the animation duration
this.duration = duration;
{
// retrieve the screen resolution and set some attributes
Rectangle rect = getScreenResolution();
this.boundX = rect.getWidth();
this.boundY = rect.getHeight();
}
// second calculation: based on the window position and the provided
// values, calculate positions on screen and the global payload for
// that specific region
switch (this.windowPosition) {
case BOTTOMRIGHT:
this.positionX = this.boundX - (this.thisWidth + this.borderX);
this.positionY = this.boundY - (this.thisHeight + this.borderY);
break;
case BOTTOMLEFT:
this.positionX = this.borderX;
this.positionY = this.boundY - (this.thisHeight + this.borderY);
break;
case TOPRIGHT:
this.positionX = this.boundX - (this.thisWidth + this.borderX);
this.positionY = this.borderY;
break;
case TOPLEFT:
this.positionX = this.borderX;
this.positionY = this.borderY;
break;
}
}
/**
* Calculates the screen size.
* @return A <code>java.awt.Rectangle</code> with the exact size of the screen.
*/
private Rectangle getScreenResolution() {
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
return environment.getMaximumWindowBounds();
}
/**
* Calculates the current window position based on the Y axis and the
* fraction of elapsed time.
* @param x Fraction of elapsed time. This value is on a continuum interval, 0 <= x <= 1.
* @return An <code>int</code> value representing the current Y value according to the
* elapsed time.
*/
private int calculateCurrentPositionOnY(float x) {
int result = 0;
// checks if the animation is the beginning
if (animationFrame == AnimationFrame.ONSHOW) {
// calculates the position, using the following math function
switch (windowPosition) {
case BOTTOMRIGHT:
case BOTTOMLEFT:
result = (int) (positionY + ((boundY - positionY) * (1 - x)));
break;
case TOPRIGHT:
case TOPLEFT:
result = (int) (positionY - ((thisHeight + borderY) * (1 - x)));
break;
}
} else {
// animation is now closing
if (animationFrame == AnimationFrame.ONCLOSE) {
// calculates the position, now using the inverse math function
switch (windowPosition) {
case BOTTOMRIGHT:
case BOTTOMLEFT:
result = (int) (positionY + ((boundY - positionY) * (x)));
break;
case TOPRIGHT:
case TOPLEFT:
result = (int) (positionY - ((thisHeight + borderY) * (x)));
break;
}
} else {
// seems animation is now on display, then just return the very
// same position
result = (int) positionY;
}
}
return result;
}
/**
* Implements the <code>timingEvent</code> method from <code>org.jdesktop.animation.timing.TimingTarget</code>.
* Please don't call this function directly.
* @param f The continnum interval referring to the animation.
*/
public void timingEvent(float f) {
// animate the window based on the Y axis
setCurrentWindowBounds((int) positionX, calculateCurrentPositionOnY(f), thisWidth, thisHeight);
}
/**
* Implements the <code>begin</code> method from <code>org.jdesktop.animation.timing.TimingTarget</code>.
* This method is called before animation begins. Please don't call this function directly.
*/
public void begin() {
// empty body
}
/**
* Implements the <code>end</code> method from <code>org.jdesktop.animation.timing.TimingTarget</code>.
* This method is called after the animation finishes. Please don't call this function directly.
*/
public void end() {
// checks if animation just finished the presenting state
if (animationFrame == AnimationFrame.ONSHOW) {
// create a new animation handler
animatorHandlerOnDisplay = new Animator(duration, 1, Animator.RepeatBehavior.LOOP, this);
// sets the current animation state
animationFrame = AnimationFrame.ONDISPLAY;
// run it
animatorHandlerOnDisplay.start();
} else {
// now checking if animation just finished displaying
if (animationFrame == AnimationFrame.ONDISPLAY) {
// create a new animation handler
animatorHandlerOnClose = new Animator(timeToAnimate, 1, Animator.RepeatBehavior.LOOP, this);
// sets the current animation state
animationFrame = AnimationFrame.ONCLOSE;
// run it
animatorHandlerOnClose.start();
} else {
// animation is done, so hide and dispose window
setCurrentWindowVisible(false);
disposeCurrentWindow();
}
}
}
/**
* Implements the <code>repeat</code> method from <code>org.jdesktop.animation.timing.TimingTarget</code>.
* This function is called on every animation repetition. Please don't call this function directly.
*/
public void repeat() {
// empty body
}
/**
* Performs the animation itself based on the parameters provided in the
* constructor method. Keep in mind this method is synchronized. Check
* the following example:
* @code
* JWindow window = new JWindow();
* Notification note = new Notification(window, WindowPosition.BOTTOMRIGHT, 25, 25, 1000);
* note.animate();
* @endcode
* Wherever possible, please use the new notification queue manager
* <b>net.sf.jcarrierpigeon.NotificationQueue</b>.
*/
public synchronized void animate() {
// set the animation state
animationFrame = AnimationFrame.ONSHOW;
// define some window properties
setCurrentWindowAlwaysOnTop(true);
setCurrentWindowVisible(true);
// defines the animator handler from Timing Framework
// first animator handler
animatorHandlerOnShow = new Animator(timeToAnimate, 1, Animator.RepeatBehavior.LOOP, this);
// start animation
animatorHandlerOnShow.start();
}
/**
* Checks if the notification process is still running. It basically calls the
* <code>isRunning</code> method from <code>org.jdesktop.animation.timing.Animator</code>.
* @return <code>true</code> if the notification is still running, or <code>false</code>
* otherwise.
*/
public boolean isRunning() {
return (animatorHandlerOnShow.isRunning())
|| (animatorHandlerOnDisplay.isRunning())
|| (animatorHandlerOnClose).isRunning();
}
/**
* Sets the bounds of the current window. It's basically a call to the
* inner window <code>setBounds</code> method.
* @param x Coordinate X
* @param y Coordinate Y
* @param width Width
* @param height Height
*/
private void setCurrentWindowBounds(int x, int y, int width, int height) {
switch (windowType) {
case JFRAME:
windowJFrame.setBounds(x, y, width, height);
break;
case JWINDOW:
windowJWindow.setBounds(x, y, width, height);
break;
}
}
/**
* Sets the visibility of the current window. It's basically a call to the
* inner window <code>setVisible</code> method.
* @param value <code>true</code> if window should be visible, or <code>false</code>
* otherwise.
*/
private void setCurrentWindowVisible(boolean value) {
switch (windowType) {
case JFRAME:
windowJFrame.setVisible(value);
break;
case JWINDOW:
windowJWindow.setVisible(value);
break;
}
}
/**
* Sets the window parameter of being on top of other windows. It's basically
* a call to the inner window <code>setAlwaysOnTop</code> method.
* @param value <code>true</code> if window should be on top of other windows, or
* <code>false</code> otherwise.
*/
private void setCurrentWindowAlwaysOnTop(boolean value) {
switch (windowType) {
case JFRAME:
windowJFrame.setAlwaysOnTop(value);
break;
case JWINDOW:
windowJWindow.setAlwaysOnTop(value);
break;
}
}
/**
* Dispose the current window. It's basically a call to the inner window
* <code>dispose</code> method.
*/
private void disposeCurrentWindow() {
switch (windowType) {
case JFRAME:
windowJFrame.dispose();
break;
case JWINDOW:
windowJWindow.dispose();
break;
}
}
/**
* Sets the animation speed. This method was rewritten and the name was
* replaced by a more meaningful one. Check the following example:
* @code
* JWindow window = new JWindow();
* Notification note = new Notification(window, WindowPosition.BOTTOMRIGHT, 25, 25, 1000);
* note.setAnimationSpeed(500);
* NotificationQueue queue = new NotificationQueue();
* queue.add(note);
* @endcode
* @param milliseconds The notification effects duration in milliseconds.
* Usually 500 milliseconds or less is an acceptable value for this parameter.
*/
public void setAnimationSpeed(int milliseconds) {
this.timeToAnimate = milliseconds;
}
}