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