2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3 * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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/>.
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.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;
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;
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;
82 * Blog reading window, adapted from JSwingReader's
83 * org.robsite.jswingreader.ui.MainWindow class
86 public class BlogReader extends JPanel
88 private JButton buttonRefresh = new JButton();
90 private JToolBar toolBar = new JToolBar();
92 private JLabel statusBar = new JLabel();
94 private JPanel panelMain = new JPanel();
96 private BorderLayout layoutMain = new BorderLayout();
98 private BorderLayout borderLayout1 = new BorderLayout();
100 private JPanel topPanel = new JPanel();
102 private JPanel bottomPanel = new JPanel();
104 private JSplitPane topBottomSplitPane = new JSplitPane();
106 private JList listItems = new JList(new DefaultListModel());
108 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
109 private BlogContentPane textDescription = new BlogContentPane();
111 // ADD IN JALVIEW BANNER FOR PRETTINESS
112 private BorderLayout borderLayout4 = new BorderLayout();
114 private BorderLayout borderLayout5 = new BorderLayout();
116 private ChannelListModel _channelModel = null;
118 private JList listChannels = new JList();
120 private Action exitAction = new Action()
124 public void actionPerformed(ActionEvent arg0)
134 parent.showNews(false);
140 public void setEnabled(boolean arg0)
146 public void removePropertyChangeListener(PropertyChangeListener arg0)
148 // TODO Auto-generated method stub
153 public void putValue(String arg0, Object arg1)
155 // TODO Auto-generated method stub
160 public boolean isEnabled()
162 // TODO Auto-generated method stub
167 public Object getValue(String arg0)
169 // TODO Auto-generated method stub
174 public void addPropertyChangeListener(PropertyChangeListener arg0)
176 // TODO Auto-generated method stub
181 private JFrame xf = null;
183 private JalviewDialog jd = null;
185 private JalviewDialog createDialog()
188 return jd = new JalviewDialog()
192 protected void raiseClosed()
196 Cache.log.info("News window closed.");
198 parent.showNews(false);
203 protected void okPressed()
205 // TODO Auto-generated method stub
210 protected void cancelPressed()
212 // TODO Auto-generated method stub
218 private JLabel lblChannels = new JLabel();
220 private List _updatableActions = new ArrayList();
222 private ItemReadTimer _itemTimer = null;
224 private JPopupMenu _popupItems = null;
226 private JPopupMenu _popupChannels = null;
228 private String lastm = "";
230 private boolean newsnew = false;
232 private Desktop parent = null;
239 // should we ignore fake gui events
240 private boolean updating = false;
242 public BlogReader(Desktop desktop)
244 Cache.log.info("Constructing news reader.");
247 _channelModel = new ChannelListModel();
248 // Construct our jalview news channel
249 Channel chan = new Channel();
250 chan.setURL(jalview.bin.Cache.getDefault(
252 jalview.bin.Cache.getDefault("www.jalview.org",
253 "http://www.jalview.org") + "/feeds/desktop/rss"));
255 _channelModel.addChannel(chan);
261 } catch (Exception e)
268 boolean setvisible = checkForNew(chan, true);
273 Cache.log.info("Will show jalview news automatically");
276 Cache.log.info("Completed construction of reader.");
281 * check if the news panel's container is visible
283 public boolean isVisible()
287 return xf != null && xf.isVisible();
289 return jd != null && jd.isVisible();
293 * display the container for the news panel
295 public void showNews()
297 final BlogReader me = this;
298 SwingUtilities.invokeLater(new Runnable()
303 Rectangle bounds = new Rectangle(5, 5, 550, 350);
307 xf.setContentPane(me);
308 xf.addWindowListener(new WindowAdapter()
310 public void windowClosing(WindowEvent e)
312 ActionEvent actionEvent = new ActionEvent(this,
313 ActionEvent.ACTION_FIRST, (String) exitAction
314 .getValue(Action.NAME));
315 exitAction.actionPerformed(actionEvent);
318 public void windowOpened(WindowEvent e)
322 me.setSize(new Dimension(550, 350));
328 bounds = new Rectangle(5, 5, 550, 350);
329 jd.initDialogFrame(me, false, false, "News from www.jalview.org",
330 bounds.width, bounds.height);
331 jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
332 Cache.log.info("Displaying news.");
340 * update hasnew flag and mark all new messages as unread.
342 private boolean checkForNew(Channel chan, boolean updateItems)
345 if (!updating || updateItems)
349 java.util.Date earliest = null;
352 earliest = new SimpleDateFormat("YYYY-MM-DD").parse(chan
353 .getHTTPLastModified());
354 } catch (Exception x)
358 if (chan != null && chan.getItems() != null)
360 Cache.log.debug("Scanning news items: newsnew=" + newsnew
361 + " and lastDate is " + lastDate);
362 for (Item i : (List<Item>) chan.getItems())
364 boolean isread = lastDate == null ? false
365 : (i.getPublishDate() != null && !lastDate.before(i
368 if (!updating || updateItems)
376 if (i.getPublishDate() != null && !i.isRead())
378 if (earliest == null || earliest.after(i.getPublishDate()))
380 earliest = i.getPublishDate();
385 if (!updateItems && !updating && lastDate == null)
392 java.util.Date lastDate = null;
394 private void loadLastM()
396 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
399 private void saveLastM(Item item)
403 if (item.getPublishDate() != null)
405 if (lastDate == null || item.getPublishDate().after(lastDate))
407 lastDate = item.getPublishDate();
411 if (_channelModel.getElementAt(0) != null)
413 checkForNew((Channel) _channelModel.getElementAt(0), false);
415 if (lastDate != null)
417 jalview.bin.Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
419 jalview.bin.Cache.log.info("Saved last read date as "
420 + jalview.bin.Cache.date_format.format(lastDate));
426 private void jbInit() throws Exception
428 setLayout(layoutMain);
429 panelMain.setLayout(borderLayout1);
430 topPanel.setLayout(borderLayout5);
431 bottomPanel.setLayout(borderLayout4);
432 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
433 topBottomSplitPane.setDividerLocation(100);
434 topBottomSplitPane.setTopComponent(topPanel);
435 topBottomSplitPane.setBottomComponent(bottomPanel);
436 JScrollPane spTextDescription = new JScrollPane(textDescription);
437 textDescription.setText("");
438 statusBar.setText(" [Status] ");
439 buttonRefresh.addActionListener(new ActionListener()
443 public void actionPerformed(ActionEvent e)
448 add(statusBar, BorderLayout.SOUTH);
449 toolBar.add(buttonRefresh);
450 toolBar.addSeparator();
451 JLabel about = new JLabel(
452 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
454 toolBar.setFloatable(false);
455 add(toolBar, BorderLayout.NORTH);
456 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
457 add(panelMain, BorderLayout.CENTER);
458 JScrollPane spListItems = new JScrollPane(listItems);
460 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
461 topPanel.add(spListItems, BorderLayout.CENTER);
462 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
463 listChannels.setModel(_channelModel);
465 listItems.addMouseListener(new java.awt.event.MouseAdapter()
467 public void mouseClicked(MouseEvent e)
469 listItems_mouseClicked(e);
472 _popupItems = _buildItemsPopupMenu();
473 _popupChannels = _buildChannelsPopupMenu();
474 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
476 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
478 listItems.addMouseListener(popupAdapter);
479 listItems.setCellRenderer(new ItemsRenderer());
480 lblChannels.setText("Channels");
483 private void postInit()
485 // clear the default hyperlink listener and replace with our own.
486 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
488 textDescription.removeHyperlinkListener(hll);
490 textDescription.addHyperlinkListener(new HyperlinkListener()
492 public void hyperlinkUpdate(HyperlinkEvent e)
494 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
496 Desktop.showUrl(e.getURL().toExternalForm());
501 listItems.addListSelectionListener(new ListSelectionListener()
503 public void valueChanged(ListSelectionEvent e)
505 if (e.getValueIsAdjusting() == false)
507 _itemsValueChanged(listItems);
511 listChannels.setSelectedIndex(1);
513 _updateToolbarButtons();
515 _itemTimer = new ItemReadTimer(listChannels, listItems);
516 _itemsValueChanged(listItems);
519 public class LaunchJvBrowserOnItem extends AbstractAction implements
522 JList _listItems = null;
524 public LaunchJvBrowserOnItem(JList listItems)
526 super("Open in Browser");
527 this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
528 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
529 _listItems = listItems;
532 public void actionPerformed(ActionEvent e)
534 Object o = _listItems.getSelectedValue();
535 if (o instanceof Item)
537 Item item = (Item) o;
539 _listItems.repaint();
541 Desktop.showUrl(item.getLink());
545 public void update(Object o)
548 if (_listItems == null || _listItems.getModel().getSize() == 0)
552 else if (_listItems.getSelectedIndex() == -1)
560 private JPopupMenu _buildItemsPopupMenu()
562 JPopupMenu popup = new JPopupMenu();
563 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
564 popup.addSeparator();
565 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
566 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
570 private JPopupMenu _buildChannelsPopupMenu()
572 JPopupMenu popup = new JPopupMenu();
573 popup.add(new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
574 popup.add(new JMenuItem(
575 new MarkChannelAsUnread(listChannels, listItems)));
579 private void initItems(Channel channel)
583 channel = new Channel();
585 if (!channel.isOpen() && channel.getURL() != null)
589 SimpleRSSParser.parse(channel);
590 } catch (Exception ex)
592 ex.printStackTrace();
595 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
597 Iterator iter = (channel.getItems() != null) ? channel.getItems()
598 .iterator() : Collections.EMPTY_LIST.iterator();
599 while (iter.hasNext())
601 itemsModel.addElement(iter.next());
603 if (itemsModel.getSize() > 0)
605 listItems.setSelectedIndex(0);
606 _itemsValueChanged(listItems);
608 setStatusBarText(channel.getURL());
612 private void _itemsValueChanged(JList itemList)
614 Item item = (Item) itemList.getSelectedValue();
617 if (itemList.getModel().getSize() > 0)
619 item = (Item) itemList.getModel().getElementAt(0);
627 itemList.setSelectedIndex(0);
631 if (_itemTimer != null)
633 // prefer a shorter delay than 5s
634 _itemTimer.setDelay(300);
636 _itemTimer.setLastItem(item);
637 final Item lastitem = item;
638 _itemTimer.addActionListener(new ActionListener()
642 public void actionPerformed(ActionEvent e)
649 setStatusBarText(item.getLink());
650 textDescription.setBlogText(item);
654 public void setStatusBarText(String text)
656 statusBar.setText(text);
659 private void _updateAllActions()
661 Iterator iter = _updatableActions.iterator();
662 while (iter.hasNext())
664 UpdatableAction action = (UpdatableAction) iter.next();
669 private void _updateToolbarButtons()
671 Map general = (Map) Main.getPreferences().get("general");
677 Component[] components = toolBar.getComponents();
678 for (int i = 0; i < components.length; i++)
680 Component component = components[i];
681 if (component instanceof JButton)
683 JButton button = (JButton) component;
684 if (Boolean.toString(false).equals(general.get("useToolBarText")))
686 // Remove the text if preferences state no toolbar text
689 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
691 button.setVerticalTextPosition(AbstractButton.BOTTOM);
692 button.setHorizontalTextPosition(AbstractButton.CENTER);
694 else if (Boolean.toString(true).equals(
695 general.get("radioTextRight")))
697 button.setVerticalTextPosition(AbstractButton.CENTER);
698 button.setHorizontalTextPosition(AbstractButton.RIGHT);
704 private void listItems_mouseClicked(MouseEvent e)
706 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
708 Item item = (Item) listItems.getSelectedValue();
711 if (_itemTimer != null)
716 Action action = new LaunchJvBrowserOnItem(listItems);
717 ActionEvent event = new ActionEvent(this,
718 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
719 action.actionPerformed(event);
724 * force the news panel to refresh
726 public void refreshNews()
730 initItems((Channel) _channelModel.getElementAt(0));
732 } catch (Exception x)
737 public static void main(String args[])
739 // this tests the detection of new news based on the last read date stored
740 // in jalview properties
741 jalview.bin.Cache.loadProperties(null);
742 jalview.bin.Cache.initLogger();
743 // test will advance read date each time
744 Calendar today = Calendar.getInstance(), lastread = Calendar
746 lastread.set(1983, 01, 01);
747 while (lastread.before(today))
749 Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
751 BlogReader me = new BlogReader();
752 System.out.println("Set last date to "
753 + jalview.bin.Cache.date_format.format(lastread.getTime()));
756 Cache.log.info("There is news to read.");
760 Cache.log.info("There is no new news.");
761 me.xf.setTitle("Testing : Last read is " + me.lastDate);
765 Cache.log.info("Waiting for closure.");
771 } catch (InterruptedException x)
775 } while (me.isVisible());
779 Cache.log.info("Still new news after reader displayed.");
781 if (lastread.getTime().before(me.lastDate))
783 Cache.log.info("The news was read.");
784 lastread.setTime(me.lastDate);
788 lastread.add(Calendar.MONTH, 1);
800 class ChannelsRenderer extends DefaultListCellRenderer
802 private final static Icon _icon = new ImageIcon(
803 Main.class.getResource("image/ComposeMail16.gif"));
805 public Component getListCellRendererComponent(JList list, Object value,
806 int index, boolean isSelected, boolean cellHasFocus)
808 JLabel component = (JLabel) super.getListCellRendererComponent(list,
809 value, index, isSelected, cellHasFocus);
810 component.setIcon(ChannelsRenderer._icon);
811 if (value instanceof Channel)
813 Channel channel = (Channel) value;
814 component.setText(channel.getTitle() + " ("
815 + channel.getUnreadItemCount() + ")");
816 component.setToolTipText(channel.getURL());
822 class ItemsRenderer extends DefaultListCellRenderer
824 private final static Icon _icon = new ImageIcon(
825 Main.class.getResource("image/ComposeMail16.gif"));
827 public Component getListCellRendererComponent(JList list, Object value,
828 int index, boolean isSelected, boolean cellHasFocus)
830 JLabel component = (JLabel) super.getListCellRendererComponent(list,
831 value, index, isSelected, cellHasFocus);
832 component.setIcon(ItemsRenderer._icon);
833 if (value instanceof Item)
835 Item item = (Item) value;
836 if (item.getPublishDate() != null)
838 component.setText(DateFormat.getDateInstance().format(
839 item.getPublishDate())
840 + " " + item.getTitle());
842 component.setToolTipText(item.getLink());
845 component.setFont(component.getFont().deriveFont(Font.BOLD));
849 component.setFont(component.getFont().deriveFont(Font.PLAIN));