/*
* Created on Mar 7, 2006 10:42:32 PM
* Copyright (C) 2006 Aelitis, All Rights Reserved.
*
* This program 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.
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package org.gudy.azureus2.ui.swt.shells;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.ui.swt.ImageRepository;
import org.gudy.azureus2.ui.swt.Messages;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.mainwindow.TorrentOpener;
import com.aelitis.azureus.ui.swt.*;
/**
*
* +=====================================+
* | +----+ |
* | |Icon| Big Bold Title |
* | +----+ |
* | Wrapping message text |
* | with optional URL links |
* | +-----+ |
* | |BGImg| XX more slideys.. |
* | | Icon| Closing in XX secs |
* | +-----+ [HideAll] [Details] [Hide] |
* +=====================================+
*
* @author TuxPaper
* @created Mar 7, 2006
*
*/
public class MessageSlideShell
{
private static boolean USE_SWT32_BG_SET = true;
private static final boolean DEBUG = false;
private final static String REGEX_URLHTML = "<A HREF=\"(.+?)\">(.+?)</A>";
/** Slide until there's this much gap between shell and edge of screen */
private final static int EDGE_GAP = 0;
/** Width used when BG image can't be loaded */
private final static int SHELL_DEF_WIDTH = 280;
/** Standard height of the shell. Shell may grow depending on text */
private final static int SHELL_MIN_HEIGHT = 150;
/** Maximimum height of popup. If text is too long, the full text will be
* put into details.
*/
private final static int SHELL_MAX_HEIGHT = 330;
/** Width of the details shell */
private final static int DETAILS_WIDTH = 550;
/** Height of the details shell */
private final static int DETAILS_HEIGHT = 180;
/** Synchronization for popupList */
private final static AEMonitor monitor = new AEMonitor("slidey_mon");
/** List of all popups ever created */
private static ArrayList historyList = new ArrayList();
/** Current popup being displayed */
private static int currentPopupIndex = -1;
/** Index of first message which the user has not seen (index) - set to -1 if we don't care. :) **/
private static int firstUnreadMessage = -1;
/** Shell for popup */
private Shell shell;
/** Composite in shell */
private Composite cShell;
/** popup could and closing in xx seconds label */
private Label lblCloseIn;
/** Button that hides all slideys in the popupList. Visible only when there's
* more than 1 slidey
*/
private Button btnHideAll;
/** Button to move to next message. Text changes from "Hide" to "Next"
* appropriately.
*/
private Button btnNext;
/** paused state of auto-close delay */
private boolean bDelayPaused = false;
/** List of SWT objects needing disposal */
private ArrayList disposeList = new ArrayList();
/** Text to put into details popup */
private String sDetails;
/** Position this popup is in the history list */
private int idxHistory;
private Image imgPopup;
/** Forces the timer feature to be active; overriding the default behavior */
private boolean forceTimer = true;
/** Open a popup using resource keys for title/text
*
* @param display Display to create the shell on
* @param iconID SWT.ICON_* constant for icon in top left
* @param keyPrefix message bundle key prefix used to get title and text.
* Title will be keyPrefix + ".title", and text will be set to
* keyPrefix + ".text"
* @param details actual text for details (not a key)
* @param textParams any parameters for text
*
* @note Display moved to end to remove conflict in constructors
*/
public MessageSlideShell(Display display, int iconID, String keyPrefix,
String details, String[] textParams) {
this(display, iconID, MessageText.getString(keyPrefix + ".title"),
MessageText.getString(keyPrefix + ".text", textParams), details);
}
public MessageSlideShell(Display display, int iconID, String keyPrefix,
String details, String[] textParams, Object[] relatedObjects) {
this(display, iconID, MessageText.getString(keyPrefix + ".title"),
MessageText.getString(keyPrefix + ".text", textParams), details,
relatedObjects, false);
}
/**
*
* @param display
* @param iconID
* @param keyPrefix
* @param details
* @param textParams
* @param relatedObjects
* @param forceTimer Forces the timer feature to be active; overriding the default logic
*/
public MessageSlideShell(Display display, int iconID, String keyPrefix,
String details, String[] textParams, Object[] relatedObjects,
boolean forceTimer) {
this(display, iconID, MessageText.getString(keyPrefix + ".title"),
MessageText.getString(keyPrefix + ".text", textParams), details,
relatedObjects, forceTimer);
}
public MessageSlideShell(Display display, int iconID, String title,
String text, String details) {
this(display, iconID, title, text, details, null, false);
}
/**
* Open Mr Slidey
*
* @param display Display to create the shell on
* @param iconID SWT.ICON_* constant for icon in top left
* @param title Text to put in the title
* @param text Text to put in the body
* @param details Text displayed when the Details button is pressed. Null
* for disabled Details button.
* @param forceTimer Forces the timer feature to be active; overriding the default logic
*/
public MessageSlideShell(Display display, int iconID, String title,
String text, String details, Object[] relatedObjects, boolean forceTimer) {
try {
monitor.enter();
this.forceTimer = forceTimer;
PopupParams popupParams = new PopupParams(iconID, title, text, details,
relatedObjects);
historyList.add(popupParams);
if (currentPopupIndex < 0) {
create(display, popupParams, true);
}
} catch (Exception e) {
Logger.log(new LogEvent(LogIDs.GUI, "Mr. Slidey Init", e));
disposeShell(shell);
Utils.disposeSWTObjects(disposeList);
} finally {
monitor.exit();
}
}
private MessageSlideShell(Display display, PopupParams popupParams,
boolean bSlide) {
create(display, popupParams, bSlide);
}
public static void displayLastMessage(final Display display,
final boolean last_unread) {
display.asyncExec(new AERunnable() {
public void runSupport() {
if (historyList.isEmpty()) {
return;
}
if (currentPopupIndex >= 0) {
return;
} // Already being displayed.
int msg_index = firstUnreadMessage;
if (!last_unread || msg_index == -1) {
msg_index = historyList.size() - 1;
}
new MessageSlideShell(display,
(PopupParams) historyList.get(msg_index), true);
}
});
}
/**
* Adds this message to the slide shell without forcing it to be displayed.
* @param relatedTo
*/
public static void recordMessage(int iconID, String title, String text,
String details, Object[] relatedTo) {
try {
monitor.enter();
historyList.add(new PopupParams(iconID, title, text, details, relatedTo));
if (firstUnreadMessage == -1) {
firstUnreadMessage = historyList.size() - 1;
}
} finally {
monitor.exit();
}
}
private void create(final Display display, final PopupParams popupParams,
boolean bSlide) {
firstUnreadMessage = -1; // Reset the last read message counter.
GridData gridData;
int shellWidth;
int style = SWT.ON_TOP;
boolean bDisableSliding = COConfigurationManager.getBooleanParameter("GUI_SWT_DisableAlertSliding");
if (bDisableSliding) {
bSlide = false;
style = SWT.NONE;
}
if (DEBUG)
System.out.println("create " + (bSlide ? "SlideIn" : "") + ";"
+ historyList.indexOf(popupParams) + ";");
idxHistory = historyList.indexOf(popupParams);
// 2 Assertions
if (idxHistory < 0) {
System.err.println("Not in popup history list");
return;
}
if (currentPopupIndex == idxHistory) {
System.err.println("Trying to open already opened!! " + idxHistory);
return;
}
try {
monitor.enter();
currentPopupIndex = idxHistory;
} finally {
monitor.exit();
}
if (DEBUG)
System.out.println("set currIdx = " + idxHistory);
sDetails = popupParams.details;
// Load Images
// Disable BG Image on OSX
if (imgPopup == null) {
if (Constants.isOSX && (SWT.getVersion() < 3221 || !USE_SWT32_BG_SET)) {
USE_SWT32_BG_SET = false;
imgPopup = null;
} else {
imgPopup = ImageRepository.getImage("popup");
}
}
Rectangle imgPopupBounds;
if (imgPopup != null) {
shellWidth = imgPopup.getBounds().width;
imgPopupBounds = imgPopup.getBounds();
} else {
shellWidth = SHELL_DEF_WIDTH;
imgPopupBounds = null;
}
Image imgIcon = null;
switch (popupParams.iconID) {
case SWT.ICON_ERROR:
imgIcon = ImageRepository.getImage("error");
break;
case SWT.ICON_WARNING:
imgIcon = ImageRepository.getImage("warning");
break;
case SWT.ICON_INFORMATION:
imgIcon = ImageRepository.getImage("info");
break;
default:
imgIcon = null;
break;
}
/*
* If forceTimer is true then we always show the counter for auto-closing the shell;
* otherwise proceed to the more fine-grained logic
*/
if (true == forceTimer) {
bDelayPaused = false;
} else {
// if there's a link, or the info is non-information,
// disable timer and mouse watching
bDelayPaused = UrlUtils.parseHTMLforURL(popupParams.text) != null
|| popupParams.iconID != SWT.ICON_INFORMATION || !bSlide;
}
// Pause the auto-close delay when mouse is over slidey
// This will be applies to every control
final MouseTrackAdapter mouseAdapter = bDelayPaused ? null
: new MouseTrackAdapter() {
public void mouseEnter(MouseEvent e) {
bDelayPaused = true;
}
public void mouseExit(MouseEvent e) {
bDelayPaused = false;
}
};
// Create shell & widgets
if (bDisableSliding) {
UIFunctionsSWT uiFunctions = UIFunctionsManagerSWT.getUIFunctionsSWT();
if (uiFunctions != null) {
Shell mainShell = uiFunctions.getMainShell();
if (mainShell != null) {
shell = new Shell(mainShell, style);
}
}
}
if (shell == null) {
shell = new Shell(display, style);
}
if (USE_SWT32_BG_SET) {
try {
shell.setBackgroundMode(SWT.INHERIT_DEFAULT);
} catch (NoSuchMethodError e) {
// Ignore
} catch (NoSuchFieldError e2) {
// ignore
}
}
Utils.setShellIcon(shell);
shell.setText(popupParams.title);
UISkinnableSWTListener[] listeners = UISkinnableManagerSWT.getInstance().getSkinnableListeners(
MessageSlideShell.class.toString());
for (int i = 0; i < listeners.length; i++) {
try {
listeners[i].skinBeforeComponents(shell, this, popupParams.relatedTo);
} catch (Exception e) {
Debug.out(e);
}
}
FormLayout shellLayout = new FormLayout();
shell.setLayout(shellLayout);
cShell = new Composite(shell, SWT.NULL);
GridLayout layout = new GridLayout(3, false);
cShell.setLayout(layout);
FormData formData = new FormData();
formData.left = new FormAttachment(0, 0);
formData.right = new FormAttachment(100, 0);
cShell.setLayoutData(formData);
Label lblIcon = new Label(cShell, SWT.NONE);
lblIcon.setImage(imgIcon);
lblIcon.setLayoutData(new GridData());
Label lblTitle = new Label(cShell, SWT.getVersion() < 3100 ? SWT.NONE
: SWT.WRAP);
gridData = new GridData(GridData.FILL_HORIZONTAL);
if (SWT.getVersion() < 3100)
gridData.widthHint = 140;
lblTitle.setLayoutData(gridData);
lblTitle.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
lblTitle.setText(popupParams.title);
FontData[] fontData = lblTitle.getFont().getFontData();
fontData[0].setStyle(SWT.BOLD);
fontData[0].setHeight((int) (fontData[0].getHeight() * 1.5));
Font boldFont = new Font(display, fontData);
disposeList.add(boldFont);
lblTitle.setFont(boldFont);
final Button btnDetails = new Button(cShell, SWT.TOGGLE);
btnDetails.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
Messages.setLanguageText(btnDetails, "popup.error.details");
gridData = new GridData();
btnDetails.setLayoutData(gridData);
btnDetails.addListener(SWT.MouseUp, new Listener() {
public void handleEvent(Event arg0) {
try {
boolean bShow = btnDetails.getSelection();
if (bShow) {
Shell detailsShell = new Shell(display, SWT.BORDER | SWT.ON_TOP);
Utils.setShellIcon(detailsShell);
detailsShell.setLayout(new FillLayout());
StyledText textDetails = new StyledText(detailsShell, SWT.READ_ONLY
| SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
textDetails.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
textDetails.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
textDetails.setWordWrap(true);
textDetails.setText(sDetails);
detailsShell.layout();
Rectangle shellBounds = shell.getBounds();
detailsShell.setBounds(shellBounds.x + shellBounds.width
- DETAILS_WIDTH, shellBounds.y - DETAILS_HEIGHT, DETAILS_WIDTH,
DETAILS_HEIGHT);
detailsShell.open();
shell.setData("detailsShell", detailsShell);
shell.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
Shell detailsShell = (Shell) shell.getData("detailsShell");
if (detailsShell != null && !detailsShell.isDisposed()) {
detailsShell.dispose();
}
}
});
// disable auto-close on opening of details
bDelayPaused = true;
removeMouseTrackListener(shell, mouseAdapter);
} else {
Shell detailsShell = (Shell) shell.getData("detailsShell");
if (detailsShell != null && !detailsShell.isDisposed()) {
detailsShell.dispose();
}
}
} catch (Exception e) {
Logger.log(new LogEvent(LogIDs.GUI, "Mr. Slidey DetailsButton", e));
}
}
});
createLinkLabel(cShell, true, popupParams);
lblCloseIn = new Label(cShell, SWT.TRAIL);
lblCloseIn.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
// Ensure computeSize computes for 2 lined label
lblCloseIn.setText("\n");
gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
gridData.horizontalSpan = 3;
lblCloseIn.setLayoutData(gridData);
final Composite cButtons = new Composite(cShell, SWT.NULL);
GridLayout gridLayout = new GridLayout();
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.verticalSpacing = 0;
if (Constants.isOSX)
gridLayout.horizontalSpacing = 0;
gridLayout.numColumns = (idxHistory > 0) ? 3 : 2;
cButtons.setLayout(gridLayout);
gridData = new GridData(GridData.HORIZONTAL_ALIGN_END
| GridData.VERTICAL_ALIGN_CENTER);
gridData.horizontalSpan = 3;
cButtons.setLayoutData(gridData);
btnHideAll = new Button(cButtons, SWT.PUSH);
Messages.setLanguageText(btnHideAll, "popup.error.hideall");
btnHideAll.setVisible(false);
btnHideAll.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
// XXX SWT.Selection doesn't work on latest GTK (2.8.17) & SWT3.2 for ON_TOP
btnHideAll.addListener(SWT.MouseUp, new Listener() {
public void handleEvent(Event arg0) {
cButtons.setEnabled(false);
shell.dispose();
}
});
if (idxHistory > 0) {
final Button btnPrev = new Button(cButtons, SWT.PUSH);
btnPrev.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
btnPrev.setText(MessageText.getString("popup.previous", new String[] {
"" + idxHistory
}));
btnPrev.addListener(SWT.MouseUp, new Listener() {
public void handleEvent(Event arg0) {
disposeShell(shell);
int idx = historyList.indexOf(popupParams) - 1;
if (idx >= 0) {
PopupParams item = (PopupParams) historyList.get(idx);
showPopup(display, item, false);
disposeShell(shell);
}
}
});
}
btnNext = new Button(cButtons, SWT.PUSH);
btnNext.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
int numAfter = historyList.size() - idxHistory - 1;
setButtonNextText(numAfter);
btnNext.addListener(SWT.MouseUp, new Listener() {
public void handleEvent(Event arg0) {
if (DEBUG)
System.out.println("Next Pressed");
if (idxHistory + 1 < historyList.size()) {
showPopup(display, (PopupParams) historyList.get(idxHistory + 1),
false);
}
disposeShell(shell);
}
});
// Image has gap for text at the top (with image at bottom left)
// trim top to height of shell
Point bestSize = cShell.computeSize(shellWidth, SWT.DEFAULT);
if (bestSize.y < SHELL_MIN_HEIGHT)
bestSize.y = SHELL_MIN_HEIGHT;
else if (bestSize.y > SHELL_MAX_HEIGHT) {
bestSize.y = SHELL_MAX_HEIGHT;
if (sDetails == null) {
sDetails = popupParams.text;
} else {
sDetails = popupParams.text + "\n===============\n" + sDetails;
}
}
if (imgPopup != null) {
// no text on the frog in the bottom left
int bottomHeight = cButtons.computeSize(SWT.DEFAULT, SWT.DEFAULT).y
+ lblCloseIn.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
if (bottomHeight < 50)
bestSize.y += 50 - bottomHeight;
final Image imgBackground = new Image(display, bestSize.x, bestSize.y);
disposeList.add(imgBackground);
GC gc = new GC(imgBackground);
int dstY = imgPopupBounds.height - bestSize.y;
if (dstY < 0)
dstY = 0;
gc.drawImage(imgPopup, 0, dstY, imgPopupBounds.width,
imgPopupBounds.height - dstY, 0, 0, bestSize.x, bestSize.y);
gc.dispose();
boolean bAlternateDrawing = true;
if (USE_SWT32_BG_SET) {
try {
shell.setBackgroundImage(imgBackground);
bAlternateDrawing = false;
} catch (NoSuchMethodError e) {
}
}
if (bAlternateDrawing) {
// Drawing of BG Image for pre SWT 3.2
cShell.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.drawImage(imgBackground, e.x, e.y, e.width, e.height, e.x,
e.y, e.width, e.height);
}
});
Color colorBG = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
final RGB bgRGB = colorBG.getRGB();
PaintListener paintListener = new PaintListener() {
// OSX: copyArea() causes a paint event, resulting in recursion
boolean alreadyPainting = false;
public void paintControl(PaintEvent e) {
if (alreadyPainting || e.width <= 0 || e.height <= 0) {
return;
}
alreadyPainting = true;
try {
Rectangle bounds = ((Control) e.widget).getBounds();
Image img = new Image(display, e.width, e.height);
e.gc.copyArea(img, e.x, e.y);
e.gc.drawImage(imgBackground, -bounds.x, -bounds.y);
// Set the background color to invisible. img.setBackground
// doesn't work, so change transparentPixel directly and roll
// a new image
ImageData data = img.getImageData();
data.transparentPixel = data.palette.getPixel(bgRGB);
Image imgTransparent = new Image(display, data);
// This is an alternative way of setting the transparency.
// Probably much slower
//int bgIndex = data.palette.getPixel(bgRGB);
//ImageData transparencyMask = data.getTransparencyMask();
//for (int y = 0; y < data.height; y++) {
// for (int x = 0; x < data.width; x++) {
// if (bgIndex == data.getPixel(x, y))
// transparencyMask.setPixel(x, y, 0);
// }
//}
//
//Image imgTransparent = new Image(display, data, transparencyMask);
e.gc.drawImage(imgTransparent, 0, 0, e.width, e.height, e.x, e.y,
e.width, e.height);
img.dispose();
imgTransparent.dispose();
} finally {
alreadyPainting = false;
}
}
};
shell.setBackground(colorBG);
cShell.setBackground(colorBG);
addPaintListener(cShell, paintListener, colorBG, true);
}
}
Rectangle bounds = null;
try {
UIFunctionsSWT uiFunctions = UIFunctionsManagerSWT.getUIFunctionsSWT();
if (uiFunctions != null) {
Shell mainShell = uiFunctions.getMainShell();
if (mainShell != null) {
bounds = mainShell.getMonitor().getClientArea();
}
} else {
Shell shell = display.getActiveShell();
if (shell != null) {
bounds = shell.getMonitor().getClientArea();
}
}
if (bounds == null) {
bounds = shell.getMonitor().getClientArea();
}
} catch (Exception e) {
}
if (bounds == null) {
bounds = display.getClientArea();
}
Rectangle endBounds;
if (bDisableSliding) {
endBounds = new Rectangle(((bounds.x + bounds.width) / 2)
- (bestSize.x / 2), ((bounds.y + bounds.height) / 2)
- (bestSize.y / 2), bestSize.x, bestSize.y);
} else {
int boundsX2 = bounds.x + bounds.width;
int boundsY2 = bounds.y + bounds.height;
endBounds = shell.computeTrim(boundsX2 - bestSize.x, boundsY2
- bestSize.y, bestSize.x, bestSize.y);
// bottom and right trim will be off the edge, calulate this trim
// and adjust it up and left (trim may not be the same size on all sides)
int diff = (endBounds.x + endBounds.width) - boundsX2;
if (diff >= 0)
endBounds.x -= diff + EDGE_GAP;
diff = (endBounds.y + endBounds.height) - boundsY2;
if (diff >= 0) {
endBounds.y -= diff + EDGE_GAP;
}
//System.out.println("best" + bestSize + ";mon" + bounds + ";end" + endBounds);
}
FormData data = new FormData(bestSize.x, bestSize.y);
cShell.setLayoutData(data);
btnDetails.setVisible(sDetails != null);
if (sDetails == null) {
gridData = new GridData();
gridData.widthHint = 0;
btnDetails.setLayoutData(gridData);
}
shell.layout();
btnNext.setFocus();
shell.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
Utils.disposeSWTObjects(disposeList);
if (currentPopupIndex == idxHistory) {
if (DEBUG)
System.out.println("Clear #" + currentPopupIndex + "/" + idxHistory);
try {
monitor.enter();
currentPopupIndex = -1;
} finally {
monitor.exit();
}
}
}
});
shell.addListener(SWT.Traverse, new Listener() {
public void handleEvent(Event event) {
if (event.detail == SWT.TRAVERSE_ESCAPE) {
disposeShell(shell);
event.doit = false;
}
}
});
if (mouseAdapter != null)
addMouseTrackListener(shell, mouseAdapter);
for (int i = 0; i < listeners.length; i++) {
try {
listeners[i].skinAfterComponents(shell, this, popupParams.relatedTo);
} catch (Exception e) {
Debug.out(e);
}
}
runPopup(endBounds, idxHistory, bSlide);
}
/**
* @param shell2
* @param b
*
* @since 3.0.0.9
*/
private void createLinkLabel(Composite shell, boolean tryLinkIfURLs,
PopupParams popupParams) {
Matcher matcher = Pattern.compile(REGEX_URLHTML, Pattern.CASE_INSENSITIVE).matcher(
popupParams.text);
boolean hasHTML = matcher.find();
if (tryLinkIfURLs && hasHTML) {
try {
Link linkLabel = new Link(cShell, SWT.WRAP);
GridData gridData = new GridData(GridData.FILL_BOTH);
gridData.horizontalSpan = 3;
linkLabel.setLayoutData(gridData);
linkLabel.setForeground(shell.getDisplay().getSystemColor(
SWT.COLOR_BLACK));
linkLabel.setText(popupParams.text);
linkLabel.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if (e.text.startsWith(":")) {
return;
}
if (e.text.endsWith(".torrent"))
TorrentOpener.openTorrent(e.text);
else
Utils.launch(e.text);
}
});
String tooltip = null;
matcher.reset();
while (matcher.find()) {
if (tooltip == null)
tooltip = "";
else
tooltip += "\n";
String url = matcher.group(1);
if (url != null && url.startsWith(":")) {
url = url.substring(1);
}
tooltip += matcher.group(2) + ": " + url;
}
linkLabel.setToolTipText(tooltip);
} catch (Throwable t) {
createLinkLabel(shell, false, popupParams);
}
} else {
// 3.0
Label linkLabel = new Label(cShell, SWT.WRAP);
GridData gridData = new GridData(GridData.FILL_BOTH);
gridData.horizontalSpan = 3;
linkLabel.setLayoutData(gridData);
//<a href="http://atorre.s">test</A> and <a href="http://atorre.s">test2</A>
if (hasHTML) {
matcher.reset();
popupParams.text = matcher.replaceAll("$2 ($1)");
if (sDetails == null) {
sDetails = popupParams.text;
} else {
sDetails = popupParams.text + "\n---------\n" + sDetails;
}
}
linkLabel.setForeground(shell.getDisplay().getSystemColor(SWT.COLOR_BLACK));
linkLabel.setText(popupParams.text);
}
}
/**
* @param numAfter
*/
private void setButtonNextText(int numAfter) {
if (numAfter <= 0)
Messages.setLanguageText(btnNext, "popup.error.hide");
else
Messages.setLanguageText(btnNext, "popup.next", new String[] {
"" + numAfter
});
cShell.layout(true);
}
/**
* Show the popup with the specified parameters.
*
* @param display Display to show on
* @param item popup to display. Must already exist in historyList
* @param bSlide Whether to slide in or show immediately
*/
private void showPopup(final Display display, final PopupParams item,
final boolean bSlide) {
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
new MessageSlideShell(display, item, bSlide);
}
});
}
/**
* Adds mousetracklistener to composite and all it's children
*
* @param parent Composite to start at
* @param listener Listener to add
*/
private void addMouseTrackListener(Composite parent,
MouseTrackListener listener) {
if (parent == null || listener == null || parent.isDisposed())
return;
parent.addMouseTrackListener(listener);
Control[] children = parent.getChildren();
for (int i = 0; i < children.length; i++) {
Control control = children[i];
if (control instanceof Composite)
addMouseTrackListener((Composite) control, listener);
else
control.addMouseTrackListener(listener);
}
}
private void addPaintListener(Composite parent, PaintListener listener,
Color colorBG, boolean childrenOnly) {
if (parent == null || listener == null || parent.isDisposed())
return;
if (!childrenOnly) {
parent.addPaintListener(listener);
parent.setBackground(colorBG);
}
Control[] children = parent.getChildren();
for (int i = 0; i < children.length; i++) {
Control control = children[i];
control.addPaintListener(listener);
control.setBackground(colorBG);
if (control instanceof Composite)
addPaintListener((Composite) control, listener, colorBG, true);
}
}
/**
* removes mousetracklistener from composite and all it's children
*
* @param parent Composite to start at
* @param listener Listener to remove
*/
private void removeMouseTrackListener(Composite parent,
MouseTrackListener listener) {
if (parent == null || listener == null || parent.isDisposed())
return;
Control[] children = parent.getChildren();
for (int i = 0; i < children.length; i++) {
Control control = children[i];
control.removeMouseTrackListener(listener);
if (control instanceof Composite)
removeMouseTrackListener((Composite) control, listener);
}
}
/**
* Start the slid in, wait specified time while notifying user of impending
* auto-close, then slide out. Run on separate thread, so this method
* returns immediately
*
* @param endBounds end location and size wanted
* @param idx Index in historyList of popup (Used to calculate # prev, next)
* @param bSlide Whether to slide in, or show immediately
*/
private void runPopup(final Rectangle endBounds, final int idx,
final boolean bSlide) {
if (shell == null || shell.isDisposed())
return;
final Display display = shell.getDisplay();
if (DEBUG)
System.out.println("runPopup " + idx + ((bSlide) ? " Slide" : " Instant"));
AEThread thread = new AEThread("Slidey", true) {
private final static int PAUSE = 500;
public void runSupport() {
if (shell == null || shell.isDisposed())
return;
if (bSlide) {
new SlideShell(shell, SWT.UP, endBounds).run();
} else {
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
shell.setBounds(endBounds);
shell.open();
}
});
}
int delayLeft = COConfigurationManager.getIntParameter("Message Popup Autoclose in Seconds") * 1000;
final boolean autohide = (delayLeft != 0);
long lastDelaySecs = 0;
int lastNumPopups = -1;
while ((!autohide || bDelayPaused || delayLeft > 0)
&& !shell.isDisposed()) {
int delayPausedOfs = (bDelayPaused ? 1 : 0);
final long delaySecs = Math.round(delayLeft / 1000.0)
+ delayPausedOfs;
final int numPopups = historyList.size();
if (lastDelaySecs != delaySecs || lastNumPopups != numPopups) {
lastDelaySecs = delaySecs;
lastNumPopups = numPopups;
shell.getDisplay().asyncExec(new AERunnable() {
public void runSupport() {
String sText = "";
if (lblCloseIn == null || lblCloseIn.isDisposed())
return;
lblCloseIn.setRedraw(false);
if (!bDelayPaused && autohide)
sText += MessageText.getString("popup.closing.in",
new String[] {
String.valueOf(delaySecs)
});
int numPopupsAfterUs = numPopups - idx - 1;
boolean bHasMany = numPopupsAfterUs > 0;
if (bHasMany) {
sText += "\n";
sText += MessageText.getString("popup.more.waiting",
new String[] {
String.valueOf(numPopupsAfterUs)
});
}
lblCloseIn.setText(sText);
if (btnHideAll.getVisible() != bHasMany) {
cShell.setRedraw(false);
btnHideAll.setVisible(bHasMany);
lblCloseIn.getParent().layout(true);
cShell.setRedraw(true);
}
setButtonNextText(numPopupsAfterUs);
// Need to redraw to cause a paint
lblCloseIn.setRedraw(true);
}
});
}
if (!bDelayPaused)
delayLeft -= PAUSE;
try {
Thread.sleep(PAUSE);
} catch (InterruptedException e) {
delayLeft = 0;
}
}
if (this.isInterrupted()) {
// App closedown likely, boot out ASAP
disposeShell(shell);
return;
}
// Assume that if the shell was disposed during loop, it's on purpose
// and that it has handled whether to show the next popup or not
if (shell != null && !shell.isDisposed()) {
if (idx + 1 < historyList.size()) {
showPopup(display, (PopupParams) historyList.get(idx + 1), true);
}
// slide out current popup
if (bSlide)
new SlideShell(shell, SWT.RIGHT).run();
disposeShell(shell);
}
}
};
thread.start();
}
private void disposeShell(final Shell shell) {
if (shell == null || shell.isDisposed())
return;
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
shell.dispose();
}
});
}
/**
* Waits until all slideys are closed before returning to caller.
*/
public static void waitUntilClosed() {
if (currentPopupIndex < 0)
return;
Display display = Display.getCurrent();
while (currentPopupIndex >= 0) {
if (!display.readAndDispatch())
display.sleep();
}
}
public static String stripOutHyperlinks(String message) {
return Pattern.compile(REGEX_URLHTML, Pattern.CASE_INSENSITIVE).matcher(
message).replaceAll("$2");
}
/**
* XXX This could/should be its own class
*/
private class SlideShell
{
private int STEP = 8;
private int PAUSE = 30;
private Shell shell;
private Rectangle shellBounds = null;
private Rectangle endBounds;
private final int direction;
private final boolean slideIn;
/**
* Slide In
*
* @param shell
* @param direction
* @param endBounds
*/
public SlideShell(final Shell shell, int direction,
final Rectangle endBounds) {
this.shell = shell;
this.endBounds = endBounds;
this.slideIn = true;
this.direction = direction;
if (shell == null || shell.isDisposed())
return;
Display display = shell.getDisplay();
display.syncExec(new Runnable() {
public void run() {
if (shell == null || shell.isDisposed())
return;
switch (SlideShell.this.direction) {
case SWT.UP:
default:
shell.setLocation(endBounds.x, endBounds.y);
Rectangle displayBounds = null;
try {
boolean ok = false;
Monitor[] monitors = shell.getDisplay().getMonitors();
for (int i = 0; i < monitors.length; i++) {
Monitor monitor = monitors[i];
displayBounds = monitor.getBounds();
if (displayBounds.contains(endBounds.x, endBounds.y)) {
ok = true;
break;
}
}
if (!ok) {
displayBounds = shell.getMonitor().getBounds();
}
} catch (Throwable t) {
displayBounds = shell.getDisplay().getBounds();
}
shellBounds = new Rectangle(endBounds.x, displayBounds.y
+ displayBounds.height, endBounds.width, 0);
break;
}
shell.setBounds(shellBounds);
shell.setVisible(true);
if (DEBUG)
System.out.println("Slide In: " + shell.getText());
}
});
}
/**
* Slide Out
*
* @param shell
* @param direction
*/
public SlideShell(final Shell shell, int direction) {
this.shell = shell;
this.slideIn = false;
this.direction = direction;
if (DEBUG && canContinue())
shell.getDisplay().syncExec(new Runnable() {
public void run() {
System.out.println("Slide Out: " + shell.getText());
}
});
}
private boolean canContinue() {
if (shell == null || shell.isDisposed())
return false;
if (shellBounds == null)
return true;
//System.out.println((slideIn ? "In" : "Out") + ";" + direction + ";S:" + shellBounds + ";" + endBounds);
if (slideIn) {
if (direction == SWT.UP) {
return shellBounds.y > endBounds.y;
}
// TODO: Other directions
} else {
if (direction == SWT.RIGHT) {
// stop early, because some OSes have trim, and won't allow the window
// to go smaller than it.
return shellBounds.width > 10;
}
}
return false;
}
public void run() {
while (canContinue()) {
long lStartedAt = System.currentTimeMillis();
shell.getDisplay().syncExec(new AERunnable() {
public void runSupport() {
if (shell == null || shell.isDisposed()) {
return;
}
if (shellBounds == null) {
shellBounds = shell.getBounds();
}
int delta;
if (slideIn) {
switch (direction) {
case SWT.UP:
delta = Math.min(endBounds.height - shellBounds.height, STEP);
shellBounds.height += delta;
delta = Math.min(shellBounds.y - endBounds.y, STEP);
shellBounds.y -= delta;
break;
default:
break;
}
} else {
switch (direction) {
case SWT.RIGHT:
delta = Math.min(shellBounds.width, STEP);
shellBounds.width -= delta;
shellBounds.x += delta;
if (shellBounds.width == 0) {
shell.dispose();
return;
}
break;
default:
break;
}
}
shell.setBounds(shellBounds);
shell.update();
}
});
try {
long lDrawTime = System.currentTimeMillis() - lStartedAt;
long lSleepTime = PAUSE - lDrawTime;
if (lSleepTime < 15) {
double d = (lDrawTime + 15.0) / PAUSE;
PAUSE *= d;
STEP *= d;
lSleepTime = 15;
}
Thread.sleep(lSleepTime);
} catch (Exception e) {
}
}
}
}
private static class PopupParams
{
int iconID;
String title;
String text;
String details;
long addedOn;
Object[] relatedTo;
/**
* @param iconID
* @param title
* @param text
* @param details
*/
public PopupParams(int iconID, String title, String text, String details) {
this.iconID = iconID;
this.title = title;
this.text = text;
this.details = details;
addedOn = System.currentTimeMillis();
}
/**
* @param iconID2
* @param title2
* @param text2
* @param details2
* @param relatedTo
*/
public PopupParams(int iconID, String title, String text, String details,
Object[] relatedTo) {
this(iconID, title, text, details);
this.relatedTo = relatedTo;
}
}
/**
* Test
*
* @param args
*/
public static void main(String[] args) {
final Display display = Display.getDefault();
Shell shell = new Shell(display, SWT.DIALOG_TRIM);
shell.setLayout(new FillLayout());
Button btn = new Button(shell, SWT.PUSH);
btn.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
test(display);
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
public static void test(Display display) {
ImageRepository.loadImages(display);
String title = "This is the title that never ends, never ends!";
String text = "This is a very long message with lots of information and "
+ "stuff you really should read. Are you still reading? Good, because "
+ "reading <a href=\"http://moo.com\">stimulates</a> the mind and grows "
+ "hair on your chest.\n\n Unless you are a girl, then it makes you want "
+ "to read more. It's an endless cycle of reading that will never "
+ "end. Cursed is the long text that is in this test and may it fill"
+ "every last line of the shell until there is no more.";
// delay before running, to give eclipse time to finish up it's work
// Otherwise, Mr Slidey is jumpy
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// MessagePopupShell shell = new MessagePopupShell(display,
// MessagePopupShell.ICON_INFO, "Title", text, "Details");
new MessageSlideShell(display, SWT.ICON_INFORMATION,
"Simple. . . . . . . . . . . . . . . . . . .", "Simple", (String) null);
new MessageSlideShell(display, SWT.ICON_INFORMATION, title + "1", text,
"Details: " + text);
new MessageSlideShell(display, SWT.ICON_INFORMATION, "ShortTitle2",
"ShortText", "Details");
MessageSlideShell.waitUntilClosed();
new MessageSlideShell(display, SWT.ICON_INFORMATION, "ShortTitle3",
"ShortText", (String) null);
for (int x = 0; x < 10; x++)
text += "\n\n\n\n\n\n\n\nWow";
new MessageSlideShell(display, SWT.ICON_INFORMATION, title + "4", text,
"Details");
new MessageSlideShell(display, SWT.ICON_ERROR, title + "5", text,
(String) null);
MessageSlideShell.waitUntilClosed();
}
/**
* @return the imgPopup
*/
public Image getImgPopup() {
return imgPopup;
}
/**
* @param imgPopup the imgPopup to set
*/
public void setImgPopup(Image imgPopup) {
this.imgPopup = imgPopup;
}
}