/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.gui; import jalview.bin.Cache; import jalview.util.MessageManager; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dialog.ModalExclusionType; import java.awt.Dimension; import java.awt.Font; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeListener; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JToolBar; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.robsite.jswingreader.action.MarkChannelAsRead; import org.robsite.jswingreader.action.MarkChannelAsUnread; import org.robsite.jswingreader.action.MarkItemAsRead; import org.robsite.jswingreader.action.MarkItemAsUnread; import org.robsite.jswingreader.action.UpdatableAction; import org.robsite.jswingreader.model.Channel; import org.robsite.jswingreader.model.ChannelListModel; import org.robsite.jswingreader.model.Item; import org.robsite.jswingreader.model.SimpleRSSParser; import org.robsite.jswingreader.ui.BlogContentPane; import org.robsite.jswingreader.ui.ItemReadTimer; import org.robsite.jswingreader.ui.Main; import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter; /** * Blog reading window, adapted from JSwingReader's * org.robsite.jswingreader.ui.MainWindow class */ public class BlogReader extends JPanel { private JButton buttonRefresh = new JButton(); private JToolBar toolBar = new JToolBar(); private JLabel statusBar = new JLabel(); private JPanel panelMain = new JPanel(); private BorderLayout layoutMain = new BorderLayout(); private BorderLayout borderLayout1 = new BorderLayout(); private JPanel topPanel = new JPanel(); private JPanel bottomPanel = new JPanel(); private JSplitPane topBottomSplitPane = new JSplitPane(); private JList listItems = new JList(new DefaultListModel()); // SWITCH IN JALVIEW HTML VIEWER PANE HERE private BlogContentPane textDescription = new BlogContentPane(); // ADD IN JALVIEW BANNER FOR PRETTINESS private BorderLayout borderLayout4 = new BorderLayout(); private BorderLayout borderLayout5 = new BorderLayout(); private ChannelListModel _channelModel = null; private JList listChannels = new JList(); private Action exitAction = new Action() { @Override public void actionPerformed(ActionEvent arg0) { if (xf != null) { xf.dispose(); } xf = null; jd = null; if (parent != null) { parent.showNews(false); } } @Override public void setEnabled(boolean arg0) { } @Override public void removePropertyChangeListener(PropertyChangeListener arg0) { // TODO Auto-generated method stub } @Override public void putValue(String arg0, Object arg1) { // TODO Auto-generated method stub } @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } @Override public Object getValue(String arg0) { // TODO Auto-generated method stub return null; } @Override public void addPropertyChangeListener(PropertyChangeListener arg0) { // TODO Auto-generated method stub } }; private JFrame xf = null; private JalviewDialog jd = null; private JalviewDialog createDialog() { return jd = new JalviewDialog() { @Override protected void raiseClosed() { if (parent != null) { Cache.log.debug("News window closed."); jd = null; parent.showNews(false); } } @Override protected void okPressed() { // TODO Auto-generated method stub } @Override protected void cancelPressed() { // TODO Auto-generated method stub } }; }; private JLabel lblChannels = new JLabel(); private List _updatableActions = new ArrayList(); private ItemReadTimer _itemTimer = null; private JPopupMenu _popupItems = null; private JPopupMenu _popupChannels = null; private String lastm = ""; private boolean newsnew = false; private Desktop parent = null; BlogReader() { this(null); } // should we ignore fake gui events private boolean updating = false; public BlogReader(Desktop desktop) { Cache.log.debug("Constructing news reader."); parent = desktop; _channelModel = new ChannelListModel(); // Construct our jalview news channel Channel chan = new Channel(); chan.setURL( jalview.bin.Cache.getDefault("JALVIEW_NEWS_RSS", jalview.bin.Cache.getDefault("www.jalview.org", "https://www.jalview.org") + "/feeds/desktop/rss")); loadLastM(); _channelModel.addChannel(chan); updating = true; try { jbInit(); postInit(); } catch (Exception e) { e.printStackTrace(); } initItems(chan); updating = false; if (!Cache.getDefault("NONEWS", false)) { boolean setvisible = checkForNew(chan, true); if (setvisible) { Cache.log.debug("Will show jalview news automatically"); showNews(); } } Cache.log.debug("Completed construction of reader."); } /** * check if the news panel's container is visible */ @Override public boolean isVisible() { if (parent == null) { return xf != null && xf.isVisible(); } return jd != null && jd.isVisible(); } /** * display the container for the news panel */ public void showNews() { final BlogReader me = this; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Rectangle bounds = new Rectangle(5, 5, 550, 350); if (parent == null) { xf = new JFrame(); xf.setContentPane(me); xf.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { ActionEvent actionEvent = new ActionEvent(this, ActionEvent.ACTION_FIRST, (String) exitAction.getValue(Action.NAME)); exitAction.actionPerformed(actionEvent); } @Override public void windowOpened(WindowEvent e) { } }); me.setSize(new Dimension(550, 350)); xf.setVisible(true); } else { createDialog(); bounds = new Rectangle(5, 5, 550, 350); jd.initDialogFrame(me, false, false, MessageManager.getString("label.news_from_jalview"), bounds.width, bounds.height); jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE); Cache.log.debug("Displaying news."); jd.waitForInput(); } } }); } /** * update hasnew flag and mark all new messages as unread. */ private boolean checkForNew(Channel chan, boolean updateItems) { if (!updating || updateItems) { newsnew = false; } java.util.Date earliest = null; try { earliest = new SimpleDateFormat("YYYY-MM-DD") .parse(chan.getHTTPLastModified()); } catch (Exception x) { } ; if (chan != null && chan.getItems() != null) { Cache.log.debug("Scanning news items: newsnew=" + newsnew + " and lastDate is " + lastDate); for (Item i : (List) chan.getItems()) { Date published = i.getPublishDate(); boolean isread = lastDate == null ? false : (published != null && !lastDate.before(published)); if (!updating || updateItems) { newsnew |= !isread; } if (updateItems) { i.setRead(isread); } if (published != null && !i.isRead()) { if (earliest == null || earliest.after(published)) { earliest = published; } } } } if (!updateItems && !updating && lastDate == null) { lastDate = earliest; } return newsnew; } java.util.Date lastDate = null; private void loadLastM() { lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED"); } private void saveLastM(Item item) { if (item != null) { if (item.getPublishDate() != null) { if (lastDate == null || item.getPublishDate().after(lastDate)) { lastDate = item.getPublishDate(); } } if (_channelModel.getElementAt(0) != null) { checkForNew((Channel) _channelModel.getElementAt(0), false); } if (lastDate != null) { String formatted = Cache .setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", lastDate); Cache.log.debug("Saved last read date as " + formatted); } } } private void jbInit() throws Exception { setLayout(layoutMain); panelMain.setLayout(borderLayout1); topPanel.setLayout(borderLayout5); bottomPanel.setLayout(borderLayout4); topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); topBottomSplitPane.setDividerLocation(100); topBottomSplitPane.setTopComponent(topPanel); topBottomSplitPane.setBottomComponent(bottomPanel); JScrollPane spTextDescription = new JScrollPane(textDescription); textDescription.setText(""); statusBar.setText(new StringBuffer("[") .append(MessageManager.getString("label.status")).append("]") .toString()); buttonRefresh.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { refreshNews(); } }); add(statusBar, BorderLayout.SOUTH); toolBar.add(buttonRefresh); toolBar.addSeparator(); JLabel about = new JLabel( "brought to you by JSwingReader (jswingreader.sourceforge.net)"); toolBar.add(about); toolBar.setFloatable(false); add(toolBar, BorderLayout.NORTH); panelMain.add(topBottomSplitPane, BorderLayout.CENTER); add(panelMain, BorderLayout.CENTER); JScrollPane spListItems = new JScrollPane(listItems); listItems .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); topPanel.add(spListItems, BorderLayout.CENTER); bottomPanel.add(spTextDescription, BorderLayout.CENTER); listChannels.setModel(_channelModel); listItems.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { listItems_mouseClicked(e); } }); _popupItems = _buildItemsPopupMenu(); _popupChannels = _buildChannelsPopupMenu(); ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter( _popupItems); ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter( _popupChannels); listItems.addMouseListener(popupAdapter); listItems.setCellRenderer(new ItemsRenderer()); lblChannels.setText(MessageManager.getString("label.channels")); } private void postInit() { // clear the default hyperlink listener and replace with our own. for (HyperlinkListener hll : textDescription.getHyperlinkListeners()) { textDescription.removeHyperlinkListener(hll); } textDescription.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { Desktop.showUrl(e.getURL().toExternalForm()); } } }); listItems.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting() == false) { _itemsValueChanged(listItems); } } }); listChannels.setSelectedIndex(1); _updateAllActions(); _updateToolbarButtons(); _itemTimer = new ItemReadTimer(listChannels, listItems); _itemsValueChanged(listItems); } public class LaunchJvBrowserOnItem extends AbstractAction implements UpdatableAction { JList _listItems = null; public LaunchJvBrowserOnItem(JList listItems) { super("Open in Browser"); this.putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O)); this.putValue(Action.LONG_DESCRIPTION, "Open in Browser"); _listItems = listItems; } @Override public void actionPerformed(ActionEvent e) { Object o = _listItems.getSelectedValue(); if (o instanceof Item) { Item item = (Item) o; item.setRead(true); _listItems.repaint(); Desktop.showUrl(item.getLink()); } } @Override public void update(Object o) { setEnabled(true); if (_listItems == null || _listItems.getModel().getSize() == 0) { setEnabled(false); } else if (_listItems.getSelectedIndex() == -1) { setEnabled(false); } } } private JPopupMenu _buildItemsPopupMenu() { JPopupMenu popup = new JPopupMenu(); popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems))); popup.addSeparator(); popup.add(new JMenuItem(new MarkItemAsRead(listItems))); popup.add(new JMenuItem(new MarkItemAsUnread(listItems))); return popup; } private JPopupMenu _buildChannelsPopupMenu() { JPopupMenu popup = new JPopupMenu(); popup.add( new JMenuItem(new MarkChannelAsRead(listChannels, listItems))); popup.add(new JMenuItem( new MarkChannelAsUnread(listChannels, listItems))); return popup; } private void initItems(Channel channel) { if (channel == null) { channel = new Channel(); } if (!channel.isOpen() && channel.getURL() != null) { try { SimpleRSSParser.parse(channel); } catch (Exception ex) { ex.printStackTrace(); } } DefaultListModel itemsModel = (DefaultListModel) listItems.getModel(); itemsModel.clear(); Iterator iter = (channel.getItems() != null) ? channel.getItems().iterator() : Collections.EMPTY_LIST.iterator(); while (iter.hasNext()) { itemsModel.addElement(iter.next()); } if (itemsModel.getSize() > 0) { listItems.setSelectedIndex(0); _itemsValueChanged(listItems); } setStatusBarText(channel.getURL()); _updateAllActions(); } private void _itemsValueChanged(JList itemList) { Item item = (Item) itemList.getSelectedValue(); if (item == null) { if (itemList.getModel().getSize() > 0) { item = (Item) itemList.getModel().getElementAt(0); } if (item == null) { item = new Item(); } else { itemList.setSelectedIndex(0); } } if (_itemTimer != null) { // prefer a shorter delay than 5s _itemTimer.setDelay(300); _itemTimer.start(); _itemTimer.setLastItem(item); final Item lastitem = item; _itemTimer.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveLastM(lastitem); } }); } setStatusBarText(item.getLink()); textDescription.setBlogText(item); _updateAllActions(); } public void setStatusBarText(String text) { statusBar.setText(text); } private void _updateAllActions() { Iterator iter = _updatableActions.iterator(); while (iter.hasNext()) { UpdatableAction action = (UpdatableAction) iter.next(); action.update(this); } } private void _updateToolbarButtons() { Map general = (Map) Main.getPreferences().get("general"); if (general == null) { return; } Component[] components = toolBar.getComponents(); for (int i = 0; i < components.length; i++) { Component component = components[i]; if (component instanceof JButton) { JButton button = (JButton) component; if (Boolean.toString(false).equals(general.get("useToolBarText"))) { // Remove the text if preferences state no toolbar text button.setText(""); } if (Boolean.toString(true).equals(general.get("radioTextBelow"))) { button.setVerticalTextPosition(AbstractButton.BOTTOM); button.setHorizontalTextPosition(AbstractButton.CENTER); } else if (Boolean.toString(true) .equals(general.get("radioTextRight"))) { button.setVerticalTextPosition(AbstractButton.CENTER); button.setHorizontalTextPosition(AbstractButton.RIGHT); } } } } private void listItems_mouseClicked(MouseEvent e) { if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON) { Item item = (Item) listItems.getSelectedValue(); item.setRead(true); saveLastM(item); if (_itemTimer != null) { _itemTimer.stop(); } Action action = new LaunchJvBrowserOnItem(listItems); ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem"); action.actionPerformed(event); } } /** * force the news panel to refresh */ public void refreshNews() { try { initItems((Channel) _channelModel.getElementAt(0)); } catch (Exception x) { } } public static void main(String args[]) { // this tests the detection of new news based on the last read date stored // in jalview properties jalview.bin.Cache.loadProperties(null); jalview.bin.Cache.initLogger(); // test will advance read date each time Calendar today = Calendar.getInstance(), lastread = Calendar.getInstance(); lastread.set(1983, 01, 01); while (lastread.before(today)) { String formattedDate = Cache.setDateProperty( "JALVIEW_NEWS_RSS_LASTMODIFIED", lastread.getTime()); BlogReader me = new BlogReader(); System.out.println("Set last date to " + formattedDate); if (me.isNewsNew()) { Cache.log.debug("There is news to read."); } else { Cache.log.debug("There is no new news."); me.xf.setTitle("Testing : Last read is " + me.lastDate); me.showNews(); me.xf.toFront(); } Cache.log.debug("Waiting for closure."); do { try { Thread.sleep(300); } catch (InterruptedException x) { } ; } while (me.isVisible()); if (me.isNewsNew()) { Cache.log.debug("Still new news after reader displayed."); } if (lastread.getTime().before(me.lastDate)) { Cache.log.debug("The news was read."); lastread.setTime(me.lastDate); } else { lastread.add(Calendar.MONTH, 1); } } } boolean isNewsNew() { return newsnew; } } class ChannelsRenderer extends DefaultListCellRenderer { private final static Icon _icon = new ImageIcon( Main.class.getResource("image/ComposeMail16.gif")); @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel component = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); component.setIcon(ChannelsRenderer._icon); if (value instanceof Channel) { Channel channel = (Channel) value; component.setText(MessageManager .formatMessage("label.channel_title_item_count", new String[] { channel.getTitle(), Integer .valueOf(channel.getUnreadItemCount()).toString() })); component.setToolTipText(channel.getURL()); } return component; } } class ItemsRenderer extends DefaultListCellRenderer { private final static Icon _icon = new ImageIcon( Main.class.getResource("image/ComposeMail16.gif")); @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel component = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); component.setIcon(ItemsRenderer._icon); if (value instanceof Item) { Item item = (Item) value; if (item.getPublishDate() != null) { component.setText(MessageManager.formatMessage( "label.blog_item_published_on_date", new String[] { DateFormat .getDateInstance(DateFormat.LONG, MessageManager.getLocale()) .format(item.getPublishDate()).toString(), item.getTitle() })); } component.setToolTipText(item.getLink()); if (!item.isRead()) { component.setFont(component.getFont().deriveFont(Font.BOLD)); } else { component.setFont(component.getFont().deriveFont(Font.PLAIN)); } } return component; } }