/* ********************************************************************** **
** 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.dialogs;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.rssowl.core.Owl;
import org.rssowl.core.connection.ConnectionException;
import org.rssowl.core.connection.IProtocolHandler;
import org.rssowl.core.interpreter.InterpreterException;
import org.rssowl.core.interpreter.ParserException;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IFeed;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.StringUtils;
import org.rssowl.ui.internal.Activator;
import org.rssowl.ui.internal.Application;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.actions.OpenInBrowserAction;
import org.rssowl.ui.internal.editors.feed.NewsBrowserLabelProvider;
import org.rssowl.ui.internal.util.CBrowser;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.util.LayoutUtils;
import org.rssowl.ui.internal.util.UIBackgroundJob;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.List;
/**
* The {@link PreviewFeedDialog} can be used to preview a {@link IBookMark}s
* contents without adding it to the list of subscriptions.
*
* @author bpasero
*/
public class PreviewFeedDialog extends Dialog {
/* Windows only: Mark of the Web */
private static final String IE_MOTW = "<!-- saved from url=(0014)about:internet -->"; //$NON-NLS-1$
private static final int DIALOG_WIDTH_DLUS = 600;
private static final int DIALOG_HEIGHT_DLUS = 400;
private static final String DIALOG_SETTINGS_KEY = "org.rssowl.ui.internal.dialogs.PreviewFeedDialog"; //$NON-NLS-1$
private static final int MAX_NEWS_SHOWN = 50;
private IBookMark fBookmark;
private FeedLinkReference fFeedReference;
private IFeed fLoadedFeed;
private CBrowser fBrowser;
private boolean fFirstTimeOpen;
private NewsBrowserLabelProvider fLabelProvider;
private String fNewsFontFamily;
private String fNormalFontCSS;
private Link fStatusLabel;
/**
* @param parentShell
* @param bookmark
*/
public PreviewFeedDialog(Shell parentShell, IBookMark bookmark) {
this(parentShell, bookmark, null, null);
}
/**
* @param parentShell
* @param bookmark
* @param feedReference
*/
public PreviewFeedDialog(Shell parentShell, IBookMark bookmark, FeedLinkReference feedReference) {
this(parentShell, bookmark, null, feedReference);
}
/**
* @param parentShell
* @param bookmark
* @param feed
*/
public PreviewFeedDialog(Shell parentShell, IBookMark bookmark, IFeed feed) {
this(parentShell, bookmark, feed, null);
}
PreviewFeedDialog(Shell parentShell, IBookMark bookmark, IFeed feed, FeedLinkReference feedReference) {
super(parentShell);
fBookmark = bookmark;
fLoadedFeed = feed;
fFeedReference = feedReference;
fFirstTimeOpen = (Activator.getDefault().getDialogSettings().getSection(DIALOG_SETTINGS_KEY) == null);
createFonts();
}
/* Init the Theme Font (from UI Thread) */
private void createFonts() {
int fontHeight = 10;
Font newsFont = OwlUI.getThemeFont(OwlUI.NEWS_TEXT_FONT_ID, SWT.NORMAL);
FontData[] fontData = newsFont.getFontData();
if (fontData.length > 0) {
fNewsFontFamily = fontData[0].getName();
fontHeight = fontData[0].getHeight();
}
int normal = fontHeight;
String fontUnit = "pt"; //$NON-NLS-1$
fNormalFontCSS = "font-size: " + normal + fontUnit + ";"; //$NON-NLS-1$ //$NON-NLS-2$
}
/*
* @see org.eclipse.jface.dialogs.TitleAreaDialog#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Control createDialogArea(Composite parent) {
/* Composite to hold all components */
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayout(LayoutUtils.createGridLayout(1, 0, 0));
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
/* Browser to preview News */
fBrowser = new CBrowser(composite, SWT.NONE);
fBrowser.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
fBrowser.setScriptDisabled(true);
fBrowser.getControl().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.character == SWT.ESC)
close();
}
});
/* Label Provider to produce HTML per News */
fLabelProvider = new NewsBrowserLabelProvider(fBrowser);
/* Load and Display the Feed */
loadFeed();
/* Separator */
new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
applyDialogFont(composite);
return composite;
}
private void loadFeed() {
/* Show Info that Feed is loading */
if (fLoadedFeed == null || fLoadedFeed.getVisibleNews().isEmpty()) {
if (StringUtils.isSet(fBookmark.getName()))
showMessage(NLS.bind(Messages.PreviewFeedDialog_LOAD_FEED_N, fBookmark.getName()), false, true);
else
showMessage(Messages.PreviewFeedDialog_LOAD_FEED, false, true);
}
/* Load Feed in Background */
JobRunner.runUIUpdater(new UIBackgroundJob(fBrowser.getControl()) {
private IFeed feed;
private Exception error;
@Override
protected void runInBackground(IProgressMonitor monitor) {
/* First Check if a Feed was already provided */
if (fLoadedFeed != null && !fLoadedFeed.getVisibleNews().isEmpty()) {
feed = fLoadedFeed;
return;
}
/* Otherwise Load Feed */
try {
/* Resolve Feed if existing */
if (fFeedReference != null)
feed = fFeedReference.resolve();
/* Create Temporary Feed */
if (feed == null || feed.getVisibleNews().isEmpty()) {
feed = Owl.getModelFactory().createFeed(null, fBookmark.getFeedLinkReference().getLink());
/* Return if dialog closed */
if (monitor.isCanceled() || getShell().isDisposed() || fBrowser.getControl().isDisposed())
return;
/* Retrieve Stream */
IProtocolHandler handler = Owl.getConnectionService().getHandler(feed.getLink());
InputStream inS = handler.openStream(feed.getLink(), monitor, null);
/* Return if dialog closed */
if (monitor.isCanceled() || getShell().isDisposed() || fBrowser.getControl().isDisposed())
return;
/* Interpret Feed */
Owl.getInterpreter().interpret(inS, feed, null);
}
} catch (ConnectionException e) {
error = e;
Activator.safeLogError(e.getMessage(), e);
} catch (ParserException e) {
error = e;
Activator.safeLogError(e.getMessage(), e);
} catch (InterpreterException e) {
error = e;
Activator.safeLogError(e.getMessage(), e);
}
}
@Override
protected void runInUI(IProgressMonitor monitor) {
if (feed != null && error == null)
showFeed(feed);
else if (error != null) {
String errorMessage = CoreUtils.toMessage(error);
if (StringUtils.isSet(errorMessage))
showMessage(NLS.bind(Messages.PreviewFeedDialog_UNABLE_LOAD_FEED, errorMessage), true, false);
}
}
});
}
private void showMessage(String msg, boolean isError, boolean showProgress) {
if (fBrowser.getControl().isDisposed())
return;
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"); //$NON-NLS-1$
html.append("<html>\n"); //$NON-NLS-1$
html.append("<body style=\"overflow: auto; font-family: ").append(fNewsFontFamily).append(",Verdanna,sans-serif; ").append(fNormalFontCSS).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (showProgress)
html.append("<img src=\"" + OwlUI.getImageUri("/icons/obj16/progress.gif", "progress.gif") + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
if (isError)
html.append("<span style=\"color: darkred;\">"); //$NON-NLS-1$
else if (showProgress)
html.append("<span style=\"padding-left:3px; vertical-align:top;\">"); //$NON-NLS-1$
html.append(msg);
if (isError || showProgress)
html.append("</span>"); //$NON-NLS-1$
html.append("</body>\n"); //$NON-NLS-1$
html.append("</html>\n"); //$NON-NLS-1$
fBrowser.getControl().setText(html.toString());
}
private void showFeed(final IFeed feed) {
if (feed != null && !fBrowser.getControl().isDisposed()) {
List<INews> news = feed.getNewsByStates(INews.State.getVisible());
int newsCount= news.size();
if (news.size() > MAX_NEWS_SHOWN)
news = news.subList(0, MAX_NEWS_SHOWN);
/* Start HTML */
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"); //$NON-NLS-1$
/* Windows only: Mark of the Web */
if (Application.IS_WINDOWS) {
html.append(IE_MOTW);
html.append("\n"); //$NON-NLS-1$
}
/* Head */
html.append("<html>\n <head>\n"); //$NON-NLS-1$
/* Append Base URI if available */
URI base = (feed.getBase() != null) ? feed.getBase() : feed.getLink();
if (base != null) {
html.append(" <base href=\""); //$NON-NLS-1$
html.append(base);
html.append("\">"); //$NON-NLS-1$
}
/* Meta */
html.append("\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); //$NON-NLS-1$
/* CSS */
try {
StringWriter writer = new StringWriter();
fLabelProvider.writeCSS(writer, false);
html.append(writer.toString());
} catch (IOException e) {
/* Will Never Happen */
}
/* Open Body */
html.append(" </head>\n <body id=\"owlbody\">\n"); //$NON-NLS-1$
/* Title */
if (StringUtils.isSet(fBookmark.getName()))
html.append("<div class=\"group\">").append(fBookmark.getName()).append("</div>"); //$NON-NLS-1$ //$NON-NLS-2$
/* Write News */
for (INews item : news) {
html.append(fLabelProvider.getText(item, false));
}
/* End HTML */
html.append("\n </body>\n</html>"); //$NON-NLS-1$
/* Apply to Browser */
fBrowser.getControl().setText(html.toString());
/* Also Update Status */
if (StringUtils.isSet(fBookmark.getName())) {
StringBuilder str = new StringBuilder();
if (feed.getHomepage() != null) {
str.append(NLS.bind(Messages.PreviewFeedDialog_FOUND_N_NEWS_HOMEPAGE, newsCount, fBookmark.getName()));
fStatusLabel.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
new OpenInBrowserAction(new StructuredSelection(feed.getHomepage())).run();
}
});
} else
str.append(NLS.bind(Messages.PreviewFeedDialog_FOUND_N_NEWS, newsCount, fBookmark.getName()));
fStatusLabel.setText(str.toString());
}
}
}
/*
* @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
*/
@Override
protected void configureShell(Shell shell) {
super.configureShell(shell);
if (StringUtils.isSet(fBookmark.getName()))
shell.setText(NLS.bind(Messages.PreviewFeedDialog_PREVIEW_OF, fBookmark.getName()));
else
shell.setText(Messages.PreviewFeedDialog_PREVIEW);
}
/*
* @see org.eclipse.jface.window.Window#getShellStyle()
*/
@Override
protected int getShellStyle() {
int style = SWT.TITLE | SWT.BORDER | SWT.RESIZE | SWT.MIN | SWT.MAX | SWT.CLOSE | getDefaultOrientation();
return style;
}
/*
* @see org.eclipse.jface.dialogs.TrayDialog#createButtonBar(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Control createButtonBar(Composite parent) {
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
Composite buttonBar = new Composite(parent, SWT.NONE);
buttonBar.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
buttonBar.setLayout(layout);
/* Status Label */
fStatusLabel = new Link(buttonBar, SWT.NONE);
applyDialogFont(fStatusLabel);
fStatusLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
if (StringUtils.isSet(fBookmark.getName()))
fStatusLabel.setText(fBookmark.getName());
/* Close */
Button closeButton = createButton(buttonBar, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, false);
closeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
close();
}
});
return buttonBar;
}
/*
* @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings()
*/
@Override
protected IDialogSettings getDialogBoundsSettings() {
IDialogSettings settings = Activator.getDefault().getDialogSettings();
IDialogSettings section = settings.getSection(DIALOG_SETTINGS_KEY);
if (section != null)
return section;
return settings.addNewSection(DIALOG_SETTINGS_KEY);
}
/*
* @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsStrategy()
*/
@Override
protected int getDialogBoundsStrategy() {
return Dialog.DIALOG_PERSISTLOCATION | Dialog.DIALOG_PERSISTSIZE;
}
/*
* @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
*/
@Override
protected void initializeBounds() {
super.initializeBounds();
Shell shell = getShell();
/* Minimum Size */
int minWidth = convertHorizontalDLUsToPixels(DIALOG_WIDTH_DLUS);
int minHeight = convertHorizontalDLUsToPixels(DIALOG_HEIGHT_DLUS);
/* Required Size */
Point requiredSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
/* Set Size */
shell.setSize(Math.max(minWidth, requiredSize.x), Math.max(minHeight, requiredSize.y));
/* Set Location */
if (fFirstTimeOpen)
LayoutUtils.positionShell(shell);
}
}