1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2015-2016 - Scilab Enterprises - Simon Marchetto
4  * Copyright (C) 2016 - Scilab Enterprises - Clement David
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16 
17 package org.scilab.modules.ui_data.newsfeed;
18 
19 import org.scilab.modules.commons.ScilabConstants;
20 import org.scilab.modules.commons.gui.FindIconHelper;
21 import org.scilab.modules.gui.utils.WebBrowser;
22 
23 import javax.swing.JPanel;
24 import javax.swing.JEditorPane;
25 import javax.swing.JScrollPane;
26 import javax.swing.text.html.HTMLEditorKit;
27 import javax.swing.text.Document;
28 import javax.swing.event.HyperlinkEvent;
29 import javax.swing.event.HyperlinkListener;
30 import javax.swing.SwingUtilities;
31 import javax.swing.UIManager;
32 
33 import java.awt.BorderLayout;
34 import java.awt.Font;
35 import java.awt.Point;
36 import java.awt.event.ActionEvent;
37 import java.io.File;
38 import java.text.SimpleDateFormat;
39 
40 import java.net.MalformedURLException;
41 import java.net.URL;
42 import java.util.logging.Level;
43 import java.util.logging.Logger;
44 import javax.swing.AbstractAction;
45 import javax.swing.Action;
46 import javax.swing.ImageIcon;
47 import javax.swing.JButton;
48 
49 /**
50  * News feed widget Displays one news at a time.
51  * Controlled by NewsFeedController
52  * Displays news in HTML format (JEditorPane/HTMLEditorKit)
53  * Loads styles from CSS newsfeed.css (in directory ui_data/etc or SCIHOME)
54  */
55 public class NewsFeedWidget extends JPanel implements NewsFeedEventListener, HyperlinkListener {
56     private static final String NEWS_DATE_HTML_ID = "news_date";
57     private static final String NEWS_CONTENT_HTML_ID = "news_content";
58     private static final String NEWS_LINK_HTML_ID = "news_link";
59     private static final String NEWS_MEDIA_CONTENT_HTML_ID = "news_media_content";
60     private static final String NEWS_DESCRIPTION_HTML_ID = "news_description";
61 
62     private final NewsFeedController newsFeedController;
63     private final JEditorPane editorPane;
64     private final JScrollPane scrollPane;
65     private final JPanel headerPane;
66     private final JButton headerButton;
67 
NewsFeedWidget(NewsFeedController newsFeedController)68     public NewsFeedWidget(NewsFeedController newsFeedController) {
69         this.newsFeedController = newsFeedController;
70 
71         editorPane = new JEditorPane();
72         editorPane.setEditable(false);
73         editorPane.setContentType("text/html");
74 
75         // use current look & feel font
76         editorPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
77         Font font = UIManager.getFont("Label.font");
78         editorPane.setFont(font);
79 
80         HTMLEditorKit htmlEditorKit = new HTMLEditorKit();
81         editorPane.setEditorKit(htmlEditorKit);
82         Document doc = htmlEditorKit.createDefaultDocument();
83         editorPane.setDocument(doc);
84 
85         htmlEditorKit.getStyleSheet().importStyleSheet(getStyleSheetURL());
86 
87         editorPane.addHyperlinkListener(this);
88 
89         headerButton = new JButton();
90         headerPane = new JPanel(new BorderLayout());
91         headerPane.add(new JButton(new AbstractAction(null, new ImageIcon(FindIconHelper.findIcon("go-previous"))) {
92             @Override
93             public void actionPerformed(ActionEvent e) {
94                 newsFeedController.previousNews();
95             }
96         }), BorderLayout.WEST);
97         headerPane.add(headerButton, BorderLayout.CENTER);
98         headerPane.add(new JButton(new AbstractAction(null, new ImageIcon(FindIconHelper.findIcon("go-next"))) {
99             @Override
100             public void actionPerformed(ActionEvent e) {
101                 newsFeedController.nextNews();
102             }
103         }), BorderLayout.EAST);
104 
105         scrollPane = new JScrollPane(editorPane);
106 
107         setLayout(new BorderLayout());
108         add(headerPane, BorderLayout.NORTH);
109         add(scrollPane, BorderLayout.CENTER);
110 
111         // at startup, display an error message
112         displayError(NewsFeedUIMessages.NEWS_FEED_UNAVAILABLE_ERROR);
113     }
114 
newsFeedEventReceived(NewsFeedEvent evt)115     public void newsFeedEventReceived(NewsFeedEvent evt) {
116         switch (evt.getEventType()) {
117             case NewsFeedEvent.NEWS_CHANGED: {
118                 displayNews(newsFeedController.getCurrentNews());
119                 break;
120             }
121             case NewsFeedEvent.NEWSFEED_ERROR: {
122                 displayError(((NewsFeedErrorEvent) evt).getErrorMessage());
123                 break;
124             }
125         }
126     }
127 
hyperlinkUpdate(HyperlinkEvent event)128     public void hyperlinkUpdate(HyperlinkEvent event) {
129         if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
130             URL url = event.getURL();
131             if (url != null) {
132                 WebBrowser.openUrl(url, event.getDescription());
133             }
134         }
135     }
136 
displayNews(News news)137     private void displayNews(News news) {
138         StringBuilder newsHtmlBuilder = new StringBuilder();
139 
140         // Add media content (image) if exist
141         NewsMediaContent mediaContent = news.getMediaContent();
142         if (mediaContent != null) {
143             newsHtmlBuilder.append(getDivHtml(getImageHtml(mediaContent.getURL(), mediaContent.getWidth(), mediaContent.getHeight()), NEWS_MEDIA_CONTENT_HTML_ID));
144         }
145 
146         if (news.getContent() != null) {
147             // if given, use RSS item content
148             newsHtmlBuilder.append(getDivHtml(news.getContent(), NEWS_CONTENT_HTML_ID));
149         } else {
150             // otherwise get content from description
151             newsHtmlBuilder.append(getDivHtml(news.getDescription(), NEWS_DESCRIPTION_HTML_ID));
152         }
153 
154         // Add news link if exist
155         if (news.getLink() != null) {
156             newsHtmlBuilder.append(getDivHtml(getLinkHtml(news.getLink(), news.getLink()), NEWS_LINK_HTML_ID));
157         }
158 
159         display(new AbstractAction(news.getTitle()) {
160             @Override
161             public void actionPerformed(ActionEvent e) {
162                 try {
163                     WebBrowser.openUrl(new URL(news.getLink()), news.getLink());
164                 } catch (MalformedURLException ex) {
165                     Logger.getLogger(NewsFeedWidget.class.getName()).log(Level.SEVERE, null, ex);
166                 }
167             }
168         }, getHTML(newsHtmlBuilder));
169     }
170 
getImageHtml(String url, String width, String height)171     private String getImageHtml(String url, String width, String height) {
172         // Cannot setup border with CSS, limitation of CSS support of HTMLEditorKit
173         return String.format("<img src='%s' border='0' width='%s', height='%s'/>", url, width, height);
174     }
175 
getLinkHtml(String url, String description)176     private String getLinkHtml(String url, String description) {
177         return String.format("<a href='%s'>%s</a>", url, description);
178     }
179 
getSpanHtml(String spanContent, String spanId)180     private String getSpanHtml(String spanContent, String spanId) {
181         return String.format("<span id='%s'>%s</span>", spanId, spanContent);
182     }
183 
getDivHtml(String divContent, String divId)184     private String getDivHtml(String divContent, String divId) {
185         return String.format("<div id='%s'>%s</div>", divId, divContent);
186     }
187 
getDivHtml(String divContent)188     private String getDivHtml(String divContent) {
189         return String.format("<div>%s</div>", divContent);
190     }
191 
displayError(String errorMessage)192     private void displayError(String errorMessage) {
193         StringBuilder errMsgHtmlBuilder = new StringBuilder(errorMessage);
194         display(new AbstractAction("") {
195             @Override
196             public void actionPerformed(ActionEvent e) {
197                 URL url = newsFeedController.getNewsFetcher().getURL();
198                 WebBrowser.openUrl(url, url.toString());
199             }
200         }, getHTML(errMsgHtmlBuilder));
201     }
202 
getHTML(StringBuilder htmlBuilder)203     private String getHTML(StringBuilder htmlBuilder) {
204         htmlBuilder.insert(0, "<html><body>");
205         htmlBuilder.append("</body></html>");
206         return htmlBuilder.toString();
207     }
208 
display(final Action titleAction, final String htmlContent)209     private void display(final Action titleAction, final String htmlContent) {
210         try	{
211             SwingUtilities.invokeLater(new Runnable() {
212                 public void run() {
213                     headerButton.setAction(titleAction);
214                     headerButton.setToolTipText(titleAction.getValue(Action.NAME).toString());
215 
216                     editorPane.setText(htmlContent);
217                     // reset position to the upper left
218                     editorPane.setCaretPosition(0);
219                 }
220             });
221         } catch (Exception e) {
222         }
223     }
224 
getStyleSheetURL()225     private URL getStyleSheetURL() {
226         File settingsFile = new File(ScilabConstants.SCIHOME + "/newsfeed.css");
227         if (!settingsFile.exists()) {
228             settingsFile = new File(ScilabConstants.SCI + "/modules/ui_data/etc/newsfeed.css");
229         }
230         if (settingsFile.exists()) {
231             try {
232                 return settingsFile.toURI().toURL();
233             } catch (MalformedURLException e) {
234                 return null;
235             }
236         } else {
237             return null;
238         }
239     }
240 }
241