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