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
5 * This file is part of Jalview.
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.
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.
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 *******************************************************************************/
20 import jalview.bin.Cache;
21 import java.awt.BorderLayout;
22 import java.awt.Component;
23 import java.awt.Dimension;
25 import java.awt.Rectangle;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.awt.event.KeyEvent;
29 import java.awt.event.MouseEvent;
30 import java.awt.event.WindowAdapter;
31 import java.awt.event.WindowEvent;
32 import java.beans.PropertyChangeListener;
33 import java.text.DateFormat;
34 import java.text.SimpleDateFormat;
35 import java.util.ArrayList;
36 import java.util.Calendar;
37 import java.util.Collections;
38 import java.util.Iterator;
39 import java.util.List;
42 import javax.swing.AbstractAction;
43 import javax.swing.AbstractButton;
44 import javax.swing.Action;
45 import javax.swing.DefaultListCellRenderer;
46 import javax.swing.DefaultListModel;
47 import javax.swing.Icon;
48 import javax.swing.ImageIcon;
49 import javax.swing.JButton;
50 import javax.swing.JFrame;
51 import javax.swing.JLabel;
52 import javax.swing.JList;
53 import javax.swing.JMenuItem;
54 import javax.swing.JPanel;
55 import javax.swing.JPopupMenu;
56 import javax.swing.JScrollPane;
57 import javax.swing.JSplitPane;
58 import javax.swing.JToolBar;
59 import javax.swing.ListSelectionModel;
60 import javax.swing.SwingUtilities;
61 import javax.swing.event.HyperlinkEvent;
62 import javax.swing.event.HyperlinkListener;
63 import javax.swing.event.ListSelectionEvent;
64 import javax.swing.event.ListSelectionListener;
66 import org.robsite.jswingreader.action.MarkChannelAsRead;
67 import org.robsite.jswingreader.action.MarkChannelAsUnread;
68 import org.robsite.jswingreader.action.MarkItemAsRead;
69 import org.robsite.jswingreader.action.MarkItemAsUnread;
70 import org.robsite.jswingreader.action.UpdatableAction;
71 import org.robsite.jswingreader.model.Channel;
72 import org.robsite.jswingreader.model.ChannelListModel;
73 import org.robsite.jswingreader.model.Item;
74 import org.robsite.jswingreader.model.SimpleRSSParser;
75 import org.robsite.jswingreader.ui.BlogContentPane;
76 import org.robsite.jswingreader.ui.ItemReadTimer;
77 import org.robsite.jswingreader.ui.Main;
78 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
81 * Blog reading window, adapted from JSwingReader's
82 * org.robsite.jswingreader.ui.MainWindow class
85 public class BlogReader extends JPanel
87 private JButton buttonRefresh = new JButton();
89 private JToolBar toolBar = new JToolBar();
91 private JLabel statusBar = new JLabel();
93 private JPanel panelMain = new JPanel();
95 private BorderLayout layoutMain = new BorderLayout();
97 private BorderLayout borderLayout1 = new BorderLayout();
99 private JPanel topPanel = new JPanel();
101 private JPanel bottomPanel = new JPanel();
103 private JSplitPane topBottomSplitPane = new JSplitPane();
105 private JList listItems = new JList(new DefaultListModel());
107 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
108 private BlogContentPane textDescription = new BlogContentPane();
110 // ADD IN JALVIEW BANNER FOR PRETTINESS
111 private BorderLayout borderLayout4 = new BorderLayout();
113 private BorderLayout borderLayout5 = new BorderLayout();
115 private ChannelListModel _channelModel = null;
117 private JList listChannels = new JList();
119 private Action exitAction = new Action()
123 public void actionPerformed(ActionEvent arg0)
132 parent.showNews(false);
138 public void setEnabled(boolean arg0)
144 public void removePropertyChangeListener(PropertyChangeListener arg0)
146 // TODO Auto-generated method stub
151 public void putValue(String arg0, Object arg1)
153 // TODO Auto-generated method stub
158 public boolean isEnabled()
160 // TODO Auto-generated method stub
165 public Object getValue(String arg0)
167 // TODO Auto-generated method stub
172 public void addPropertyChangeListener(PropertyChangeListener arg0)
174 // TODO Auto-generated method stub
179 private JFrame xf = null;
181 private JalviewDialog jd = null;
183 private JalviewDialog createDialog()
186 return jd = new JalviewDialog()
190 protected void raiseClosed()
194 Cache.log.info("News window closed.");
196 parent.showNews(false);
201 protected void okPressed()
203 // TODO Auto-generated method stub
208 protected void cancelPressed()
210 // TODO Auto-generated method stub
216 private JLabel lblChannels = new JLabel();
218 private List _updatableActions = new ArrayList();
220 private ItemReadTimer _itemTimer = null;
222 private JPopupMenu _popupItems = null;
224 private JPopupMenu _popupChannels = null;
226 private String lastm = "";
228 private boolean newsnew = false;
230 private Desktop parent = null;
237 // should we ignore fake gui events
238 private boolean updating = false;
240 public BlogReader(Desktop desktop)
242 Cache.log.info("Constructing news reader.");
245 _channelModel = new ChannelListModel();
246 // Construct our jalview news channel
247 Channel chan = new Channel();
248 chan.setURL(jalview.bin.Cache.getDefault(
250 jalview.bin.Cache.getDefault("www.jalview.org",
251 "http://www.jalview.org") + "/feeds/desktop/rss"));
253 _channelModel.addChannel(chan);
259 } catch (Exception e)
266 boolean setvisible = checkForNew(chan, true);
271 Cache.log.info("Will show jalview news automatically");
274 Cache.log.info("Completed construction of reader.");
279 * check if the news panel's container is visible
281 public boolean isVisible()
285 return xf != null && xf.isVisible();
287 return jd != null && jd.isVisible();
291 * display the container for the news panel
293 public void showNews()
295 final BlogReader me = this;
296 SwingUtilities.invokeLater(new Runnable()
301 Rectangle bounds = new Rectangle(5, 5, 550, 350);
305 xf.setContentPane(me);
306 xf.addWindowListener(new WindowAdapter()
308 public void windowClosing(WindowEvent e)
310 ActionEvent actionEvent = new ActionEvent(this,
311 ActionEvent.ACTION_FIRST, (String) exitAction
312 .getValue(Action.NAME));
313 exitAction.actionPerformed(actionEvent);
316 public void windowOpened(WindowEvent e)
320 me.setSize(new Dimension(550, 350));
326 bounds = new Rectangle(5, 5, 550, 350);
327 jd.initDialogFrame(me, true, false, "News from www.jalview.org",
328 bounds.width, bounds.height);
329 Cache.log.info("Displaying news.");
337 * update hasnew flag and mark all new messages as unread.
339 private boolean checkForNew(Channel chan, boolean updateItems)
342 if (!updating || updateItems)
346 java.util.Date earliest = null;
349 earliest = new SimpleDateFormat("YYYY-MM-DD").parse(chan
350 .getHTTPLastModified());
351 } catch (Exception x)
355 if (chan != null && chan.getItems() != null)
357 Cache.log.debug("Scanning news items: newsnew=" + newsnew
358 + " and lastDate is " + lastDate);
359 for (Item i : (List<Item>) chan.getItems())
361 boolean isread = lastDate == null ? false
362 : (i.getPublishDate() != null && !lastDate.before(i
365 if (!updating || updateItems)
373 if (i.getPublishDate() != null && !i.isRead())
375 if (earliest == null || earliest.after(i.getPublishDate()))
377 earliest = i.getPublishDate();
382 if (!updateItems && !updating && lastDate == null)
389 java.util.Date lastDate = null;
391 private void loadLastM()
393 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
396 private void saveLastM(Item item)
400 if (item.getPublishDate() != null)
402 if (lastDate == null || item.getPublishDate().after(lastDate))
404 lastDate = item.getPublishDate();
408 if (_channelModel.getElementAt(0) != null)
410 checkForNew((Channel) _channelModel.getElementAt(0), false);
412 if (lastDate != null)
414 jalview.bin.Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
416 jalview.bin.Cache.log.info("Saved last read date as "
417 + jalview.bin.Cache.date_format.format(lastDate));
423 private void jbInit() throws Exception
425 setLayout(layoutMain);
426 panelMain.setLayout(borderLayout1);
427 topPanel.setLayout(borderLayout5);
428 bottomPanel.setLayout(borderLayout4);
429 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
430 topBottomSplitPane.setDividerLocation(100);
431 topBottomSplitPane.setTopComponent(topPanel);
432 topBottomSplitPane.setBottomComponent(bottomPanel);
433 JScrollPane spTextDescription = new JScrollPane(textDescription);
434 textDescription.setText("");
435 statusBar.setText(" [Status] ");
436 buttonRefresh.addActionListener(new ActionListener()
440 public void actionPerformed(ActionEvent e)
445 add(statusBar, BorderLayout.SOUTH);
446 toolBar.add(buttonRefresh);
447 toolBar.addSeparator();
448 JLabel about = new JLabel(
449 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
451 toolBar.setFloatable(false);
452 add(toolBar, BorderLayout.NORTH);
453 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
454 add(panelMain, BorderLayout.CENTER);
455 JScrollPane spListItems = new JScrollPane(listItems);
457 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
458 topPanel.add(spListItems, BorderLayout.CENTER);
459 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
460 listChannels.setModel(_channelModel);
462 listItems.addMouseListener(new java.awt.event.MouseAdapter()
464 public void mouseClicked(MouseEvent e)
466 listItems_mouseClicked(e);
469 _popupItems = _buildItemsPopupMenu();
470 _popupChannels = _buildChannelsPopupMenu();
471 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
473 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
475 listItems.addMouseListener(popupAdapter);
476 listItems.setCellRenderer(new ItemsRenderer());
477 lblChannels.setText("Channels");
480 private void postInit()
482 // clear the default hyperlink listener and replace with our own.
483 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
485 textDescription.removeHyperlinkListener(hll);
487 textDescription.addHyperlinkListener(new HyperlinkListener()
489 public void hyperlinkUpdate(HyperlinkEvent e)
491 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
493 Desktop.showUrl(e.getURL().toExternalForm());
498 listItems.addListSelectionListener(new ListSelectionListener()
500 public void valueChanged(ListSelectionEvent e)
502 if (e.getValueIsAdjusting() == false)
504 _itemsValueChanged(listItems);
508 listChannels.setSelectedIndex(1);
510 _updateToolbarButtons();
512 _itemTimer = new ItemReadTimer(listChannels, listItems);
513 _itemsValueChanged(listItems);
516 public class LaunchJvBrowserOnItem extends AbstractAction implements
519 JList _listItems = null;
521 public LaunchJvBrowserOnItem(JList listItems)
523 super("Open in Browser");
524 this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
525 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
526 _listItems = listItems;
529 public void actionPerformed(ActionEvent e)
531 Object o = _listItems.getSelectedValue();
532 if (o instanceof Item)
534 Item item = (Item) o;
536 _listItems.repaint();
538 Desktop.showUrl(item.getLink());
542 public void update(Object o)
545 if (_listItems == null || _listItems.getModel().getSize() == 0)
549 else if (_listItems.getSelectedIndex() == -1)
557 private JPopupMenu _buildItemsPopupMenu()
559 JPopupMenu popup = new JPopupMenu();
560 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
561 popup.addSeparator();
562 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
563 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
567 private JPopupMenu _buildChannelsPopupMenu()
569 JPopupMenu popup = new JPopupMenu();
570 popup.add(new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
571 popup.add(new JMenuItem(
572 new MarkChannelAsUnread(listChannels, listItems)));
576 private void initItems(Channel channel)
580 channel = new Channel();
582 if (!channel.isOpen() && channel.getURL() != null)
586 SimpleRSSParser.parse(channel);
587 } catch (Exception ex)
589 ex.printStackTrace();
592 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
594 Iterator iter = (channel.getItems() != null) ? channel.getItems()
595 .iterator() : Collections.EMPTY_LIST.iterator();
596 while (iter.hasNext())
598 itemsModel.addElement(iter.next());
600 if (itemsModel.getSize() > 0)
602 listItems.setSelectedIndex(0);
603 _itemsValueChanged(listItems);
605 setStatusBarText(channel.getURL());
609 private void _itemsValueChanged(JList itemList)
611 Item item = (Item) itemList.getSelectedValue();
614 if (itemList.getModel().getSize() > 0)
616 item = (Item) itemList.getModel().getElementAt(0);
624 itemList.setSelectedIndex(0);
628 if (_itemTimer != null)
630 // prefer a shorter delay than 5s
631 _itemTimer.setDelay(300);
633 _itemTimer.setLastItem(item);
634 final Item lastitem = item;
635 _itemTimer.addActionListener(new ActionListener()
639 public void actionPerformed(ActionEvent e)
646 setStatusBarText(item.getLink());
647 textDescription.setBlogText(item);
651 public void setStatusBarText(String text)
653 statusBar.setText(text);
656 private void _updateAllActions()
658 Iterator iter = _updatableActions.iterator();
659 while (iter.hasNext())
661 UpdatableAction action = (UpdatableAction) iter.next();
666 private void _updateToolbarButtons()
668 Map general = (Map) Main.getPreferences().get("general");
674 Component[] components = toolBar.getComponents();
675 for (int i = 0; i < components.length; i++)
677 Component component = components[i];
678 if (component instanceof JButton)
680 JButton button = (JButton) component;
681 if (Boolean.toString(false).equals(general.get("useToolBarText")))
683 // Remove the text if preferences state no toolbar text
686 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
688 button.setVerticalTextPosition(AbstractButton.BOTTOM);
689 button.setHorizontalTextPosition(AbstractButton.CENTER);
691 else if (Boolean.toString(true).equals(
692 general.get("radioTextRight")))
694 button.setVerticalTextPosition(AbstractButton.CENTER);
695 button.setHorizontalTextPosition(AbstractButton.RIGHT);
701 private void listItems_mouseClicked(MouseEvent e)
703 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
705 Item item = (Item) listItems.getSelectedValue();
708 if (_itemTimer != null)
713 Action action = new LaunchJvBrowserOnItem(listItems);
714 ActionEvent event = new ActionEvent(this,
715 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
716 action.actionPerformed(event);
721 * force the news panel to refresh
723 public void refreshNews()
727 initItems((Channel) _channelModel.getElementAt(0));
729 } catch (Exception x)
734 public static void main(String args[])
736 // this tests the detection of new news based on the last read date stored
737 // in jalview properties
738 jalview.bin.Cache.loadProperties(null);
739 jalview.bin.Cache.initLogger();
740 // test will advance read date each time
741 Calendar today = Calendar.getInstance(), lastread = Calendar
743 lastread.set(1983, 01, 01);
744 while (lastread.before(today))
746 Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
748 BlogReader me = new BlogReader();
749 System.out.println("Set last date to "
750 + jalview.bin.Cache.date_format.format(lastread.getTime()));
753 Cache.log.info("There is news to read.");
757 Cache.log.info("There is no new news.");
758 me.xf.setTitle("Testing : Last read is " + me.lastDate);
762 Cache.log.info("Waiting for closure.");
768 } catch (InterruptedException x)
772 } while (me.isVisible());
776 Cache.log.info("Still new news after reader displayed.");
778 if (lastread.getTime().before(me.lastDate))
780 Cache.log.info("The news was read.");
781 lastread.setTime(me.lastDate);
785 lastread.add(Calendar.MONTH, 1);
797 class ChannelsRenderer extends DefaultListCellRenderer
799 private final static Icon _icon = new ImageIcon(
800 Main.class.getResource("image/ComposeMail16.gif"));
802 public Component getListCellRendererComponent(JList list, Object value,
803 int index, boolean isSelected, boolean cellHasFocus)
805 JLabel component = (JLabel) super.getListCellRendererComponent(list,
806 value, index, isSelected, cellHasFocus);
807 component.setIcon(ChannelsRenderer._icon);
808 if (value instanceof Channel)
810 Channel channel = (Channel) value;
811 component.setText(channel.getTitle() + " ("
812 + channel.getUnreadItemCount() + ")");
813 component.setToolTipText(channel.getURL());
819 class ItemsRenderer extends DefaultListCellRenderer
821 private final static Icon _icon = new ImageIcon(
822 Main.class.getResource("image/ComposeMail16.gif"));
824 public Component getListCellRendererComponent(JList list, Object value,
825 int index, boolean isSelected, boolean cellHasFocus)
827 JLabel component = (JLabel) super.getListCellRendererComponent(list,
828 value, index, isSelected, cellHasFocus);
829 component.setIcon(ItemsRenderer._icon);
830 if (value instanceof Item)
832 Item item = (Item) value;
833 if (item.getPublishDate() != null)
835 component.setText(DateFormat.getDateInstance().format(
836 item.getPublishDate())
837 + " " + item.getTitle());
839 component.setToolTipText(item.getLink());
842 component.setFont(component.getFont().deriveFont(Font.BOLD));
846 component.setFont(component.getFont().deriveFont(Font.PLAIN));