package org.marketcetera.photon.notification;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.mylyn.internal.provisional.commons.ui.AbstractNotificationPopup;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.marketcetera.core.notifications.INotification;
import org.marketcetera.core.notifications.INotification.Severity;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* A popup that displays an {@link INotification}.
*
* This class may be subclassed to override
* <ul>
* <li>{@link #getImage(INotification)} - the images displayed for the
* notifications</li>
* <li>{@link #getLabelFont()} - the font used for the heading labels</li>
* <li>{@link #fitLineToWidth(GC, String, int)} - the algorithm for truncating
* the subject text to fit on a single line</li>
* </ul>
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: DesktopNotificationPopup.java 16154 2012-07-14 16:34:05Z colin $
* @since 0.8.0
*/
@ClassVersion("$Id: DesktopNotificationPopup.java 16154 2012-07-14 16:34:05Z colin $")//$NON-NLS-1$
@SuppressWarnings("restriction")
public class DesktopNotificationPopup extends AbstractNotificationPopup {
/**
* time in milliseconds before the popup closes automatically
*/
private static final int CLOSE_DELAY = 4000;
/**
* used for truncating long text
*/
private static final String ELLIPSIS = Messages.POPUP_ELLIPSIS.getText();
/**
* the notification
*/
private final INotification mNotification;
/**
* Constructor.
*
* Subclasses should call this constructor, but may customize/override
* configuration by calling {@link #setFadingEnabled(boolean)} and
* {@link #setDelayClose(long)}.
*
* @param display
* the display used by {@link AbstractNotificationPopup}
* @param notification
* the notification to display, cannot be null
* @throws AssertionFailedException
* if notification is null
*/
public DesktopNotificationPopup(Display display, INotification notification) {
super(display);
Assert.isNotNull(notification);
this.mNotification = notification;
setFadingEnabled(true);
setDelayClose(CLOSE_DELAY);
}
@Override
protected final void createContentArea(Composite parent) {
createHeading(parent);
createLabel(parent, mNotification.getBody(), parent.getFont(), SWT.WRAP);
}
/**
* Creates the heading area of the popup.
*
* @param parent
* the parent
*/
private void createHeading(Composite parent) {
Composite composite = new Composite(parent, SWT.NO_FOCUS);
composite.setBackground(parent.getBackground());
GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
// layout depends on whether there is an image
Image image = getImage(mNotification);
GridLayoutFactory layout = GridLayoutFactory.swtDefaults().spacing(15,
5);
if (image != null) {
layout.numColumns(3).applyTo(composite);
Label imageLabel = new Label(composite, SWT.NO_FOCUS);
imageLabel.setBackground(parent.getBackground());
GridDataFactory.defaultsFor(imageLabel).span(1, 3).applyTo(
imageLabel);
imageLabel.setImage(image);
} else {
layout.numColumns(2).applyTo(composite);
}
createLabel(composite, Messages.POPUP_SUBJECT_LABEL.getText(),
getLabelFont(), SWT.RIGHT);
// Special label that truncates text with "..." as necessary
final Label subject = new Label(composite, SWT.NO_FOCUS);
subject.setBackground(parent.getBackground());
subject.setFont(parent.getFont());
GridDataFactory.fillDefaults().grab(true, false).applyTo(subject);
subject.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
subject.setText(fitLineToWidth(e.gc,
mNotification.getSubject(), e.width));
// The text only needs to be calculated once.
subject.removePaintListener(this);
}
});
createLabel(composite, Messages.POPUP_PRIORITY_LABEL.getText(),
getLabelFont(), SWT.RIGHT);
createLabel(composite, getSeverityLabel(mNotification.getSeverity()),
parent.getFont(), SWT.NONE);
createLabel(composite, Messages.POPUP_TIMESTAMP_LABEL.getText(),
getLabelFont(), SWT.RIGHT);
createLabel(composite, mNotification.getDate().toString(), parent
.getFont(), SWT.NONE);
}
/**
* Helper method to create a label.
*
* @param parent
* the parent composite
* @param text
* the label text
* @param font
* the label font
* @param style
* the label style
* @return a label configured with the provided parameters
*/
private Label createLabel(Composite parent, String text, Font font,
int style) {
Label label = new Label(parent, SWT.NO_FOCUS | style);
label.setBackground(parent.getBackground());
label.setFont(font);
GridDataFactory.defaultsFor(label).applyTo(label);
label.setText(text);
return label;
}
/**
* Helper method to provide label text for a given {@link Severity}.
*
* @param severity
* the severity
* @return label for the severity
*/
private String getSeverityLabel(Severity severity) {
switch (severity) {
case HIGH:
return Messages.POPUP_SEVERITY_LABEL_HIGH.getText();
case MEDIUM:
return Messages.POPUP_SEVERITY_LABEL_MEDIUM.getText();
case LOW:
return Messages.POPUP_SEVERITY_LABEL_LOW.getText();
default:
return ""; //$NON-NLS-1$
}
}
/**
* Provides the label font. Default is to the bold version of the JFace
* default font.
*
* Subclasses can override to provide a different font.
*
* @return the font to use for heading labels
*/
protected Font getLabelFont() {
return JFaceResources.getFontRegistry().getBold(
JFaceResources.DEFAULT_FONT);
}
/**
* Provides the image to use for the given notification. Default is based on
* severity:
* <ul>
* <li>HIGH - SWT.ICON_ERROR</li>
* <li>MEDIUM - SWT.ICON_WARNING</li>
* <li>LOW - SWT.ICON_INFORMATION</li>
* </ul>
*
* Subclass can return null if no image is desired.
*
* @return the image to display or null for no image
*/
protected Image getImage(INotification notification) {
switch (notification.getSeverity()) {
case HIGH:
return getSWTImage(SWT.ICON_ERROR);
case MEDIUM:
return getSWTImage(SWT.ICON_WARNING);
default:
return getSWTImage(SWT.ICON_INFORMATION);
}
}
/**
* Strategy for truncating text to a specified width using ellipsis. The
* default strategy tries to break at a space if possible.
*
* Subclass implementations must return a String that fits inside the given
* width when rendered using the provided GC.
*
* @param gc
* the graphics context
* @param text
* the text to fit
* @param width
* the maximum width
* @return a new string, shortened with ellipsis if necessary
*/
protected String fitLineToWidth(GC gc, String text, int width) {
int pixels = 0;
int dotextent = gc.stringExtent(ELLIPSIS).x;
if (dotextent >= width) {
// No point truncating if the ellipsis is greater than the provided
// width
return ""; //$NON-NLS-1$
}
StringCharacterIterator iter = new StringCharacterIterator(text);
pixels += gc.getAdvanceWidth(iter.current());
char c;
while ((c = iter.next()) != CharacterIterator.DONE) {
pixels += gc.getAdvanceWidth(c);
if (pixels > width) {
pixels += dotextent;
while ((pixels -= gc.getAdvanceWidth(c)) > width)
c = iter.previous();
int index = iter.getIndex();
while (c != ' ' && c != CharacterIterator.DONE)
c = iter.previous();
if (c != CharacterIterator.DONE)
index = iter.getIndex();
return text.substring(0, index) + ELLIPSIS;
}
}
return text;
}
/**
* Helper method to get an <code>Image</code> from the provided SWT image
* constant.
*
* @param imageID
* the SWT image constant
* @return image the image
*/
private Image getSWTImage(final int imageID) {
return getShell().getDisplay().getSystemImage(imageID);
}
@Override
public final int open() {
// Simply call the superclass implementation. This method was overridden
// purely to hide the discouraged access warning from clients.
return super.open();
}
}