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