/* * 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 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.Messages; import org.gudy.azureus2.ui.swt.Utils; import org.gudy.azureus2.ui.swt.mainwindow.ClipboardCopy; import org.gudy.azureus2.ui.swt.shells.GCStringPrinter.URLInfo; import com.aelitis.azureus.ui.swt.*; import com.aelitis.azureus.ui.swt.utils.ColorCache; /** * * +=====================================+ * | +----+ | * | |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 final boolean DEBUG = false; /** 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<PopupParams> historyList = new ArrayList<PopupParams>(); /** 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<Object> disposeList = new ArrayList<Object>(); /** Text to put into details popup */ private String sDetails; /** Position this popup is in the history list */ private int idxHistory; protected Color colorURL; private Color colorFG; private int shellWidth; /** 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, int timeoutSecs ) { this(display, iconID, MessageText.getString(keyPrefix + ".title"), MessageText.getString(keyPrefix + ".text", textParams), details, timeoutSecs); } /** * * @param display * @param iconID * @param keyPrefix * @param details * @param textParams * @param relatedObjects * @param timeoutSecs = -1 -> use default timeout, 0 -> no timeout, other -> timeout in secs */ public MessageSlideShell(Display display, int iconID, String keyPrefix, String details, String[] textParams, Object[] relatedObjects, int timeoutSecs ) { this(display, iconID, MessageText.getString(keyPrefix + ".title"), MessageText.getString(keyPrefix + ".text", textParams), details, relatedObjects, timeoutSecs); } public MessageSlideShell(Display display, int iconID, String title, String text, String details, int timeoutSecs) { this(display, iconID, title, text, details, null, timeoutSecs); } /** * 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 timeoutSecs = -1 -> use default timeout, 0 -> no timeout, other -> timeout in secs */ public MessageSlideShell(Display display, int iconID, String title, String text, String details, Object[] relatedObjects, int timeoutSecs ) { try { monitor.enter(); PopupParams popupParams = new PopupParams(iconID, title, text, details, relatedObjects, timeoutSecs ); addToHistory(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(); } } /** * @param popupParams * * @since 4.1.0.5 */ private static void addToHistory(PopupParams popupParams) { monitor.enter(); try { historyList.add(popupParams); } 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, 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, int timeoutSecs ) { try { monitor.enter(); addToHistory(new PopupParams(iconID, title, text, details, relatedTo, timeoutSecs)); 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 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 Image imgIcon = popupParams.iconID <= 0 ? null : display.getSystemImage(popupParams.iconID); /* * If forceTimer is true then we always show the counter for auto-closing the shell; * otherwise proceed to the more fine-grained logic */ // if there's a link, or the info is non-information, // disable timer and mouse watching bDelayPaused = 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); } try { shell.setBackgroundMode(SWT.INHERIT_DEFAULT); } catch (NoSuchMethodError e) { // Ignore } catch (NoSuchFieldError e2) { // ignore } Utils.setShellIcon(shell); if (popupParams.title != null) { shell.setText(popupParams.title); } shellWidth = SHELL_DEF_WIDTH; 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); } } if (colorFG == null) { colorFG = display.getSystemColor(SWT.COLOR_BLACK); } 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()); if (popupParams.title != null) { 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(colorFG); 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(colorFG); 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, popupParams); lblCloseIn = new Label(cShell, SWT.TRAIL); lblCloseIn.setForeground(colorFG); // 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 = 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, 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; } } 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); } } int timeoutSecs; if ( popupParams.timeoutSecs < 0 ){ timeoutSecs = COConfigurationManager.getIntParameter("Message Popup Autoclose in Seconds"); }else{ timeoutSecs = popupParams.timeoutSecs; } runPopup(endBounds, idxHistory, bSlide, timeoutSecs ); } /** * @param shell2 * @param b * * @since 3.0.0.9 */ private void createLinkLabel(Composite shell, final PopupParams popupParams) { final Canvas canvas = new Canvas(shell, SWT.None) { public Point computeSize(int wHint, int hHint, boolean changed) { Rectangle area = new Rectangle(0, 0, shellWidth, 5000); GC gc = new GC(this); GCStringPrinter sp = new GCStringPrinter(gc, popupParams.text, area, true, false, SWT.WRAP | SWT.TOP); sp.calculateMetrics(); gc.dispose(); Point size = sp.getCalculatedSize(); return size; } }; Listener l = new Listener() { GCStringPrinter sp; public void handleEvent(Event e) { if (e.type == SWT.Paint) { Rectangle area = canvas.getClientArea(); sp = new GCStringPrinter(e.gc, popupParams.text, area, true, false, SWT.WRAP | SWT.TOP); sp.setUrlColor(ColorCache.getColor(e.gc.getDevice(), "#0000ff")); if (colorURL != null) { sp.setUrlColor(colorURL); } if (colorFG != null) { e.gc.setForeground(colorFG); } sp.printString(); } else if (e.type == SWT.MouseMove) { if (sp != null) { URLInfo hitUrl = sp.getHitUrl(e.x, e.y); if (hitUrl != null) { canvas.setCursor(canvas.getDisplay().getSystemCursor( SWT.CURSOR_HAND)); canvas.setToolTipText(hitUrl.url); } else { canvas.setCursor(canvas.getDisplay().getSystemCursor( SWT.CURSOR_ARROW)); canvas.setToolTipText(null); } } } else if (e.type == SWT.MouseUp) { if (sp != null) { URLInfo hitUrl = sp.getHitUrl(e.x, e.y); if (hitUrl != null && !hitUrl.url.startsWith(":")) { Utils.launch(hitUrl.url); } } } } }; canvas.addListener(SWT.Paint, l); canvas.addListener(SWT.MouseMove, l); canvas.addListener(SWT.MouseUp, l); ClipboardCopy.addCopyToClipMenu(canvas, new ClipboardCopy.copyToClipProvider() { public String getText() { return (popupParams.title + "\n\n" + popupParams.text); } }); GridData gridData = new GridData(GridData.FILL_BOTH); gridData.horizontalSpan = 3; canvas.setLayoutData(gridData); } /** * @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); } } /** * 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, final int timeoutSecs ) { 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 ShellSlider(shell, SWT.UP, endBounds).run(); } else { Utils.execSWTThread(new AERunnable() { public void runSupport() { shell.setBounds(endBounds); shell.open(); } }); } int delayLeft = timeoutSecs * 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, historyList.get(idx + 1), true); } // slide out current popup if (bSlide) new ShellSlider(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 class PopupParams { public int iconID; public String title; public String text; public String details; public long addedOn; public Object[] relatedTo; public int timeoutSecs; /** * @param iconID * @param title * @param text * @param details */ public PopupParams(int iconID, String title, String text, String details, int timeoutSecs ) { this.iconID = iconID; this.title = title; this.text = text; this.details = details; this.timeoutSecs = timeoutSecs; 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, int timeoutSecs ) { this(iconID, title, text, details, timeoutSecs ); 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) { 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, -1); new MessageSlideShell(display, SWT.ICON_INFORMATION, title + "1", text, "Details: " + text, -1); new MessageSlideShell(display, SWT.ICON_INFORMATION, "ShortTitle2", "ShortText", "Details", -1); MessageSlideShell.waitUntilClosed(); new MessageSlideShell(display, SWT.ICON_INFORMATION, "ShortTitle3", "ShortText", (String) null, -1); 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", -1); new MessageSlideShell(display, SWT.ICON_ERROR, title + "5", text, (String) null, -1); MessageSlideShell.waitUntilClosed(); } public Color getUrlColor() { return colorURL; } public void setUrlColor(Color urlColor) { this.colorURL = urlColor; } public Color getColorFG() { return colorFG; } public void setColorFG(Color colorFG) { this.colorFG = colorFG; } }