JAL-3746 JAL-3814 don’t automatically launch the news browser when the desktop is...
[jalview.git] / src / jalview / gui / BlogReader.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.bin.Cache;
24 import jalview.bin.Console;
25 import jalview.util.MessageManager;
26
27 import java.awt.BorderLayout;
28 import java.awt.Component;
29 import java.awt.Dialog.ModalExclusionType;
30 import java.awt.Dimension;
31 import java.awt.Font;
32 import java.awt.Rectangle;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.KeyEvent;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.WindowAdapter;
38 import java.awt.event.WindowEvent;
39 import java.beans.PropertyChangeListener;
40 import java.text.DateFormat;
41 import java.text.SimpleDateFormat;
42 import java.util.ArrayList;
43 import java.util.Calendar;
44 import java.util.Collections;
45 import java.util.Date;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Map;
49
50 import javax.swing.AbstractAction;
51 import javax.swing.AbstractButton;
52 import javax.swing.Action;
53 import javax.swing.DefaultListCellRenderer;
54 import javax.swing.DefaultListModel;
55 import javax.swing.Icon;
56 import javax.swing.ImageIcon;
57 import javax.swing.JButton;
58 import javax.swing.JFrame;
59 import javax.swing.JLabel;
60 import javax.swing.JList;
61 import javax.swing.JMenuItem;
62 import javax.swing.JPanel;
63 import javax.swing.JPopupMenu;
64 import javax.swing.JScrollPane;
65 import javax.swing.JSplitPane;
66 import javax.swing.JToolBar;
67 import javax.swing.ListSelectionModel;
68 import javax.swing.SwingUtilities;
69 import javax.swing.event.HyperlinkEvent;
70 import javax.swing.event.HyperlinkListener;
71 import javax.swing.event.ListSelectionEvent;
72 import javax.swing.event.ListSelectionListener;
73
74 import org.robsite.jswingreader.action.MarkChannelAsRead;
75 import org.robsite.jswingreader.action.MarkChannelAsUnread;
76 import org.robsite.jswingreader.action.MarkItemAsRead;
77 import org.robsite.jswingreader.action.MarkItemAsUnread;
78 import org.robsite.jswingreader.action.UpdatableAction;
79 import org.robsite.jswingreader.model.Channel;
80 import org.robsite.jswingreader.model.ChannelListModel;
81 import org.robsite.jswingreader.model.Item;
82 import org.robsite.jswingreader.model.SimpleRSSParser;
83 import org.robsite.jswingreader.ui.BlogContentPane;
84 import org.robsite.jswingreader.ui.ItemReadTimer;
85 import org.robsite.jswingreader.ui.Main;
86 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
87
88 /**
89  * Blog reading window, adapted from JSwingReader's
90  * org.robsite.jswingreader.ui.MainWindow class
91  */
92
93 public class BlogReader extends JPanel
94 {
95   private JButton buttonRefresh = new JButton();
96
97   private JToolBar toolBar = new JToolBar();
98
99   private JLabel statusBar = new JLabel();
100
101   private JPanel panelMain = new JPanel();
102
103   private BorderLayout layoutMain = new BorderLayout();
104
105   private BorderLayout borderLayout1 = new BorderLayout();
106
107   private JPanel topPanel = new JPanel();
108
109   private JPanel bottomPanel = new JPanel();
110
111   private JSplitPane topBottomSplitPane = new JSplitPane();
112
113   private JList listItems = new JList(new DefaultListModel());
114
115   // SWITCH IN JALVIEW HTML VIEWER PANE HERE
116   private BlogContentPane textDescription = new BlogContentPane();
117
118   // ADD IN JALVIEW BANNER FOR PRETTINESS
119   private BorderLayout borderLayout4 = new BorderLayout();
120
121   private BorderLayout borderLayout5 = new BorderLayout();
122
123   private ChannelListModel _channelModel = null;
124
125   private JList listChannels = new JList();
126
127   private Action exitAction = new Action()
128   {
129
130     @Override
131     public void actionPerformed(ActionEvent arg0)
132     {
133       if (xf != null)
134       {
135         xf.dispose();
136       }
137       xf = null;
138       jd = null;
139       if (parent != null)
140       {
141         parent.showNews(false);
142       }
143
144     }
145
146     @Override
147     public void setEnabled(boolean arg0)
148     {
149
150     }
151
152     @Override
153     public void removePropertyChangeListener(PropertyChangeListener arg0)
154     {
155       // TODO Auto-generated method stub
156
157     }
158
159     @Override
160     public void putValue(String arg0, Object arg1)
161     {
162       // TODO Auto-generated method stub
163
164     }
165
166     @Override
167     public boolean isEnabled()
168     {
169       // TODO Auto-generated method stub
170       return true;
171     }
172
173     @Override
174     public Object getValue(String arg0)
175     {
176       // TODO Auto-generated method stub
177       return null;
178     }
179
180     @Override
181     public void addPropertyChangeListener(PropertyChangeListener arg0)
182     {
183       // TODO Auto-generated method stub
184
185     }
186   };
187
188   private JFrame xf = null;
189
190   private JalviewDialog jd = null;
191
192   private JalviewDialog createDialog()
193   {
194
195     return jd = new JalviewDialog()
196     {
197
198       @Override
199       protected void raiseClosed()
200       {
201         if (parent != null)
202         {
203           Console.debug("News window closed.");
204           jd = null;
205           parent.showNews(false);
206         }
207       }
208
209       @Override
210       protected void okPressed()
211       {
212         // TODO Auto-generated method stub
213
214       }
215
216       @Override
217       protected void cancelPressed()
218       {
219         // TODO Auto-generated method stub
220
221       }
222     };
223   }
224
225   private JLabel lblChannels = new JLabel();
226
227   private List _updatableActions = new ArrayList();
228
229   private ItemReadTimer _itemTimer = null;
230
231   private JPopupMenu _popupItems = null;
232
233   private JPopupMenu _popupChannels = null;
234
235   private String lastm = "";
236
237   private boolean newsnew = false;
238
239   private Desktop parent = null;
240
241   BlogReader()
242   {
243     this(null);
244   }
245
246   // should we ignore fake gui events
247   private boolean updating = false;
248
249   public BlogReader(Desktop desktop)
250   {
251     Console.debug("Constructing news reader.");
252
253     parent = desktop;
254     _channelModel = new ChannelListModel();
255     // Construct our jalview news channel
256     Channel chan = new Channel();
257     chan.setURL(
258             Cache.getDefault("JALVIEW_NEWS_RSS",
259                     Cache.getDefault("www.jalview.org",
260                             "https://www.jalview.org")
261                             + "/feeds/desktop/rss"));
262     loadLastM();
263     _channelModel.addChannel(chan);
264     updating = true;
265     try
266     {
267       jbInit();
268       postInit();
269     } catch (Exception e)
270     {
271       e.printStackTrace();
272     }
273
274     initItems(chan);
275     updating = false;
276     if (!Cache.getDefault("NONEWS", false))
277     {
278       boolean setvisible = checkForNew(chan, true);
279
280       if (setvisible)
281       {
282
283         Console.debug("Will show jalview news automatically");
284         showNews();
285       }
286     }
287     Console.debug("Completed construction of reader.");
288
289   }
290
291   /**
292    * check if the news panel's container is visible
293    */
294   @Override
295   public boolean isVisible()
296   {
297     if (parent == null)
298     {
299       return xf != null && xf.isVisible();
300     }
301     return jd != null && jd.isVisible();
302   }
303
304   /**
305    * display the container for the news panel
306    */
307   public void showNews()
308   {
309     final BlogReader me = this;
310     SwingUtilities.invokeLater(new Runnable()
311     {
312       @Override
313       public void run()
314       {
315         Rectangle bounds = new Rectangle(5, 5, 550, 350);
316         if (parent == null)
317         {
318           xf = new JFrame();
319           xf.setContentPane(me);
320           xf.addWindowListener(new WindowAdapter()
321           {
322             @Override
323             public void windowClosing(WindowEvent e)
324             {
325               ActionEvent actionEvent = new ActionEvent(this,
326                       ActionEvent.ACTION_FIRST,
327                       (String) exitAction.getValue(Action.NAME));
328               exitAction.actionPerformed(actionEvent);
329             }
330
331             @Override
332             public void windowOpened(WindowEvent e)
333             {
334             }
335           });
336           me.setSize(new Dimension(550, 350));
337           xf.setVisible(true);
338         }
339         else
340         {
341           createDialog();
342           bounds = new Rectangle(5, 5, 550, 350);
343           jd.initDialogFrame(me, false, false,
344                   MessageManager.getString("label.news_from_jalview"),
345                   bounds.width, bounds.height);
346           jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
347           Console.debug("Displaying news.");
348           jd.waitForInput();
349         }
350       }
351     });
352   }
353
354   /**
355    * update hasnew flag and mark all new messages as unread.
356    */
357   private boolean checkForNew(Channel chan, boolean updateItems)
358   {
359
360     if (!updating || updateItems)
361     {
362       newsnew = false;
363     }
364     java.util.Date earliest = null;
365     try
366     {
367       earliest = new SimpleDateFormat("YYYY-MM-DD")
368               .parse(chan.getHTTPLastModified());
369     } catch (Exception x)
370     {
371     }
372     if (chan != null && chan.getItems() != null)
373     {
374       Console.debug("Scanning news items: newsnew=" + newsnew
375               + " and lastDate is " + lastDate);
376       for (Item i : (List<Item>) chan.getItems())
377       {
378         Date published = i.getPublishDate();
379         boolean isread = lastDate == null ? false
380                 : (published != null && !lastDate.before(published));
381
382         if (!updating || updateItems)
383         {
384           newsnew |= !isread;
385         }
386         if (updateItems)
387         {
388           i.setRead(isread);
389         }
390         if (published != null && !i.isRead())
391         {
392           if (earliest == null || earliest.after(published))
393           {
394             earliest = published;
395           }
396         }
397       }
398     }
399     if (!updateItems && !updating && lastDate == null)
400     {
401       lastDate = earliest;
402     }
403     return newsnew;
404   }
405
406   java.util.Date lastDate = null;
407
408   private void loadLastM()
409   {
410     lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
411   }
412
413   private void saveLastM(Item item)
414   {
415     if (item != null)
416     {
417       if (item.getPublishDate() != null)
418       {
419         if (lastDate == null || item.getPublishDate().after(lastDate))
420         {
421           lastDate = item.getPublishDate();
422         }
423       }
424
425       if (_channelModel.getElementAt(0) != null)
426       {
427         checkForNew((Channel) _channelModel.getElementAt(0), false);
428       }
429       if (lastDate != null)
430       {
431         String formatted = Cache
432                 .setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", lastDate);
433         Console.debug("Saved last read date as " + formatted);
434       }
435     }
436   }
437
438   private void jbInit() throws Exception
439   {
440     setLayout(layoutMain);
441     panelMain.setLayout(borderLayout1);
442     topPanel.setLayout(borderLayout5);
443     bottomPanel.setLayout(borderLayout4);
444     topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
445     topBottomSplitPane.setDividerLocation(100);
446     topBottomSplitPane.setTopComponent(topPanel);
447     topBottomSplitPane.setBottomComponent(bottomPanel);
448     JScrollPane spTextDescription = new JScrollPane(textDescription);
449     textDescription.setText("");
450     statusBar.setText(new StringBuffer("[")
451             .append(MessageManager.getString("label.status")).append("]")
452             .toString());
453     buttonRefresh.addActionListener(new ActionListener()
454     {
455
456       @Override
457       public void actionPerformed(ActionEvent e)
458       {
459         refreshNews();
460       }
461     });
462     add(statusBar, BorderLayout.SOUTH);
463     toolBar.add(buttonRefresh);
464     toolBar.addSeparator();
465     JLabel about = new JLabel(
466             "brought to you by JSwingReader (jswingreader.sourceforge.net)");
467     toolBar.add(about);
468     toolBar.setFloatable(false);
469     add(toolBar, BorderLayout.NORTH);
470     panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
471     add(panelMain, BorderLayout.CENTER);
472     JScrollPane spListItems = new JScrollPane(listItems);
473     listItems
474             .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
475     topPanel.add(spListItems, BorderLayout.CENTER);
476     bottomPanel.add(spTextDescription, BorderLayout.CENTER);
477     listChannels.setModel(_channelModel);
478
479     listItems.addMouseListener(new java.awt.event.MouseAdapter()
480     {
481       @Override
482       public void mouseClicked(MouseEvent e)
483       {
484         listItems_mouseClicked(e);
485       }
486     });
487     _popupItems = _buildItemsPopupMenu();
488     _popupChannels = _buildChannelsPopupMenu();
489     ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
490             _popupItems);
491     ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
492             _popupChannels);
493     listItems.addMouseListener(popupAdapter);
494     listItems.setCellRenderer(new ItemsRenderer());
495     lblChannels.setText(MessageManager.getString("label.channels"));
496   }
497
498   private void postInit()
499   {
500     // clear the default hyperlink listener and replace with our own.
501     for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
502     {
503       textDescription.removeHyperlinkListener(hll);
504     }
505     textDescription.addHyperlinkListener(new HyperlinkListener()
506     {
507       @Override
508       public void hyperlinkUpdate(HyperlinkEvent e)
509       {
510         if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
511         {
512           Desktop.showUrl(e.getURL().toExternalForm());
513         }
514       }
515     });
516
517     listItems.addListSelectionListener(new ListSelectionListener()
518     {
519       @Override
520       public void valueChanged(ListSelectionEvent e)
521       {
522         if (e.getValueIsAdjusting() == false)
523         {
524           _itemsValueChanged(listItems);
525         }
526       }
527     });
528     listChannels.setSelectedIndex(1);
529     _updateAllActions();
530     _updateToolbarButtons();
531
532     _itemTimer = new ItemReadTimer(listChannels, listItems);
533     _itemsValueChanged(listItems);
534   }
535
536   public class LaunchJvBrowserOnItem extends AbstractAction
537           implements UpdatableAction
538   {
539     JList _listItems = null;
540
541     public LaunchJvBrowserOnItem(JList listItems)
542     {
543       super("Open in Browser");
544       this.putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
545       this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
546       _listItems = listItems;
547     }
548
549     @Override
550     public void actionPerformed(ActionEvent e)
551     {
552       Object o = _listItems.getSelectedValue();
553       if (o instanceof Item)
554       {
555         Item item = (Item) o;
556         item.setRead(true);
557         _listItems.repaint();
558
559         Desktop.showUrl(item.getLink());
560       }
561     }
562
563     @Override
564     public void update(Object o)
565     {
566       setEnabled(true);
567       if (_listItems == null || _listItems.getModel().getSize() == 0)
568       {
569         setEnabled(false);
570       }
571       else if (_listItems.getSelectedIndex() == -1)
572       {
573         setEnabled(false);
574       }
575     }
576
577   }
578
579   private JPopupMenu _buildItemsPopupMenu()
580   {
581     JPopupMenu popup = new JPopupMenu();
582     popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
583     popup.addSeparator();
584     popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
585     popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
586     return popup;
587   }
588
589   private JPopupMenu _buildChannelsPopupMenu()
590   {
591     JPopupMenu popup = new JPopupMenu();
592     popup.add(
593             new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
594     popup.add(new JMenuItem(
595             new MarkChannelAsUnread(listChannels, listItems)));
596     return popup;
597   }
598
599   private void initItems(Channel channel)
600   {
601     if (channel == null)
602     {
603       channel = new Channel();
604     }
605     if (!channel.isOpen() && channel.getURL() != null)
606     {
607       try
608       {
609         SimpleRSSParser.parse(channel);
610       } catch (Exception ex)
611       {
612         ex.printStackTrace();
613       }
614     }
615     DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
616     itemsModel.clear();
617     Iterator iter = (channel.getItems() != null)
618             ? channel.getItems().iterator()
619             : Collections.EMPTY_LIST.iterator();
620     while (iter.hasNext())
621     {
622       itemsModel.addElement(iter.next());
623     }
624     if (itemsModel.getSize() > 0)
625     {
626       listItems.setSelectedIndex(0);
627       _itemsValueChanged(listItems);
628     }
629     setStatusBarText(channel.getURL());
630     _updateAllActions();
631   }
632
633   private void _itemsValueChanged(JList itemList)
634   {
635     Item item = (Item) itemList.getSelectedValue();
636     if (item == null)
637     {
638       if (itemList.getModel().getSize() > 0)
639       {
640         item = (Item) itemList.getModel().getElementAt(0);
641       }
642       if (item == null)
643       {
644         item = new Item();
645       }
646       else
647       {
648         itemList.setSelectedIndex(0);
649       }
650     }
651
652     if (_itemTimer != null)
653     {
654       // prefer a shorter delay than 5s
655       _itemTimer.setDelay(300);
656       _itemTimer.start();
657       _itemTimer.setLastItem(item);
658       final Item lastitem = item;
659       _itemTimer.addActionListener(new ActionListener()
660       {
661
662         @Override
663         public void actionPerformed(ActionEvent e)
664         {
665           saveLastM(lastitem);
666         }
667       });
668     }
669
670     setStatusBarText(item.getLink());
671     textDescription.setBlogText(item);
672     _updateAllActions();
673   }
674
675   public void setStatusBarText(String text)
676   {
677     statusBar.setText(text);
678   }
679
680   private void _updateAllActions()
681   {
682     Iterator iter = _updatableActions.iterator();
683     while (iter.hasNext())
684     {
685       UpdatableAction action = (UpdatableAction) iter.next();
686       action.update(this);
687     }
688   }
689
690   private void _updateToolbarButtons()
691   {
692     Map general = (Map) Main.getPreferences().get("general");
693     if (general == null)
694     {
695       return;
696     }
697
698     Component[] components = toolBar.getComponents();
699     for (int i = 0; i < components.length; i++)
700     {
701       Component component = components[i];
702       if (component instanceof JButton)
703       {
704         JButton button = (JButton) component;
705         if (Boolean.toString(false).equals(general.get("useToolBarText")))
706         {
707           // Remove the text if preferences state no toolbar text
708           button.setText("");
709         }
710         if (Boolean.toString(true).equals(general.get("radioTextBelow")))
711         {
712           button.setVerticalTextPosition(AbstractButton.BOTTOM);
713           button.setHorizontalTextPosition(AbstractButton.CENTER);
714         }
715         else if (Boolean.toString(true)
716                 .equals(general.get("radioTextRight")))
717         {
718           button.setVerticalTextPosition(AbstractButton.CENTER);
719           button.setHorizontalTextPosition(AbstractButton.RIGHT);
720         }
721       }
722     }
723   }
724
725   private void listItems_mouseClicked(MouseEvent e)
726   {
727     if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
728     {
729       Item item = (Item) listItems.getSelectedValue();
730       item.setRead(true);
731       saveLastM(item);
732       if (_itemTimer != null)
733       {
734         _itemTimer.stop();
735       }
736
737       Action action = new LaunchJvBrowserOnItem(listItems);
738       ActionEvent event = new ActionEvent(this,
739               ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
740       action.actionPerformed(event);
741     }
742   }
743
744   /**
745    * force the news panel to refresh
746    */
747   public void refreshNews()
748   {
749     try
750     {
751       initItems((Channel) _channelModel.getElementAt(0));
752
753     } catch (Exception x)
754     {
755     }
756   }
757
758   /**
759    * @j2sIgnore
760    * @param args
761    */
762   public static void main(String args[])
763   {
764     // this tests the detection of new news based on the last read date stored
765     // in jalview properties
766     Cache.loadProperties(null);
767     Console.initLogger();
768     // test will advance read date each time
769     Calendar today = Calendar.getInstance(),
770             lastread = Calendar.getInstance();
771     lastread.set(1983, 01, 01);
772     while (lastread.before(today))
773     {
774       String formattedDate = Cache.setDateProperty(
775               "JALVIEW_NEWS_RSS_LASTMODIFIED", lastread.getTime());
776       BlogReader me = new BlogReader();
777       System.out.println("Set last date to " + formattedDate);
778       if (me.isNewsNew())
779       {
780         Console.debug("There is news to read.");
781       }
782       else
783       {
784         Console.debug("There is no new news.");
785         me.xf.setTitle("Testing : Last read is " + me.lastDate);
786         me.showNews();
787         me.xf.toFront();
788       }
789       Console.debug("Waiting for closure.");
790       do
791       {
792         try
793         {
794           Thread.sleep(300);
795         } catch (InterruptedException x)
796         {
797         }
798       } while (me.isVisible());
799
800       if (me.isNewsNew())
801       {
802         Console.debug("Still new news after reader displayed.");
803       }
804       if (lastread.getTime().before(me.lastDate))
805       {
806         Console.debug("The news was read.");
807         lastread.setTime(me.lastDate);
808       }
809       else
810       {
811         lastread.add(Calendar.MONTH, 1);
812       }
813
814     }
815   }
816
817   boolean isNewsNew()
818   {
819     return newsnew;
820   }
821 }
822
823 class ChannelsRenderer extends DefaultListCellRenderer
824 {
825   private final static Icon _icon = new ImageIcon(
826           Main.class.getResource("image/ComposeMail16.gif"));
827
828   @Override
829   public Component getListCellRendererComponent(JList list, Object value,
830           int index, boolean isSelected, boolean cellHasFocus)
831   {
832     JLabel component = (JLabel) super.getListCellRendererComponent(list,
833             value, index, isSelected, cellHasFocus);
834     component.setIcon(ChannelsRenderer._icon);
835     if (value instanceof Channel)
836     {
837       Channel channel = (Channel) value;
838       component.setText(MessageManager
839               .formatMessage("label.channel_title_item_count", new String[]
840               { channel.getTitle(), Integer
841                       .valueOf(channel.getUnreadItemCount()).toString() }));
842       component.setToolTipText(channel.getURL());
843     }
844     return component;
845   }
846 }
847
848 class ItemsRenderer extends DefaultListCellRenderer
849 {
850   private final static Icon _icon = new ImageIcon(
851           Main.class.getResource("image/ComposeMail16.gif"));
852
853   @Override
854   public Component getListCellRendererComponent(JList list, Object value,
855           int index, boolean isSelected, boolean cellHasFocus)
856   {
857     JLabel component = (JLabel) super.getListCellRendererComponent(list,
858             value, index, isSelected, cellHasFocus);
859     component.setIcon(ItemsRenderer._icon);
860     if (value instanceof Item)
861     {
862       Item item = (Item) value;
863       if (item.getPublishDate() != null)
864       {
865         component.setText(MessageManager.formatMessage(
866                 "label.blog_item_published_on_date", new String[]
867                 { DateFormat
868                         .getDateInstance(DateFormat.LONG,
869                                 MessageManager.getLocale())
870                         .format(item.getPublishDate()).toString(),
871                     item.getTitle() }));
872       }
873       component.setToolTipText(item.getLink());
874       if (!item.isRead())
875       {
876         component.setFont(component.getFont().deriveFont(Font.BOLD));
877       }
878       else
879       {
880         component.setFont(component.getFont().deriveFont(Font.PLAIN));
881       }
882     }
883     return component;
884   }
885 }