2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3 * Copyright (C) 2014 The Jalview Authors
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
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.bin.Cache;
24 import jalview.util.MessageManager;
26 import java.awt.BorderLayout;
27 import java.awt.Component;
28 import java.awt.Dimension;
30 import java.awt.Rectangle;
31 import java.awt.Dialog.ModalExclusionType;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.KeyEvent;
35 import java.awt.event.MouseEvent;
36 import java.awt.event.WindowAdapter;
37 import java.awt.event.WindowEvent;
38 import java.beans.PropertyChangeListener;
39 import java.text.DateFormat;
40 import java.text.SimpleDateFormat;
41 import java.util.ArrayList;
42 import java.util.Calendar;
43 import java.util.Collections;
44 import java.util.Iterator;
45 import java.util.List;
48 import javax.swing.AbstractAction;
49 import javax.swing.AbstractButton;
50 import javax.swing.Action;
51 import javax.swing.DefaultListCellRenderer;
52 import javax.swing.DefaultListModel;
53 import javax.swing.Icon;
54 import javax.swing.ImageIcon;
55 import javax.swing.JButton;
56 import javax.swing.JFrame;
57 import javax.swing.JLabel;
58 import javax.swing.JList;
59 import javax.swing.JMenuItem;
60 import javax.swing.JPanel;
61 import javax.swing.JPopupMenu;
62 import javax.swing.JScrollPane;
63 import javax.swing.JSplitPane;
64 import javax.swing.JToolBar;
65 import javax.swing.ListSelectionModel;
66 import javax.swing.SwingUtilities;
67 import javax.swing.event.HyperlinkEvent;
68 import javax.swing.event.HyperlinkListener;
69 import javax.swing.event.ListSelectionEvent;
70 import javax.swing.event.ListSelectionListener;
72 import org.robsite.jswingreader.action.MarkChannelAsRead;
73 import org.robsite.jswingreader.action.MarkChannelAsUnread;
74 import org.robsite.jswingreader.action.MarkItemAsRead;
75 import org.robsite.jswingreader.action.MarkItemAsUnread;
76 import org.robsite.jswingreader.action.UpdatableAction;
77 import org.robsite.jswingreader.model.Channel;
78 import org.robsite.jswingreader.model.ChannelListModel;
79 import org.robsite.jswingreader.model.Item;
80 import org.robsite.jswingreader.model.SimpleRSSParser;
81 import org.robsite.jswingreader.ui.BlogContentPane;
82 import org.robsite.jswingreader.ui.ItemReadTimer;
83 import org.robsite.jswingreader.ui.Main;
84 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
87 * Blog reading window, adapted from JSwingReader's
88 * org.robsite.jswingreader.ui.MainWindow class
91 public class BlogReader extends JPanel
93 private JButton buttonRefresh = new JButton();
95 private JToolBar toolBar = new JToolBar();
97 private JLabel statusBar = new JLabel();
99 private JPanel panelMain = new JPanel();
101 private BorderLayout layoutMain = new BorderLayout();
103 private BorderLayout borderLayout1 = new BorderLayout();
105 private JPanel topPanel = new JPanel();
107 private JPanel bottomPanel = new JPanel();
109 private JSplitPane topBottomSplitPane = new JSplitPane();
111 private JList listItems = new JList(new DefaultListModel());
113 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
114 private BlogContentPane textDescription = new BlogContentPane();
116 // ADD IN JALVIEW BANNER FOR PRETTINESS
117 private BorderLayout borderLayout4 = new BorderLayout();
119 private BorderLayout borderLayout5 = new BorderLayout();
121 private ChannelListModel _channelModel = null;
123 private JList listChannels = new JList();
125 private Action exitAction = new Action()
129 public void actionPerformed(ActionEvent arg0)
139 parent.showNews(false);
145 public void setEnabled(boolean arg0)
151 public void removePropertyChangeListener(PropertyChangeListener arg0)
153 // TODO Auto-generated method stub
158 public void putValue(String arg0, Object arg1)
160 // TODO Auto-generated method stub
165 public boolean isEnabled()
167 // TODO Auto-generated method stub
172 public Object getValue(String arg0)
174 // TODO Auto-generated method stub
179 public void addPropertyChangeListener(PropertyChangeListener arg0)
181 // TODO Auto-generated method stub
186 private JFrame xf = null;
188 private JalviewDialog jd = null;
190 private JalviewDialog createDialog()
193 return jd = new JalviewDialog()
197 protected void raiseClosed()
201 Cache.log.info("News window closed.");
203 parent.showNews(false);
208 protected void okPressed()
210 // TODO Auto-generated method stub
215 protected void cancelPressed()
217 // TODO Auto-generated method stub
223 private JLabel lblChannels = new JLabel();
225 private List _updatableActions = new ArrayList();
227 private ItemReadTimer _itemTimer = null;
229 private JPopupMenu _popupItems = null;
231 private JPopupMenu _popupChannels = null;
233 private String lastm = "";
235 private boolean newsnew = false;
237 private Desktop parent = null;
244 // should we ignore fake gui events
245 private boolean updating = false;
247 public BlogReader(Desktop desktop)
249 Cache.log.info("Constructing news reader.");
252 _channelModel = new ChannelListModel();
253 // Construct our jalview news channel
254 Channel chan = new Channel();
255 chan.setURL(jalview.bin.Cache.getDefault(
257 jalview.bin.Cache.getDefault("www.jalview.org",
258 "http://www.jalview.org") + "/feeds/desktop/rss"));
260 _channelModel.addChannel(chan);
266 } catch (Exception e)
273 boolean setvisible = checkForNew(chan, true);
278 Cache.log.info("Will show jalview news automatically");
281 Cache.log.info("Completed construction of reader.");
286 * check if the news panel's container is visible
288 public boolean isVisible()
292 return xf != null && xf.isVisible();
294 return jd != null && jd.isVisible();
298 * display the container for the news panel
300 public void showNews()
302 final BlogReader me = this;
303 SwingUtilities.invokeLater(new Runnable()
308 Rectangle bounds = new Rectangle(5, 5, 550, 350);
312 xf.setContentPane(me);
313 xf.addWindowListener(new WindowAdapter()
315 public void windowClosing(WindowEvent e)
317 ActionEvent actionEvent = new ActionEvent(this,
318 ActionEvent.ACTION_FIRST, (String) exitAction
319 .getValue(Action.NAME));
320 exitAction.actionPerformed(actionEvent);
323 public void windowOpened(WindowEvent e)
327 me.setSize(new Dimension(550, 350));
333 bounds = new Rectangle(5, 5, 550, 350);
334 jd.initDialogFrame(me, false, false, MessageManager.getString("label.news_from_jalview"),
335 bounds.width, bounds.height);
336 jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
337 Cache.log.info("Displaying news.");
345 * update hasnew flag and mark all new messages as unread.
347 private boolean checkForNew(Channel chan, boolean updateItems)
350 if (!updating || updateItems)
354 java.util.Date earliest = null;
357 earliest = new SimpleDateFormat("YYYY-MM-DD").parse(chan
358 .getHTTPLastModified());
359 } catch (Exception x)
363 if (chan != null && chan.getItems() != null)
365 Cache.log.debug("Scanning news items: newsnew=" + newsnew
366 + " and lastDate is " + lastDate);
367 for (Item i : (List<Item>) chan.getItems())
369 boolean isread = lastDate == null ? false
370 : (i.getPublishDate() != null && !lastDate.before(i
373 if (!updating || updateItems)
381 if (i.getPublishDate() != null && !i.isRead())
383 if (earliest == null || earliest.after(i.getPublishDate()))
385 earliest = i.getPublishDate();
390 if (!updateItems && !updating && lastDate == null)
397 java.util.Date lastDate = null;
399 private void loadLastM()
401 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
404 private void saveLastM(Item item)
408 if (item.getPublishDate() != null)
410 if (lastDate == null || item.getPublishDate().after(lastDate))
412 lastDate = item.getPublishDate();
416 if (_channelModel.getElementAt(0) != null)
418 checkForNew((Channel) _channelModel.getElementAt(0), false);
420 if (lastDate != null)
422 jalview.bin.Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
424 jalview.bin.Cache.log.info("Saved last read date as "
425 + jalview.bin.Cache.date_format.format(lastDate));
431 private void jbInit() throws Exception
433 setLayout(layoutMain);
434 panelMain.setLayout(borderLayout1);
435 topPanel.setLayout(borderLayout5);
436 bottomPanel.setLayout(borderLayout4);
437 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
438 topBottomSplitPane.setDividerLocation(100);
439 topBottomSplitPane.setTopComponent(topPanel);
440 topBottomSplitPane.setBottomComponent(bottomPanel);
441 JScrollPane spTextDescription = new JScrollPane(textDescription);
442 textDescription.setText("");
443 statusBar.setText(new StringBuffer("[").append(MessageManager.getString("label.status")).append("]").toString());
444 buttonRefresh.addActionListener(new ActionListener()
448 public void actionPerformed(ActionEvent e)
453 add(statusBar, BorderLayout.SOUTH);
454 toolBar.add(buttonRefresh);
455 toolBar.addSeparator();
456 JLabel about = new JLabel(
457 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
459 toolBar.setFloatable(false);
460 add(toolBar, BorderLayout.NORTH);
461 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
462 add(panelMain, BorderLayout.CENTER);
463 JScrollPane spListItems = new JScrollPane(listItems);
465 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
466 topPanel.add(spListItems, BorderLayout.CENTER);
467 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
468 listChannels.setModel(_channelModel);
470 listItems.addMouseListener(new java.awt.event.MouseAdapter()
472 public void mouseClicked(MouseEvent e)
474 listItems_mouseClicked(e);
477 _popupItems = _buildItemsPopupMenu();
478 _popupChannels = _buildChannelsPopupMenu();
479 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
481 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
483 listItems.addMouseListener(popupAdapter);
484 listItems.setCellRenderer(new ItemsRenderer());
485 lblChannels.setText(MessageManager.getString("label.channels"));
488 private void postInit()
490 // clear the default hyperlink listener and replace with our own.
491 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
493 textDescription.removeHyperlinkListener(hll);
495 textDescription.addHyperlinkListener(new HyperlinkListener()
497 public void hyperlinkUpdate(HyperlinkEvent e)
499 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
501 Desktop.showUrl(e.getURL().toExternalForm());
506 listItems.addListSelectionListener(new ListSelectionListener()
508 public void valueChanged(ListSelectionEvent e)
510 if (e.getValueIsAdjusting() == false)
512 _itemsValueChanged(listItems);
516 listChannels.setSelectedIndex(1);
518 _updateToolbarButtons();
520 _itemTimer = new ItemReadTimer(listChannels, listItems);
521 _itemsValueChanged(listItems);
524 public class LaunchJvBrowserOnItem extends AbstractAction implements
527 JList _listItems = null;
529 public LaunchJvBrowserOnItem(JList listItems)
531 super("Open in Browser");
532 this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
533 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
534 _listItems = listItems;
537 public void actionPerformed(ActionEvent e)
539 Object o = _listItems.getSelectedValue();
540 if (o instanceof Item)
542 Item item = (Item) o;
544 _listItems.repaint();
546 Desktop.showUrl(item.getLink());
550 public void update(Object o)
553 if (_listItems == null || _listItems.getModel().getSize() == 0)
557 else if (_listItems.getSelectedIndex() == -1)
565 private JPopupMenu _buildItemsPopupMenu()
567 JPopupMenu popup = new JPopupMenu();
568 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
569 popup.addSeparator();
570 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
571 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
575 private JPopupMenu _buildChannelsPopupMenu()
577 JPopupMenu popup = new JPopupMenu();
578 popup.add(new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
579 popup.add(new JMenuItem(
580 new MarkChannelAsUnread(listChannels, listItems)));
584 private void initItems(Channel channel)
588 channel = new Channel();
590 if (!channel.isOpen() && channel.getURL() != null)
594 SimpleRSSParser.parse(channel);
595 } catch (Exception ex)
597 ex.printStackTrace();
600 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
602 Iterator iter = (channel.getItems() != null) ? channel.getItems()
603 .iterator() : Collections.EMPTY_LIST.iterator();
604 while (iter.hasNext())
606 itemsModel.addElement(iter.next());
608 if (itemsModel.getSize() > 0)
610 listItems.setSelectedIndex(0);
611 _itemsValueChanged(listItems);
613 setStatusBarText(channel.getURL());
617 private void _itemsValueChanged(JList itemList)
619 Item item = (Item) itemList.getSelectedValue();
622 if (itemList.getModel().getSize() > 0)
624 item = (Item) itemList.getModel().getElementAt(0);
632 itemList.setSelectedIndex(0);
636 if (_itemTimer != null)
638 // prefer a shorter delay than 5s
639 _itemTimer.setDelay(300);
641 _itemTimer.setLastItem(item);
642 final Item lastitem = item;
643 _itemTimer.addActionListener(new ActionListener()
647 public void actionPerformed(ActionEvent e)
654 setStatusBarText(item.getLink());
655 textDescription.setBlogText(item);
659 public void setStatusBarText(String text)
661 statusBar.setText(text);
664 private void _updateAllActions()
666 Iterator iter = _updatableActions.iterator();
667 while (iter.hasNext())
669 UpdatableAction action = (UpdatableAction) iter.next();
674 private void _updateToolbarButtons()
676 Map general = (Map) Main.getPreferences().get("general");
682 Component[] components = toolBar.getComponents();
683 for (int i = 0; i < components.length; i++)
685 Component component = components[i];
686 if (component instanceof JButton)
688 JButton button = (JButton) component;
689 if (Boolean.toString(false).equals(general.get("useToolBarText")))
691 // Remove the text if preferences state no toolbar text
694 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
696 button.setVerticalTextPosition(AbstractButton.BOTTOM);
697 button.setHorizontalTextPosition(AbstractButton.CENTER);
699 else if (Boolean.toString(true).equals(
700 general.get("radioTextRight")))
702 button.setVerticalTextPosition(AbstractButton.CENTER);
703 button.setHorizontalTextPosition(AbstractButton.RIGHT);
709 private void listItems_mouseClicked(MouseEvent e)
711 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
713 Item item = (Item) listItems.getSelectedValue();
716 if (_itemTimer != null)
721 Action action = new LaunchJvBrowserOnItem(listItems);
722 ActionEvent event = new ActionEvent(this,
723 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
724 action.actionPerformed(event);
729 * force the news panel to refresh
731 public void refreshNews()
735 initItems((Channel) _channelModel.getElementAt(0));
737 } catch (Exception x)
742 public static void main(String args[])
744 // this tests the detection of new news based on the last read date stored
745 // in jalview properties
746 jalview.bin.Cache.loadProperties(null);
747 jalview.bin.Cache.initLogger();
748 // test will advance read date each time
749 Calendar today = Calendar.getInstance(), lastread = Calendar
751 lastread.set(1983, 01, 01);
752 while (lastread.before(today))
754 Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
756 BlogReader me = new BlogReader();
757 System.out.println("Set last date to "
758 + jalview.bin.Cache.date_format.format(lastread.getTime()));
761 Cache.log.info("There is news to read.");
765 Cache.log.info("There is no new news.");
766 me.xf.setTitle("Testing : Last read is " + me.lastDate);
770 Cache.log.info("Waiting for closure.");
776 } catch (InterruptedException x)
780 } while (me.isVisible());
784 Cache.log.info("Still new news after reader displayed.");
786 if (lastread.getTime().before(me.lastDate))
788 Cache.log.info("The news was read.");
789 lastread.setTime(me.lastDate);
793 lastread.add(Calendar.MONTH, 1);
805 class ChannelsRenderer extends DefaultListCellRenderer
807 private final static Icon _icon = new ImageIcon(
808 Main.class.getResource("image/ComposeMail16.gif"));
810 public Component getListCellRendererComponent(JList list, Object value,
811 int index, boolean isSelected, boolean cellHasFocus)
813 JLabel component = (JLabel) super.getListCellRendererComponent(list,
814 value, index, isSelected, cellHasFocus);
815 component.setIcon(ChannelsRenderer._icon);
816 if (value instanceof Channel)
818 Channel channel = (Channel) value;
820 .setText(MessageManager.formatMessage(
821 "label.channel_title_item_count",
825 Integer.valueOf(channel.getUnreadItemCount())
827 component.setToolTipText(channel.getURL());
833 class ItemsRenderer extends DefaultListCellRenderer
835 private final static Icon _icon = new ImageIcon(
836 Main.class.getResource("image/ComposeMail16.gif"));
838 public Component getListCellRendererComponent(JList list, Object value,
839 int index, boolean isSelected, boolean cellHasFocus)
841 JLabel component = (JLabel) super.getListCellRendererComponent(list,
842 value, index, isSelected, cellHasFocus);
843 component.setIcon(ItemsRenderer._icon);
844 if (value instanceof Item)
846 Item item = (Item) value;
847 if (item.getPublishDate() != null)
849 component.setText(MessageManager.formatMessage(
850 "label.blog_item_published_on_date",
854 .getDateInstance(DateFormat.LONG,
855 MessageManager.getLocale())
856 .format(item.getPublishDate()).toString(),
859 component.setToolTipText(item.getLink());
862 component.setFont(component.getFont().deriveFont(Font.BOLD));
866 component.setFont(component.getFont().deriveFont(Font.PLAIN));