2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.1)
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 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 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 import jalview.bin.Cache;
22 import jalview.util.MessageManager;
24 import java.awt.BorderLayout;
25 import java.awt.Component;
26 import java.awt.Dimension;
28 import java.awt.Rectangle;
29 import java.awt.Dialog.ModalExclusionType;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.awt.event.KeyEvent;
33 import java.awt.event.MouseEvent;
34 import java.awt.event.WindowAdapter;
35 import java.awt.event.WindowEvent;
36 import java.beans.PropertyChangeListener;
37 import java.text.DateFormat;
38 import java.text.SimpleDateFormat;
39 import java.util.ArrayList;
40 import java.util.Calendar;
41 import java.util.Collections;
42 import java.util.Iterator;
43 import java.util.List;
46 import javax.swing.AbstractAction;
47 import javax.swing.AbstractButton;
48 import javax.swing.Action;
49 import javax.swing.DefaultListCellRenderer;
50 import javax.swing.DefaultListModel;
51 import javax.swing.Icon;
52 import javax.swing.ImageIcon;
53 import javax.swing.JButton;
54 import javax.swing.JFrame;
55 import javax.swing.JLabel;
56 import javax.swing.JList;
57 import javax.swing.JMenuItem;
58 import javax.swing.JPanel;
59 import javax.swing.JPopupMenu;
60 import javax.swing.JScrollPane;
61 import javax.swing.JSplitPane;
62 import javax.swing.JToolBar;
63 import javax.swing.ListSelectionModel;
64 import javax.swing.SwingUtilities;
65 import javax.swing.event.HyperlinkEvent;
66 import javax.swing.event.HyperlinkListener;
67 import javax.swing.event.ListSelectionEvent;
68 import javax.swing.event.ListSelectionListener;
70 import org.robsite.jswingreader.action.MarkChannelAsRead;
71 import org.robsite.jswingreader.action.MarkChannelAsUnread;
72 import org.robsite.jswingreader.action.MarkItemAsRead;
73 import org.robsite.jswingreader.action.MarkItemAsUnread;
74 import org.robsite.jswingreader.action.UpdatableAction;
75 import org.robsite.jswingreader.model.Channel;
76 import org.robsite.jswingreader.model.ChannelListModel;
77 import org.robsite.jswingreader.model.Item;
78 import org.robsite.jswingreader.model.SimpleRSSParser;
79 import org.robsite.jswingreader.ui.BlogContentPane;
80 import org.robsite.jswingreader.ui.ItemReadTimer;
81 import org.robsite.jswingreader.ui.Main;
82 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
85 * Blog reading window, adapted from JSwingReader's
86 * org.robsite.jswingreader.ui.MainWindow class
89 public class BlogReader extends JPanel
91 private JButton buttonRefresh = new JButton();
93 private JToolBar toolBar = new JToolBar();
95 private JLabel statusBar = new JLabel();
97 private JPanel panelMain = new JPanel();
99 private BorderLayout layoutMain = new BorderLayout();
101 private BorderLayout borderLayout1 = new BorderLayout();
103 private JPanel topPanel = new JPanel();
105 private JPanel bottomPanel = new JPanel();
107 private JSplitPane topBottomSplitPane = new JSplitPane();
109 private JList listItems = new JList(new DefaultListModel());
111 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
112 private BlogContentPane textDescription = new BlogContentPane();
114 // ADD IN JALVIEW BANNER FOR PRETTINESS
115 private BorderLayout borderLayout4 = new BorderLayout();
117 private BorderLayout borderLayout5 = new BorderLayout();
119 private ChannelListModel _channelModel = null;
121 private JList listChannels = new JList();
123 private Action exitAction = new Action()
127 public void actionPerformed(ActionEvent arg0)
137 parent.showNews(false);
143 public void setEnabled(boolean arg0)
149 public void removePropertyChangeListener(PropertyChangeListener arg0)
151 // TODO Auto-generated method stub
156 public void putValue(String arg0, Object arg1)
158 // TODO Auto-generated method stub
163 public boolean isEnabled()
165 // TODO Auto-generated method stub
170 public Object getValue(String arg0)
172 // TODO Auto-generated method stub
177 public void addPropertyChangeListener(PropertyChangeListener arg0)
179 // TODO Auto-generated method stub
184 private JFrame xf = null;
186 private JalviewDialog jd = null;
188 private JalviewDialog createDialog()
191 return jd = new JalviewDialog()
195 protected void raiseClosed()
199 Cache.log.info("News window closed.");
201 parent.showNews(false);
206 protected void okPressed()
208 // TODO Auto-generated method stub
213 protected void cancelPressed()
215 // TODO Auto-generated method stub
221 private JLabel lblChannels = new JLabel();
223 private List _updatableActions = new ArrayList();
225 private ItemReadTimer _itemTimer = null;
227 private JPopupMenu _popupItems = null;
229 private JPopupMenu _popupChannels = null;
231 private String lastm = "";
233 private boolean newsnew = false;
235 private Desktop parent = null;
242 // should we ignore fake gui events
243 private boolean updating = false;
245 public BlogReader(Desktop desktop)
247 Cache.log.info("Constructing news reader.");
250 _channelModel = new ChannelListModel();
251 // Construct our jalview news channel
252 Channel chan = new Channel();
253 chan.setURL(jalview.bin.Cache.getDefault(
255 jalview.bin.Cache.getDefault("www.jalview.org",
256 "http://www.jalview.org") + "/feeds/desktop/rss"));
258 _channelModel.addChannel(chan);
264 } catch (Exception e)
271 boolean setvisible = checkForNew(chan, true);
276 Cache.log.info("Will show jalview news automatically");
279 Cache.log.info("Completed construction of reader.");
284 * check if the news panel's container is visible
286 public boolean isVisible()
290 return xf != null && xf.isVisible();
292 return jd != null && jd.isVisible();
296 * display the container for the news panel
298 public void showNews()
300 final BlogReader me = this;
301 SwingUtilities.invokeLater(new Runnable()
306 Rectangle bounds = new Rectangle(5, 5, 550, 350);
310 xf.setContentPane(me);
311 xf.addWindowListener(new WindowAdapter()
313 public void windowClosing(WindowEvent e)
315 ActionEvent actionEvent = new ActionEvent(this,
316 ActionEvent.ACTION_FIRST, (String) exitAction
317 .getValue(Action.NAME));
318 exitAction.actionPerformed(actionEvent);
321 public void windowOpened(WindowEvent e)
325 me.setSize(new Dimension(550, 350));
331 bounds = new Rectangle(5, 5, 550, 350);
332 jd.initDialogFrame(me, false, false, "News from www.jalview.org",
333 bounds.width, bounds.height);
334 jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
335 Cache.log.info("Displaying news.");
343 * update hasnew flag and mark all new messages as unread.
345 private boolean checkForNew(Channel chan, boolean updateItems)
348 if (!updating || updateItems)
352 java.util.Date earliest = null;
355 earliest = new SimpleDateFormat("YYYY-MM-DD").parse(chan
356 .getHTTPLastModified());
357 } catch (Exception x)
361 if (chan != null && chan.getItems() != null)
363 Cache.log.debug("Scanning news items: newsnew=" + newsnew
364 + " and lastDate is " + lastDate);
365 for (Item i : (List<Item>) chan.getItems())
367 boolean isread = lastDate == null ? false
368 : (i.getPublishDate() != null && !lastDate.before(i
371 if (!updating || updateItems)
379 if (i.getPublishDate() != null && !i.isRead())
381 if (earliest == null || earliest.after(i.getPublishDate()))
383 earliest = i.getPublishDate();
388 if (!updateItems && !updating && lastDate == null)
395 java.util.Date lastDate = null;
397 private void loadLastM()
399 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
402 private void saveLastM(Item item)
406 if (item.getPublishDate() != null)
408 if (lastDate == null || item.getPublishDate().after(lastDate))
410 lastDate = item.getPublishDate();
414 if (_channelModel.getElementAt(0) != null)
416 checkForNew((Channel) _channelModel.getElementAt(0), false);
418 if (lastDate != null)
420 jalview.bin.Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
422 jalview.bin.Cache.log.info("Saved last read date as "
423 + jalview.bin.Cache.date_format.format(lastDate));
429 private void jbInit() throws Exception
431 setLayout(layoutMain);
432 panelMain.setLayout(borderLayout1);
433 topPanel.setLayout(borderLayout5);
434 bottomPanel.setLayout(borderLayout4);
435 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
436 topBottomSplitPane.setDividerLocation(100);
437 topBottomSplitPane.setTopComponent(topPanel);
438 topBottomSplitPane.setBottomComponent(bottomPanel);
439 JScrollPane spTextDescription = new JScrollPane(textDescription);
440 textDescription.setText("");
441 statusBar.setText(MessageManager.getString("label.status"));
442 buttonRefresh.addActionListener(new ActionListener()
446 public void actionPerformed(ActionEvent e)
451 add(statusBar, BorderLayout.SOUTH);
452 toolBar.add(buttonRefresh);
453 toolBar.addSeparator();
454 JLabel about = new JLabel(
455 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
457 toolBar.setFloatable(false);
458 add(toolBar, BorderLayout.NORTH);
459 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
460 add(panelMain, BorderLayout.CENTER);
461 JScrollPane spListItems = new JScrollPane(listItems);
463 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
464 topPanel.add(spListItems, BorderLayout.CENTER);
465 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
466 listChannels.setModel(_channelModel);
468 listItems.addMouseListener(new java.awt.event.MouseAdapter()
470 public void mouseClicked(MouseEvent e)
472 listItems_mouseClicked(e);
475 _popupItems = _buildItemsPopupMenu();
476 _popupChannels = _buildChannelsPopupMenu();
477 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
479 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
481 listItems.addMouseListener(popupAdapter);
482 listItems.setCellRenderer(new ItemsRenderer());
483 lblChannels.setText(MessageManager.getString("label.channels"));
486 private void postInit()
488 // clear the default hyperlink listener and replace with our own.
489 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
491 textDescription.removeHyperlinkListener(hll);
493 textDescription.addHyperlinkListener(new HyperlinkListener()
495 public void hyperlinkUpdate(HyperlinkEvent e)
497 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
499 Desktop.showUrl(e.getURL().toExternalForm());
504 listItems.addListSelectionListener(new ListSelectionListener()
506 public void valueChanged(ListSelectionEvent e)
508 if (e.getValueIsAdjusting() == false)
510 _itemsValueChanged(listItems);
514 listChannels.setSelectedIndex(1);
516 _updateToolbarButtons();
518 _itemTimer = new ItemReadTimer(listChannels, listItems);
519 _itemsValueChanged(listItems);
522 public class LaunchJvBrowserOnItem extends AbstractAction implements
525 JList _listItems = null;
527 public LaunchJvBrowserOnItem(JList listItems)
529 super("Open in Browser");
530 this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
531 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
532 _listItems = listItems;
535 public void actionPerformed(ActionEvent e)
537 Object o = _listItems.getSelectedValue();
538 if (o instanceof Item)
540 Item item = (Item) o;
542 _listItems.repaint();
544 Desktop.showUrl(item.getLink());
548 public void update(Object o)
551 if (_listItems == null || _listItems.getModel().getSize() == 0)
555 else if (_listItems.getSelectedIndex() == -1)
563 private JPopupMenu _buildItemsPopupMenu()
565 JPopupMenu popup = new JPopupMenu();
566 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
567 popup.addSeparator();
568 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
569 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
573 private JPopupMenu _buildChannelsPopupMenu()
575 JPopupMenu popup = new JPopupMenu();
576 popup.add(new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
577 popup.add(new JMenuItem(
578 new MarkChannelAsUnread(listChannels, listItems)));
582 private void initItems(Channel channel)
586 channel = new Channel();
588 if (!channel.isOpen() && channel.getURL() != null)
592 SimpleRSSParser.parse(channel);
593 } catch (Exception ex)
595 ex.printStackTrace();
598 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
600 Iterator iter = (channel.getItems() != null) ? channel.getItems()
601 .iterator() : Collections.EMPTY_LIST.iterator();
602 while (iter.hasNext())
604 itemsModel.addElement(iter.next());
606 if (itemsModel.getSize() > 0)
608 listItems.setSelectedIndex(0);
609 _itemsValueChanged(listItems);
611 setStatusBarText(channel.getURL());
615 private void _itemsValueChanged(JList itemList)
617 Item item = (Item) itemList.getSelectedValue();
620 if (itemList.getModel().getSize() > 0)
622 item = (Item) itemList.getModel().getElementAt(0);
630 itemList.setSelectedIndex(0);
634 if (_itemTimer != null)
636 // prefer a shorter delay than 5s
637 _itemTimer.setDelay(300);
639 _itemTimer.setLastItem(item);
640 final Item lastitem = item;
641 _itemTimer.addActionListener(new ActionListener()
645 public void actionPerformed(ActionEvent e)
652 setStatusBarText(item.getLink());
653 textDescription.setBlogText(item);
657 public void setStatusBarText(String text)
659 statusBar.setText(text);
662 private void _updateAllActions()
664 Iterator iter = _updatableActions.iterator();
665 while (iter.hasNext())
667 UpdatableAction action = (UpdatableAction) iter.next();
672 private void _updateToolbarButtons()
674 Map general = (Map) Main.getPreferences().get("general");
680 Component[] components = toolBar.getComponents();
681 for (int i = 0; i < components.length; i++)
683 Component component = components[i];
684 if (component instanceof JButton)
686 JButton button = (JButton) component;
687 if (Boolean.toString(false).equals(general.get("useToolBarText")))
689 // Remove the text if preferences state no toolbar text
692 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
694 button.setVerticalTextPosition(AbstractButton.BOTTOM);
695 button.setHorizontalTextPosition(AbstractButton.CENTER);
697 else if (Boolean.toString(true).equals(
698 general.get("radioTextRight")))
700 button.setVerticalTextPosition(AbstractButton.CENTER);
701 button.setHorizontalTextPosition(AbstractButton.RIGHT);
707 private void listItems_mouseClicked(MouseEvent e)
709 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
711 Item item = (Item) listItems.getSelectedValue();
714 if (_itemTimer != null)
719 Action action = new LaunchJvBrowserOnItem(listItems);
720 ActionEvent event = new ActionEvent(this,
721 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
722 action.actionPerformed(event);
727 * force the news panel to refresh
729 public void refreshNews()
733 initItems((Channel) _channelModel.getElementAt(0));
735 } catch (Exception x)
740 public static void main(String args[])
742 // this tests the detection of new news based on the last read date stored
743 // in jalview properties
744 jalview.bin.Cache.loadProperties(null);
745 jalview.bin.Cache.initLogger();
746 // test will advance read date each time
747 Calendar today = Calendar.getInstance(), lastread = Calendar
749 lastread.set(1983, 01, 01);
750 while (lastread.before(today))
752 Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
754 BlogReader me = new BlogReader();
755 System.out.println("Set last date to "
756 + jalview.bin.Cache.date_format.format(lastread.getTime()));
759 Cache.log.info("There is news to read.");
763 Cache.log.info("There is no new news.");
764 me.xf.setTitle("Testing : Last read is " + me.lastDate);
768 Cache.log.info("Waiting for closure.");
774 } catch (InterruptedException x)
778 } while (me.isVisible());
782 Cache.log.info("Still new news after reader displayed.");
784 if (lastread.getTime().before(me.lastDate))
786 Cache.log.info("The news was read.");
787 lastread.setTime(me.lastDate);
791 lastread.add(Calendar.MONTH, 1);
803 class ChannelsRenderer extends DefaultListCellRenderer
805 private final static Icon _icon = new ImageIcon(
806 Main.class.getResource("image/ComposeMail16.gif"));
808 public Component getListCellRendererComponent(JList list, Object value,
809 int index, boolean isSelected, boolean cellHasFocus)
811 JLabel component = (JLabel) super.getListCellRendererComponent(list,
812 value, index, isSelected, cellHasFocus);
813 component.setIcon(ChannelsRenderer._icon);
814 if (value instanceof Channel)
816 Channel channel = (Channel) value;
817 component.setText(MessageManager.formatMessage("label.channel_title_item_count", new String[]{channel.getTitle(),Integer.valueOf(channel.getUnreadItemCount()).toString()}));
818 component.setToolTipText(channel.getURL());
824 class ItemsRenderer extends DefaultListCellRenderer
826 private final static Icon _icon = new ImageIcon(
827 Main.class.getResource("image/ComposeMail16.gif"));
829 public Component getListCellRendererComponent(JList list, Object value,
830 int index, boolean isSelected, boolean cellHasFocus)
832 JLabel component = (JLabel) super.getListCellRendererComponent(list,
833 value, index, isSelected, cellHasFocus);
834 component.setIcon(ItemsRenderer._icon);
835 if (value instanceof Item)
837 Item item = (Item) value;
838 if (item.getPublishDate() != null)
840 component.setText(MessageManager.formatMessage("label.blog_item_published_on_date", new String[]{DateFormat.getDateInstance(DateFormat.LONG, MessageManager.getLocale()).format(item.getPublishDate()).toString(),item.getTitle()}));
842 component.setToolTipText(item.getLink());
845 component.setFont(component.getFont().deriveFont(Font.BOLD));
849 component.setFont(component.getFont().deriveFont(Font.PLAIN));