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