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