/* ********************************************************************** ** ** Copyright notice ** ** ** ** (c) 2005-2009 RSSOwl Development Team ** ** http://www.rssowl.org/ ** ** ** ** All rights reserved ** ** ** ** This program and the accompanying materials are made available under ** ** the terms of the Eclipse Public License v1.0 which accompanies this ** ** distribution, and is available at: ** ** http://www.rssowl.org/legal/epl-v10.html ** ** ** ** A copy is found in the file epl-v10.html and important notices to the ** ** license from the team is found in the textfile LICENSE.txt distributed ** ** in this package. ** ** ** ** This copyright notice MUST APPEAR in all copies of the file! ** ** ** ** Contributors: ** ** RSSOwl Development Team - initial API and implementation ** ** ** ** ********************************************************************** */ package org.rssowl.ui.internal.notifier; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.PopupDialog; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Region; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.UIJob; import org.rssowl.core.Owl; import org.rssowl.core.internal.persist.pref.DefaultPreferences; import org.rssowl.core.persist.pref.IPreferenceScope; import org.rssowl.core.util.StringUtils; import org.rssowl.ui.internal.Controller; import org.rssowl.ui.internal.OwlUI; import org.rssowl.ui.internal.notifier.NotificationService.Mode; import org.rssowl.ui.internal.util.CCLabel; import org.rssowl.ui.internal.util.LayoutUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Todo: * <ul> * <li>SearchNotificationItems are not aggregated if the max. number of items is * already showing</li> * </ul> * * @author bpasero */ public class NotificationPopup extends PopupDialog { /* Height of a CLabel with 16px Icon inside */ private static final int CLABEL_HEIGHT = 22; /* Max. Number of Items being displayed in the Popup */ private static final int MAX_ITEMS = 30; /* Default Width of the Popup */ private static final int DEFAULT_WIDTH = 420; /* Milliseconds before incrementing the alpha for fade in / fade out */ private static final int FADE_DELAY = 40; /* Alpha increment for fade in */ private static final int FADE_IN_INCREMENT = 20; /* A field that is set to true in case fading is not supported on the OS */ private static boolean fgFadeSupported = true; /* System Property to disable Transparency for the Notifier */ private static final String DISABLE_TRANSPARENCY = "disableNotifierTransparency"; //$NON-NLS-1$ private static final boolean FADE_ENABLED = System.getProperty(DISABLE_TRANSPARENCY) == null; private Shell fShell; private final List<NotificationItem> fDisplayedItems = new ArrayList<NotificationItem>(); private final ResourceManager fResources; private Image fCloseImageNormal; private Image fCloseImageHot; private Image fCloseImagePressed; private CLabel fTitleCircleLabel; private CLabel fFooterCircleLabel; private Composite fInnerContentCircle; private Composite fOuterContentCircle; private final Font fNormalTextFont; private final Font fBoldTextFont; private UIJob fAutoCloser; private MouseTrackListener fMouseTrackListner; private final IPreferenceScope fGlobalScope; private final int fVisibleItemsCount; private int fItemLimit; private NotifierColors fNotifierColors; private Region fLastUsedRegion; private Image fMarkReadIcon; private Image fMarkReadDisabledIcon; private Image fItemStickyIcon; private Image fItemNonStickyIcon; private Image fItemNonStickyDisabledIcon; private Color fStickyBgColor; private Image fPrevImageNormal; private Image fPrevImagePressed; private Image fPrevImageDisabled; private Image fNextImageNormal; private Image fNextImagePressed; private Image fNextImageDisabled; private Image fTitleBgImage; private Image fFooterBgImage; private CLabel fNextButton; private CLabel fPrevButton; private int fDisplayOffset; private boolean fMouseOverNotifier; private FadeJob fFadeJob; private Collection<NotificationItem> fInitialItems; private final Mode fMode; private class FadeJob extends UIJob { FadeJob() { super(""); //$NON-NLS-1$ setSystem(true); } private boolean proceed(IProgressMonitor monitor) { Shell shell = getShell(); if (monitor.isCanceled() || shell == null || shell.isDisposed() || shell.getDisplay().isDisposed() || Controller.getDefault().isShuttingDown()) return false; return true; } @Override public IStatus runInUIThread(final IProgressMonitor monitor) { if (proceed(monitor)) { final int alpha = getShell().getAlpha(); if (shouldFadeIn() && alpha < 255) { if (proceed(monitor)) { getShell().getDisplay().syncExec(new Runnable() { public void run() { if (proceed(monitor)) { int newAlpha = Math.min(alpha + FADE_IN_INCREMENT, 255); getShell().setAlpha(newAlpha); if (newAlpha != getShell().getAlpha()) fgFadeSupported = false; } } }); schedule(FADE_DELAY); } } } return Status.OK_STATUS; } }; NotificationPopup(int visibleItemCount, Mode mode) { super(new Shell(PlatformUI.getWorkbench().getDisplay()), SWT.NO_TRIM | SWT.ON_TOP, mode != Mode.INCOMING_AUTOMATIC, false, false, false, false, null, null); fMode = mode; fResources = new LocalResourceManager(JFaceResources.getResources()); fBoldTextFont = OwlUI.getThemeFont(OwlUI.NOTIFICATION_POPUP_FONT_ID, SWT.BOLD); fNormalTextFont = OwlUI.getThemeFont(OwlUI.NOTIFICATION_POPUP_FONT_ID, SWT.NORMAL); fGlobalScope = Owl.getPreferenceService().getGlobalScope(); fItemLimit = fGlobalScope.getInteger(DefaultPreferences.LIMIT_NOTIFICATION_SIZE); if (fItemLimit <= 0) fItemLimit = MAX_ITEMS; fVisibleItemsCount = (visibleItemCount > fItemLimit) ? fItemLimit : visibleItemCount; createAutoCloser(); createMouseTrackListener(); initResources(); } /* * @see org.eclipse.jface.window.Window#getShellStyle() */ @Override protected int getShellStyle() { return SWT.NO_TRIM | SWT.ON_TOP; } /* * @see org.eclipse.jface.window.Window#create() */ @Override public void create() { super.create(); /* Show initial set of items */ makeVisible(fInitialItems); /* Make shell invisible initially if fading in */ if (shouldFadeIn() && fGlobalScope.getBoolean(DefaultPreferences.FADE_NOTIFIER)) getShell().setAlpha(0); } /** * @param items the initial items to show inside the window. * @return the status code from opening the window. */ public int open(Collection<NotificationItem> items) { Assert.isLegal(items != null && !items.isEmpty()); fInitialItems = items; int res = super.open(); /* Make shell fade in */ if (shouldFadeIn() && fGlobalScope.getBoolean(DefaultPreferences.FADE_NOTIFIER)) fadeIn(); return res; } private void fadeIn() { if (fFadeJob != null) fFadeJob.cancel(); fFadeJob = new FadeJob(); fFadeJob.schedule(); } private void addRegion(Shell shell) { Region region = new Region(); Point s = shell.getSize(); /* Add entire Shell */ region.add(0, 0, s.x, s.y); /* Subtract Top-Left Corner */ region.subtract(0, 0, 5, 1); region.subtract(0, 1, 3, 1); region.subtract(0, 2, 2, 1); region.subtract(0, 3, 1, 1); region.subtract(0, 4, 1, 1); /* Subtract Top-Right Corner */ region.subtract(s.x - 5, 0, 5, 1); region.subtract(s.x - 3, 1, 3, 1); region.subtract(s.x - 2, 2, 2, 1); region.subtract(s.x - 1, 3, 1, 1); region.subtract(s.x - 1, 4, 1, 1); /* Subtract Bottom-Left Corner */ region.subtract(0, s.y, 5, 1); region.subtract(0, s.y - 1, 3, 1); region.subtract(0, s.y - 2, 2, 1); region.subtract(0, s.y - 3, 1, 1); region.subtract(0, s.y - 4, 1, 1); /* Subtract Bottom-Right Corner */ region.subtract(s.x - 5, 0, 5, 1); region.subtract(s.x - 3, 1, 3, 1); region.subtract(s.x - 2, 2, 2, 1); region.subtract(s.x - 1, 3, 1, 1); region.subtract(s.x - 1, 4, 1, 1); /* Dispose old first */ if (shell.getRegion() != null) shell.getRegion().dispose(); /* Apply Region */ shell.setRegion(region); /* Remember to dispose later */ fLastUsedRegion = region; } private void createAutoCloser() { fAutoCloser = new UIJob(PlatformUI.getWorkbench().getDisplay(), "") { //$NON-NLS-1$ @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (!fMouseOverNotifier && getShell() != null && !getShell().isDisposed() && !monitor.isCanceled()) doClose(); return Status.OK_STATUS; } }; fAutoCloser.setSystem(true); } /* Listener to control Auto-Close Job */ private void createMouseTrackListener() { fMouseTrackListner = new MouseTrackAdapter() { @Override public void mouseEnter(MouseEvent e) { fMouseOverNotifier = true; if (!fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP)) fAutoCloser.cancel(); if (shouldFadeIn() && fGlobalScope.getBoolean(DefaultPreferences.FADE_NOTIFIER) && getShell().getAlpha() < 255) { if (fFadeJob != null) fFadeJob.cancel(); getShell().setAlpha(255); } } @Override public void mouseExit(MouseEvent e) { fMouseOverNotifier = false; if (!fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP)) fAutoCloser.schedule(fGlobalScope.getInteger(DefaultPreferences.AUTOCLOSE_NOTIFICATION_VALUE) * 1000); } }; } void makeVisible(Collection<NotificationItem> items) { /* Cancel Auto Closer and reschedule */ if (!fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP)) { fAutoCloser.cancel(); fAutoCloser.schedule(fGlobalScope.getInteger(DefaultPreferences.AUTOCLOSE_NOTIFICATION_VALUE) * 1000); } /* Add / Update Notification Items to display */ for (NotificationItem item : items) { int indexOfItem = fDisplayedItems.indexOf(item); /* Replace existing Item */ if (indexOfItem >= 0) { NotificationItem existingItem = fDisplayedItems.get(indexOfItem); if (existingItem.getColor() != null && item.getColor() == null) item.setColor(existingItem.getColor()); fDisplayedItems.set(indexOfItem, item); } /* Add new Item to the end */ else fDisplayedItems.add(item); } /* Update Title Label */ updateTitleLabel(); /* Update Contents */ updateContents(fDisplayOffset); } private void updateTitleLabel() { int totalPages = (fDisplayedItems.size() / fItemLimit) + ((fDisplayedItems.size() % fItemLimit != 0) ? 1 : 0); int currentPage = (fDisplayOffset / fItemLimit) + 1; /* Count News Notification Items and Search Notification Items */ int newsCount = 0; int searchCount = 0; for (NotificationItem item : fDisplayedItems) { if (item instanceof NewsNotificationItem) newsCount++; else searchCount++; } String titlePart; if (fMode == Mode.RECENT) titlePart = Messages.NotificationPopup_RECENT_NEWS; else if (fMode == Mode.INCOMING_MANUAL) titlePart = Messages.NotificationPopup_INCOMING_NEWS; else if (newsCount == 0 && searchCount > 0) titlePart = (searchCount == 1) ? NLS.bind(Messages.NotificationPopup_N_SEARCH_MATCH, searchCount) : NLS.bind(Messages.NotificationPopup_N_SEARCH_MATCHES, searchCount); else titlePart = NLS.bind(Messages.NotificationPopup_N_INCOMING, newsCount); String footerPart = ""; //$NON-NLS-1$ /* More than one page */ if (totalPages > 1) footerPart = NLS.bind(Messages.NotificationPopup_PAGE_N_OF_M, currentPage, totalPages); /* Apply Text */ fTitleCircleLabel.setText(titlePart); fFooterCircleLabel.setText(footerPart); } private void updateContents(int offset) { /* Dispose old Items first */ Control[] children = fInnerContentCircle.getChildren(); for (Control child : children) { child.dispose(); } /* Show Items */ for (int i = offset; i < offset + fItemLimit && i < fDisplayedItems.size(); i++) renderItem(fDisplayedItems.get(i)); /* Layout */ fOuterContentCircle.layout(true, true); /* Update Shell Bounds */ Point oldSize = fShell.getSize(); int newHeight = fShell.computeSize(DEFAULT_WIDTH, SWT.DEFAULT).y; newHeight = Math.max(oldSize.y, newHeight); Point newSize = new Point(oldSize.x, newHeight); Point newLocation = getInitialLocation(newSize); fShell.setBounds(newLocation.x, newLocation.y, newSize.x, newSize.y); /* Add Region to Shell */ addRegion(fShell); /* Update Nav Buttons */ updateNavButtons(); } private void updateItemColor(NotificationItem item, CCLabel itemLabel) { if (!OwlUI.isHighContrast()) { if (item.getColor() == null) itemLabel.setForeground(itemLabel.getDisplay().getSystemColor(SWT.COLOR_BLACK)); else itemLabel.setForeground(OwlUI.getColor(fResources, item.getColor())); } } private void renderItem(final NotificationItem item) { /* Image Label with Tooltip */ final CLabel imageLabel = new CLabel(fInnerContentCircle, SWT.NONE); imageLabel.setBackground(fInnerContentCircle.getBackground()); imageLabel.setCursor(imageLabel.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); imageLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); imageLabel.addMouseTrackListener(fMouseTrackListner); imageLabel.setImage(OwlUI.getImage(fResources, item.getImage())); imageLabel.setToolTipText(item.getOrigin()); /* Use a CCLabel per Item */ final CCLabel itemLabel = new CCLabel(fInnerContentCircle, SWT.NONE); itemLabel.setBackground(fInnerContentCircle.getBackground()); itemLabel.setCursor(itemLabel.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); itemLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); itemLabel.setText(item.getText()); itemLabel.setFont(item.isRead() ? fNormalTextFont : fBoldTextFont); updateItemColor(item, itemLabel); itemLabel.addMouseTrackListener(fMouseTrackListner); /* Paint text blue on mouse-enter */ MouseTrackAdapter mouseTrackListener = new MouseTrackAdapter() { @Override public void mouseEnter(MouseEvent e) { if (!OwlUI.isHighContrast()) itemLabel.setForeground(itemLabel.getDisplay().getSystemColor(SWT.COLOR_BLUE)); } @Override public void mouseExit(MouseEvent e) { updateItemColor(item, itemLabel); } }; itemLabel.addMouseTrackListener(mouseTrackListener); imageLabel.addMouseTrackListener(mouseTrackListener); /* Clicked on item to open it */ MouseAdapter mouseListener = new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { /* Close Popup if required after opening */ if (fGlobalScope.getBoolean(DefaultPreferences.CLOSE_NOTIFIER_ON_OPEN)) { item.open(e); doClose(); } /* Indicate the item is marked as read now and open */ else { item.setRead(true); itemLabel.setFont(fNormalTextFont); item.open(e); } } }; itemLabel.addMouseListener(mouseListener); imageLabel.addMouseListener(mouseListener); /* Offer Label to mark item read */ CLabel markReadLabel = new CLabel(fInnerContentCircle, SWT.NONE); markReadLabel.setImage(item.supportsMarkRead() ? fMarkReadIcon : fMarkReadDisabledIcon); markReadLabel.setBackground(fInnerContentCircle.getBackground()); markReadLabel.addMouseTrackListener(fMouseTrackListner); markReadLabel.setEnabled(item.supportsMarkRead()); markReadLabel.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); markReadLabel.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { boolean newStateRead = !item.isRead(); /* Update Font */ itemLabel.setFont(newStateRead ? fNormalTextFont : fBoldTextFont); /* Apply state */ item.setRead(newStateRead); } }); /* Offer Label to mark item sticky */ final CLabel markStickyLabel = new CLabel(fInnerContentCircle, SWT.NONE); markStickyLabel.setImage(item.supportsSticky() ? fItemNonStickyIcon : fItemNonStickyDisabledIcon); markStickyLabel.setBackground(fInnerContentCircle.getBackground()); markStickyLabel.setEnabled(item.supportsSticky()); markStickyLabel.addMouseTrackListener(fMouseTrackListner); markStickyLabel.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); markStickyLabel.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { boolean newStateSticky = !item.isSticky(); /* Update Background Color */ if (!OwlUI.isHighContrast()) itemLabel.setRoundRectangleBackground(newStateSticky ? fStickyBgColor : null); /* Update Image */ markStickyLabel.setImage(newStateSticky ? fItemStickyIcon : fItemNonStickyIcon); /* Apply state */ item.setSticky(newStateSticky); } }); /* Show Sticky if required */ if (item.supportsSticky() && item.isSticky()) { if (!OwlUI.isHighContrast()) itemLabel.setRoundRectangleBackground(fStickyBgColor); markStickyLabel.setImage(fItemStickyIcon); } /* Show excerpt of content if set */ if (fGlobalScope.getBoolean(DefaultPreferences.SHOW_EXCERPT_IN_NOTIFIER)) { String description = item.getDescription(); if (!StringUtils.isSet(description)) description = Messages.NotificationPopup_NO_CONTENT; final Composite descriptionContainer = new Composite(fInnerContentCircle, SWT.NONE); descriptionContainer.setLayout(LayoutUtils.createGridLayout(1)); ((GridLayout) descriptionContainer.getLayout()).marginBottom = 5; descriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 4, 1)); descriptionContainer.addMouseTrackListener(fMouseTrackListner); descriptionContainer.setBackground(fInnerContentCircle.getBackground()); descriptionContainer.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { GC gc = e.gc; Rectangle clArea = descriptionContainer.getClientArea(); gc.setForeground(fNotifierColors.getBorder()); gc.setBackground(fNotifierColors.getGradientEnd()); gc.fillGradientRectangle(4, 1, clArea.width, 1, false); } }); Label descriptionText = new Label(descriptionContainer, SWT.WRAP); descriptionText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); descriptionText.setFont(fNormalTextFont); descriptionText.setBackground(fInnerContentCircle.getBackground()); descriptionText.setText(description); descriptionText.addMouseTrackListener(fMouseTrackListner); } } private void initResources() { /* Colors */ fNotifierColors = new NotifierColors(getParentShell().getDisplay(), fResources); fStickyBgColor = OwlUI.getThemeColor(OwlUI.STICKY_BG_COLOR_ID, fResources, new RGB(255, 255, 180)); /* Icons */ fCloseImageNormal = OwlUI.getImage(fResources, "icons/etool16/close_normal.png"); //$NON-NLS-1$ fCloseImageHot = OwlUI.getImage(fResources, "icons/etool16/close_hot.png"); //$NON-NLS-1$ fCloseImagePressed = OwlUI.getImage(fResources, "icons/etool16/close_pressed.png"); //$NON-NLS-1$ fMarkReadIcon = OwlUI.getImage(fResources, "icons/elcl16/mark_read_light16.gif"); //$NON-NLS-1$ fMarkReadDisabledIcon = OwlUI.getImage(fResources, "icons/dlcl16/mark_read.gif"); //$NON-NLS-1$ fItemStickyIcon = OwlUI.getImage(fResources, "icons/obj16/news_pinned_light16.gif"); //$NON-NLS-1$ fItemNonStickyIcon = OwlUI.getImage(fResources, "icons/obj16/news_pin_light16.gif"); //$NON-NLS-1$ fItemNonStickyDisabledIcon = OwlUI.getImage(fResources, "icons/obj16/news_pin_disabled.gif"); //$NON-NLS-1$ fPrevImageNormal = OwlUI.getImage(fResources, "icons/etool16/prev_normal.png"); //$NON-NLS-1$ fPrevImagePressed = OwlUI.getImage(fResources, "icons/etool16/prev_pressed.png"); //$NON-NLS-1$ fPrevImageDisabled = OwlUI.getImage(fResources, "icons/etool16/prev_disabled.png"); //$NON-NLS-1$ fNextImageNormal = OwlUI.getImage(fResources, "icons/etool16/next_normal.png"); //$NON-NLS-1$ fNextImagePressed = OwlUI.getImage(fResources, "icons/etool16/next_pressed.png"); //$NON-NLS-1$ fNextImageDisabled = OwlUI.getImage(fResources, "icons/etool16/next_disabled.png"); //$NON-NLS-1$ } /* * @see org.eclipse.jface.dialogs.PopupDialog#createContents(org.eclipse.swt.widgets.Composite) */ @Override protected Control createContents(Composite parent) { fShell = parent.getShell(); parent.setBackground(fNotifierColors.getBorder()); return createDialogArea(parent); } /* * @see org.eclipse.jface.dialogs.PopupDialog#createDialogArea(org.eclipse.swt.widgets.Composite) */ @Override protected Control createDialogArea(Composite parent) { ((GridLayout) parent.getLayout()).marginWidth = 1; ((GridLayout) parent.getLayout()).marginHeight = 1; /* Outer Compositing holding the controlls */ final Composite outerCircle = new Composite(parent, SWT.NO_FOCUS); outerCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); outerCircle.setLayout(LayoutUtils.createGridLayout(1, 0, 0, 0)); /* Title area containing label and close button */ final Composite titleCircle = new Composite(outerCircle, SWT.NO_FOCUS); titleCircle.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); titleCircle.setBackgroundMode(SWT.INHERIT_FORCE); titleCircle.setLayout(LayoutUtils.createGridLayout(2, 3, 0, 5, 3, false)); titleCircle.addMouseTrackListener(fMouseTrackListner); titleCircle.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle clArea = titleCircle.getClientArea(); Image oldBgImage = fTitleBgImage; fTitleBgImage = new Image(titleCircle.getDisplay(), clArea.width, clArea.height); GC gc = new GC(fTitleBgImage); /* Gradient */ drawGradient(gc, clArea); /* Fix Region Shape */ fixRegion(gc, clArea); gc.dispose(); titleCircle.setBackgroundImage(fTitleBgImage); if (oldBgImage != null) oldBgImage.dispose(); } private void drawGradient(GC gc, Rectangle clArea) { gc.setForeground(fNotifierColors.getGradientBegin()); gc.setBackground(fNotifierColors.getGradientEnd()); gc.fillGradientRectangle(clArea.x, clArea.y, clArea.width, clArea.height, true); } private void fixRegion(GC gc, Rectangle clArea) { gc.setForeground(fNotifierColors.getBorder()); /* Fill Top Left */ gc.drawPoint(2, 0); gc.drawPoint(3, 0); gc.drawPoint(1, 1); gc.drawPoint(0, 2); gc.drawPoint(0, 3); /* Fill Top Right */ gc.drawPoint(clArea.width - 4, 0); gc.drawPoint(clArea.width - 3, 0); gc.drawPoint(clArea.width - 2, 1); gc.drawPoint(clArea.width - 1, 2); gc.drawPoint(clArea.width - 1, 3); } }); /* Title Label displaying RSSOwl */ fTitleCircleLabel = new CLabel(titleCircle, SWT.NO_FOCUS); fTitleCircleLabel.setImage(OwlUI.getImage(fResources, "icons/product/24x24.png")); //$NON-NLS-1$ fTitleCircleLabel.setText("RSSOwl"); //$NON-NLS-1$ fTitleCircleLabel.setFont(fBoldTextFont); fTitleCircleLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); fTitleCircleLabel.addMouseTrackListener(fMouseTrackListner); fTitleCircleLabel.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); fTitleCircleLabel.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { doRestore(); } }); /* CLabel to display a cross to close the popup */ final CLabel closeButton = new CLabel(titleCircle, SWT.NO_FOCUS); closeButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); closeButton.setImage(fCloseImageNormal); closeButton.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); closeButton.addMouseTrackListener(fMouseTrackListner); closeButton.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { doClose(); } @Override public void mouseDown(MouseEvent e) { closeButton.setImage(fCloseImagePressed); } }); closeButton.addMouseTrackListener(new MouseTrackAdapter() { @Override public void mouseEnter(MouseEvent e) { closeButton.setImage(fCloseImageHot); } @Override public void mouseExit(MouseEvent e) { closeButton.setImage(fCloseImageNormal); } }); /* Outer composite to hold content controlls */ fOuterContentCircle = new Composite(outerCircle, SWT.NONE); fOuterContentCircle.setLayout(LayoutUtils.createGridLayout(1, 0, 0)); fOuterContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); fOuterContentCircle.setBackground(outerCircle.getBackground()); /* Middle composite to show a 1px black line around the content controlls */ Composite middleContentCircle = new Composite(fOuterContentCircle, SWT.NO_FOCUS); middleContentCircle.setLayout(LayoutUtils.createGridLayout(1, 0, 0)); ((GridLayout) middleContentCircle.getLayout()).marginTop = 1; ((GridLayout) middleContentCircle.getLayout()).marginBottom = 1; middleContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); middleContentCircle.setBackground(fNotifierColors.getBorder()); /* Inner composite containing the content controlls */ fInnerContentCircle = new Composite(middleContentCircle, SWT.NO_FOCUS); fInnerContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); fInnerContentCircle.setLayout(LayoutUtils.createGridLayout(4, 0, 5, 0, 0, false)); ((GridLayout) fInnerContentCircle.getLayout()).marginLeft = 5; ((GridLayout) fInnerContentCircle.getLayout()).marginRight = 2; fInnerContentCircle.addMouseTrackListener(fMouseTrackListner); fInnerContentCircle.setBackground(fShell.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); /* Footer Area containing navigational controls */ final Composite footerCircle = new Composite(outerCircle, SWT.NO_FOCUS); footerCircle.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); footerCircle.setBackgroundMode(SWT.INHERIT_FORCE); footerCircle.setLayout(LayoutUtils.createGridLayout(3, 3, 0, 5, 3, false)); footerCircle.addMouseTrackListener(fMouseTrackListner); footerCircle.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle clArea = footerCircle.getClientArea(); Image oldBgImage = fFooterBgImage; fFooterBgImage = new Image(footerCircle.getDisplay(), clArea.width, clArea.height); GC gc = new GC(fFooterBgImage); /* Gradient */ drawGradient(gc, clArea); /* Fix Region Shape */ fixRegion(gc, clArea); gc.dispose(); footerCircle.setBackgroundImage(fFooterBgImage); if (oldBgImage != null) oldBgImage.dispose(); } private void drawGradient(GC gc, Rectangle clArea) { gc.setBackground(fNotifierColors.getGradientBegin()); gc.setForeground(fNotifierColors.getGradientEnd()); gc.fillGradientRectangle(clArea.x, clArea.y, clArea.width, clArea.height, true); } private void fixRegion(GC gc, Rectangle clArea) { gc.setForeground(fNotifierColors.getBorder()); /* Fill Bottom Left */ gc.drawPoint(2, clArea.height - 0); gc.drawPoint(3, clArea.height - 0); gc.drawPoint(1, clArea.height - 1); gc.drawPoint(0, clArea.height - 2); gc.drawPoint(0, clArea.height - 3); /* Fill Bottom Right */ gc.drawPoint(clArea.width - 4, clArea.height - 0); gc.drawPoint(clArea.width - 3, clArea.height - 0); gc.drawPoint(clArea.width - 2, clArea.height - 1); gc.drawPoint(clArea.width - 1, clArea.height - 2); gc.drawPoint(clArea.width - 1, clArea.height - 3); } }); /* Title Label displaying RSSOwl */ fFooterCircleLabel = new CLabel(footerCircle, SWT.NO_FOCUS); fFooterCircleLabel.setFont(fBoldTextFont); fFooterCircleLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); /* Nav to previous News */ fPrevButton = new CLabel(footerCircle, SWT.NO_FOCUS); fPrevButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, true)); fPrevButton.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); fPrevButton.addMouseTrackListener(fMouseTrackListner); fPrevButton.setImage(fPrevImageDisabled); fPrevButton.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { onNavPrevious(); } @Override public void mouseDown(MouseEvent e) { if (canNavPrevious()) fPrevButton.setImage(fPrevImagePressed); } }); /* Nav to next News */ fNextButton = new CLabel(footerCircle, fMode == Mode.INCOMING_AUTOMATIC ? SWT.NO_FOCUS : SWT.NONE); fNextButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, true)); fNextButton.addMouseTrackListener(fMouseTrackListner); fNextButton.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); fNextButton.setImage(fNextImageDisabled); fNextButton.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { onNavNext(); } @Override public void mouseDown(MouseEvent e) { if (canNavNext()) fNextButton.setImage(fNextImagePressed); } }); /* Simple Keybinding Support */ KeyAdapter keyListener = new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { handleUserAction(); if (e.keyCode == SWT.ARROW_LEFT || e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.PAGE_UP) onNavPrevious(); else if (e.keyCode == SWT.ARROW_RIGHT || e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.PAGE_DOWN) onNavNext(); else if (e.keyCode == SWT.HOME) onNavFirstPage(); else if (e.keyCode == SWT.END) onNavLastPage(); else if (e.keyCode == ' ') onNavNextAndMarkRead(); else if (e.keyCode == SWT.CR) doRestore(); else if (e.keyCode == SWT.ESC) doClose(); } }; if (fMode == Mode.INCOMING_AUTOMATIC) fShell.addKeyListener(keyListener); else fNextButton.addKeyListener(keyListener); /* Allow to navigate with Mouse Wheel Too */ fShell.addMouseWheelListener(new MouseWheelListener() { public void mouseScrolled(MouseEvent e) { handleUserAction(); if (e.count < 0) onNavNext(); else if (e.count > 0) onNavPrevious(); } }); /* Close Shell once deactivated and not incoming automatic */ fShell.addListener(SWT.Deactivate, new Listener() { public void handleEvent(Event event) { if (fMode != Mode.INCOMING_AUTOMATIC && fGlobalScope.getBoolean(DefaultPreferences.CLOSE_NOTIFIER_ON_OPEN)) doClose(); } }); return outerCircle; } private void handleUserAction() { if (fMouseOverNotifier || fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP)) return; fAutoCloser.cancel(); fAutoCloser.schedule(fGlobalScope.getInteger(DefaultPreferences.AUTOCLOSE_NOTIFICATION_VALUE) * 1000); } /* * @see org.eclipse.jface.dialogs.PopupDialog#getFocusControl() */ @Override protected Control getFocusControl() { return fNextButton; } private void onNavFirstPage() { int newOffset = 0; fDisplayOffset = newOffset; updateTitleLabel(); updateContents(newOffset); } private void onNavLastPage() { int newOffset; if (fDisplayedItems.size() % fItemLimit == 0) newOffset = fDisplayedItems.size() - fItemLimit; else newOffset = fDisplayedItems.size() - (fDisplayedItems.size() % fItemLimit); if (newOffset >= 0) { fDisplayOffset = newOffset; updateTitleLabel(); updateContents(newOffset); } } private boolean onNavPrevious() { int newOffset = fDisplayOffset - fItemLimit; if (newOffset >= 0) { fDisplayOffset = newOffset; updateTitleLabel(); updateContents(newOffset); return true; } return false; } private boolean onNavNext() { int newOffset = fDisplayOffset + fItemLimit; if (newOffset < fDisplayedItems.size()) { fDisplayOffset = newOffset; updateTitleLabel(); updateContents(newOffset); return true; } return false; } private void onNavNextAndMarkRead() { for (int i = fDisplayOffset; i < fDisplayOffset + fItemLimit && i < fDisplayedItems.size(); i++) { NotificationItem item = fDisplayedItems.get(i); item.setRead(true); } boolean navigated = onNavNext(); if (!navigated) doClose(); } private void updateNavButtons() { boolean isPrevEnabled = canNavPrevious(); boolean isNextEnabled = canNavNext(); fPrevButton.setImage(isPrevEnabled ? fPrevImageNormal : fPrevImageDisabled); fNextButton.setImage(isNextEnabled ? fNextImageNormal : fNextImageDisabled); } private boolean canNavNext() { return fDisplayOffset + fItemLimit < fDisplayedItems.size(); } private boolean canNavPrevious() { return fDisplayOffset - fItemLimit >= 0; } /* * @see org.eclipse.jface.dialogs.PopupDialog#getInitialLocation(org.eclipse.swt.graphics.Point) */ @Override protected Point getInitialLocation(Point initialSize) { Rectangle clArea = getPrimaryClientArea(); return new Point(clArea.width + clArea.x - initialSize.x, clArea.height + clArea.y - initialSize.y); } /* * @see org.eclipse.jface.dialogs.PopupDialog#getInitialSize() */ @Override protected Point getInitialSize() { int initialHeight = fShell.computeSize(DEFAULT_WIDTH, SWT.DEFAULT).y; return new Point(DEFAULT_WIDTH, initialHeight + fVisibleItemsCount * CLABEL_HEIGHT); } /** * Get the Client Area of the primary Monitor. * * @return Returns the Client Area of the primary Monitor. */ private Rectangle getPrimaryClientArea() { Monitor primaryMonitor = fShell.getDisplay().getPrimaryMonitor(); return (primaryMonitor != null) ? primaryMonitor.getClientArea() : fShell.getDisplay().getClientArea(); } /* * @see org.eclipse.jface.dialogs.PopupDialog#close() */ @Override public boolean close() { /* * This method is overriden because the parent PopupDialog class automatically * closes the popup when the Shell is deactivated. * We want to leave the popup open in this case though for stability reasons. */ return false; } /** * The actual implementation of close(). * * @return <code>true</code> if the window is (or was already) closed, and * <code>false</code> if it is still open */ protected boolean doClose() { fResources.dispose(); if (fLastUsedRegion != null) fLastUsedRegion.dispose(); if (fTitleBgImage != null) fTitleBgImage.dispose(); if (fFooterBgImage != null) fFooterBgImage.dispose(); if (fInitialItems != null) fInitialItems.clear(); return super.close(); } /* Restore RSSOwl and Close the Notifier */ private void doRestore() { IWorkbenchPage page = OwlUI.getPage(); if (page != null) { /* Restore Window */ OwlUI.restoreWindow(page); /* Close Notifier */ doClose(); } } private boolean shouldFadeIn() { return fMode == Mode.INCOMING_AUTOMATIC && FADE_ENABLED && fgFadeSupported; } }