/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.spark.roar.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.Border;
import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.spark.component.RolloverButton;
import org.jivesoftware.spark.util.ImageCombiner;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.log.Log;
/**
* Class to show tosters in multiplatform
*
* @author daniele piras
*/
public class SparkToaster {
/**
* The default Hand cursor.
*/
public static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR);
/**
* The default Text Cursor.
*/
public static final Cursor DEFAULT_CURSOR = new Cursor(
Cursor.DEFAULT_CURSOR);
// Width of the toster
private int toasterWidth = 200;
// Height of the toster
private int toasterHeight = 150;
// Step for the toaster
private int step = 20;
// Step time
private int stepTime = 20;
// Show time
private int displayTime = 3000;
// Current number of toaster...
private int currentNumberOfToaster = 0;
// Last opened toaster
private int maxToaster = 0;
// Font used to display message
private Font font;
// Color for border
private Color borderColor;
// Color for toaster
private Color toasterColor;
// Set message color
private Color messageColor;
// Set the margin
int margin;
// Flag that indicate if use alwaysOnTop or not.
// method always on top start only SINCE JDK 5 !
boolean useAlwaysOnTop = true;
private String title;
private Border border;
private Action customAction;
private Window window;
private JPanel mainPanel = new JPanel();
private TitleLabel titleLabel;
private boolean hideable = true;
/**
* Constructor to initialized toaster component...
*/
public SparkToaster() {
// Set default font...
font = new Font("Dialog", Font.PLAIN, 11);
// Border color
borderColor = new Color(245, 153, 15);
toasterColor = Color.WHITE;
messageColor = Color.BLACK;
useAlwaysOnTop = true;
}
/**
* Class that rappresent a single toaster
*
* @author daniele piras
*/
class SingleToaster extends javax.swing.JWindow {
private static final long serialVersionUID = 1L;
// Label to store Icon
// Text area for the message
private JTextArea message = new JTextArea();
/**
* Simple costructor that initialized components...
*/
public SingleToaster() {
initComponents();
}
/**
* Function to initialized components
*/
private void initComponents() {
message.setFont(getToasterMessageFont());
mainPanel.setBackground(Color.white);
message.setOpaque(false);
mainPanel.setLayout(new GridBagLayout());
message.setMargin(new Insets(2, 2, 2, 2));
message.setLineWrap(true);
message.setWrapStyleWord(true);
message.setForeground(getMessageColor());
titleLabel = new TitleLabel(getTitle(), true);
titleLabel.setForeground(new Color(87, 166, 211));
titleLabel.setFont(new Font("Dialog", Font.BOLD, 13));
mainPanel.add(titleLabel,
new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0,
GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0,
0), 0, 0));
titleLabel.getCloseButton().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
}
});
if (border != null) {
mainPanel.setBorder(border);
}
message.setForeground(Color.BLACK);
message.setOpaque(false);
getContentPane().add(mainPanel);
mainPanel.addMouseListener(new PaneMouseListener());
message.addMouseListener(new PaneMouseListener());
pack();
setSize(toasterWidth, toasterHeight);
mainPanel
.setBorder(BorderFactory.createLineBorder(Color.lightGray));
}
/**
* Start toaster animation...
*/
public void animate() {
(new Animation(this)).start();
}
private class PaneMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
if (customAction != null) {
customAction.actionPerformed(null);
}
if (hideable) {
setVisible(false);
dispose();
}
}
public void mouseEntered(MouseEvent e) {
message.setCursor(HAND_CURSOR);
setCursor(HAND_CURSOR);
}
public void mouseExited(MouseEvent e) {
message.setCursor(DEFAULT_CURSOR);
setCursor(DEFAULT_CURSOR);
}
}
}
/**
* Class that manage the animation
*/
class Animation extends Thread {
SingleToaster toaster;
public Animation(SingleToaster toaster) {
this.toaster = toaster;
}
/**
* Animate vertically the toaster. The toaster could be moved from
* bottom to upper or to upper to bottom
*
* @param posx
* X position for toaster.
* @param fromY
* Y from position
* @param toY
* Y to position
* @throws InterruptedException
* if animation is interrupted
*/
protected void animateVertically(int posx, int fromY, int toY)
throws InterruptedException {
toaster.setLocation(posx, fromY);
if (toY < fromY) {
for (int i = fromY; i > toY; i -= step) {
toaster.setLocation(posx, i);
Thread.sleep(stepTime);
}
} else {
for (int i = fromY; i < toY; i += step) {
toaster.setLocation(posx, i);
Thread.sleep(stepTime);
}
}
toaster.setLocation(posx, toY);
toaster.invalidate();
toaster.validate();
toaster.repaint();
}
public void run() {
try {
boolean animateFromBottom = true;
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
Rectangle screenRect = ge.getMaximumWindowBounds();
int screenHeight = screenRect.height;
int startYPosition;
int stopYPosition;
if (screenRect.y > 0) {
animateFromBottom = false; // Animate from top!
}
int maxToasterInSceen = screenHeight / toasterHeight;
int posx = screenRect.width - toasterWidth - 1;
toaster.setLocation(posx, screenHeight);
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
toaster.setVisible(true);
}
});
} catch (Exception e) {
Log.error(e);
}
if (useAlwaysOnTop) {
toaster.setAlwaysOnTop(true);
}
if (animateFromBottom) {
startYPosition = screenHeight;
stopYPosition = startYPosition - toasterHeight - 1;
if (currentNumberOfToaster > 0) {
stopYPosition = stopYPosition
- (maxToaster % maxToasterInSceen * toasterHeight);
} else {
maxToaster = 0;
}
} else {
startYPosition = screenRect.y - toasterHeight;
stopYPosition = screenRect.y;
if (currentNumberOfToaster > 0) {
stopYPosition = stopYPosition
+ (maxToaster % maxToasterInSceen * toasterHeight);
} else {
maxToaster = 0;
}
}
currentNumberOfToaster++;
maxToaster++;
animateVertically(posx, startYPosition, stopYPosition);
Thread.sleep(displayTime);
animateVertically(posx, stopYPosition, startYPosition);
currentNumberOfToaster--;
toaster.setVisible(false);
toaster.dispose();
} catch (Exception e) {
Log.error(e);
}
}
}
/**
* Show a toaster with the specified message and the associated icon.
*
* @param icon
* Icon to show in toaster popup.
* @param msg
* Message to show in toaster popup.
*/
public void showToaster(Icon icon, String msg) {
SingleToaster singleToaster = new SingleToaster();
final JScrollPane pane = new JScrollPane(singleToaster.message);
pane.setOpaque(false);
pane.setBorder(BorderFactory.createEmptyBorder());
pane.getViewport().setBackground(Color.white);
mainPanel.add(pane, new GridBagConstraints(1, 2, 3, 1, 1.0, 1.0,
GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2,
5, 2, 5), 0, 0));
if (icon != null) {
titleLabel.setIcon(icon);
}
if (ModelUtil.hasLength(msg) && msg.startsWith("/me ")) {
msg = msg.replaceFirst("/me", getTitle());
singleToaster.message.setForeground(Color.MAGENTA);
}
singleToaster.message.setText(msg);
singleToaster.message.setCaretPosition(0);
singleToaster.animate();
window = singleToaster;
}
/**
* Show a toaster with the specified message and the associated icon.
*
* @param title
* Title to use in toaster popup
* @param comp
* Component to add to toaster popup
*/
public void showToaster(final String title, final Component comp) {
SingleToaster singleToaster = new SingleToaster();
mainPanel.add(comp, new GridBagConstraints(1, 2, 3, 1, 1.0, 1.0,
GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2,
0, 0, 0), 0, 0));
titleLabel.setTitle(title);
singleToaster.animate();
window = singleToaster;
}
public void showToaster(Icon icon) {
SingleToaster singleToaster = new SingleToaster();
if (icon != null) {
titleLabel.setIcon(icon);
}
singleToaster.animate();
window = singleToaster;
}
public void showToaster() {
SingleToaster singleToaster = new SingleToaster();
singleToaster.animate();
window = singleToaster;
}
/**
* Show a toaster with the specified message.
*
* @param msg
* Message to display.
*/
public void showToaster(String msg) {
showToaster(null, msg);
}
/**
* @return Returns the font
*/
public Font getToasterMessageFont() {
return font;
}
/**
* Set the font for the message
*
* @param f
* Font to set on toaster messages.
*/
public void setToasterMessageFont(Font f) {
font = f;
}
/**
* @return Returns the borderColor.
*/
public Color getBorderColor() {
return borderColor;
}
/**
* @param borderColor
* The borderColor to set.
*/
public void setBorderColor(Color borderColor) {
this.borderColor = borderColor;
}
/**
* @return Returns the displayTime.
*/
public int getDisplayTime() {
return displayTime;
}
/**
* @param displayTime
* The displayTime to set.
*/
public void setDisplayTime(int displayTime) {
this.displayTime = displayTime;
}
/**
* @return Returns the margin.
*/
public int getMargin() {
return margin;
}
/**
* @param margin
* The margin to set.
*/
public void setMargin(int margin) {
this.margin = margin;
}
/**
* @return Returns the messageColor.
*/
public Color getMessageColor() {
return messageColor;
}
/**
* @param messageColor
* The messageColor to set.
*/
public void setMessageColor(Color messageColor) {
this.messageColor = messageColor;
}
/**
* @return Returns the step.
*/
public int getStep() {
return step;
}
/**
* @param step
* The step to set.
*/
public void setStep(int step) {
this.step = step;
}
/**
* @return Returns the stepTime.
*/
public int getStepTime() {
return stepTime;
}
/**
* @param stepTime
* The stepTime to set.
*/
public void setStepTime(int stepTime) {
this.stepTime = stepTime;
}
/**
* @return Returns the toasterColor.
*/
public Color getToasterColor() {
return toasterColor;
}
/**
* @param toasterColor
* The toasterColor to set.
*/
public void setToasterColor(Color toasterColor) {
this.toasterColor = toasterColor;
}
/**
* @return Returns the toasterHeight.
*/
public int getToasterHeight() {
return toasterHeight;
}
/**
* @param toasterHeight
* The toasterHeight to set.
*/
public void setToasterHeight(int toasterHeight) {
this.toasterHeight = toasterHeight;
}
/**
* @return Returns the toasterWidth.
*/
public int getToasterWidth() {
return toasterWidth;
}
/**
* @param toasterWidth
* The toasterWidth to set.
*/
public void setToasterWidth(int toasterWidth) {
this.toasterWidth = toasterWidth;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Border getBorder() {
return border;
}
public void setBorder(Border border) {
this.border = border;
}
public void setCustomAction(Action action) {
this.customAction = action;
}
public void setComponent(Component comp) {
mainPanel.add(comp, new GridBagConstraints(1, 2, 3, 1, 1.0, 1.0,
GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2,
5, 2, 5), 0, 0));
}
public void close() {
if (window != null) {
window.dispose();
}
}
public Window getWindow() {
return window;
}
public void hideTitle() {
titleLabel.setVisible(false);
}
class TitleLabel extends JPanel {
private static final long serialVersionUID = -5163519932953987400L;
private JLabel label;
private RolloverButton closeButton;
public TitleLabel(String text, final boolean showCloseIcon) {
setLayout(new GridBagLayout());
label = new JLabel(text);
label.setFont(new Font("Dialog", Font.BOLD, 11));
label.setHorizontalTextPosition(JLabel.LEFT);
label.setHorizontalAlignment(JLabel.LEFT);
add(label, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5), 0, 0));
closeButton = new RolloverButton(
SparkRes.getImageIcon(SparkRes.CLOSE_IMAGE));
if (showCloseIcon) {
add(closeButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.EAST, GridBagConstraints.NONE,
new Insets(5, 5, 5, 5), 0, 0));
}
setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0,
Color.lightGray));
}
public void setIcon(Icon icon) {
if (icon.getIconHeight() > 64 || icon.getIconWidth() > 64) {
Image image = ImageCombiner.iconToImage(icon);
label.setIcon(new ImageIcon(image.getScaledInstance(-1, 64,
Image.SCALE_SMOOTH)));
} else {
label.setIcon(icon);
}
}
public RolloverButton getCloseButton() {
return closeButton;
}
public void setTitle(String title) {
label.setText(title);
}
public void paintComponent(Graphics g) {
final Image backgroundImage = Default.getImageIcon(
Default.TOP_BOTTOM_BACKGROUND_IMAGE).getImage();
double scaleX = getWidth()
/ (double) backgroundImage.getWidth(null);
double scaleY = getHeight()
/ (double) backgroundImage.getHeight(null);
AffineTransform xform = AffineTransform.getScaleInstance(scaleX,
scaleY);
((Graphics2D) g).drawImage(backgroundImage, xform, this);
}
}
public void setHidable(boolean hideable) {
this.hideable = hideable;
}
}