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 jalview.util.MessageManager;
23 import java.awt.BorderLayout;
24 import java.awt.Component;
25 import java.awt.Dimension;
27 import java.awt.Rectangle;
28 import java.awt.Dialog.ModalExclusionType;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.awt.event.KeyEvent;
32 import java.awt.event.MouseEvent;
33 import java.awt.event.WindowAdapter;
34 import java.awt.event.WindowEvent;
35 import java.beans.PropertyChangeListener;
36 import java.text.DateFormat;
37 import java.text.SimpleDateFormat;
38 import java.util.ArrayList;
39 import java.util.Calendar;
40 import java.util.Collections;
41 import java.util.Iterator;
42 import java.util.List;
45 import javax.swing.AbstractAction;
46 import javax.swing.AbstractButton;
47 import javax.swing.Action;
48 import javax.swing.DefaultListCellRenderer;
49 import javax.swing.DefaultListModel;
50 import javax.swing.Icon;
51 import javax.swing.ImageIcon;
52 import javax.swing.JButton;
53 import javax.swing.JFrame;
54 import javax.swing.JLabel;
55 import javax.swing.JList;
56 import javax.swing.JMenuItem;
57 import javax.swing.JPanel;
58 import javax.swing.JPopupMenu;
59 import javax.swing.JScrollPane;
60 import javax.swing.JSplitPane;
61 import javax.swing.JToolBar;
62 import javax.swing.ListSelectionModel;
63 import javax.swing.SwingUtilities;
64 import javax.swing.event.HyperlinkEvent;
65 import javax.swing.event.HyperlinkListener;
66 import javax.swing.event.ListSelectionEvent;
67 import javax.swing.event.ListSelectionListener;
69 import org.robsite.jswingreader.action.MarkChannelAsRead;
70 import org.robsite.jswingreader.action.MarkChannelAsUnread;
71 import org.robsite.jswingreader.action.MarkItemAsRead;
72 import org.robsite.jswingreader.action.MarkItemAsUnread;
73 import org.robsite.jswingreader.action.UpdatableAction;
74 import org.robsite.jswingreader.model.Channel;
75 import org.robsite.jswingreader.model.ChannelListModel;
76 import org.robsite.jswingreader.model.Item;
77 import org.robsite.jswingreader.model.SimpleRSSParser;
78 import org.robsite.jswingreader.ui.BlogContentPane;
79 import org.robsite.jswingreader.ui.ItemReadTimer;
80 import org.robsite.jswingreader.ui.Main;
81 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
84 * Blog reading window, adapted from JSwingReader's
85 * org.robsite.jswingreader.ui.MainWindow class
88 public class BlogReader extends JPanel
90 private JButton buttonRefresh = new JButton();
92 private JToolBar toolBar = new JToolBar();
94 private JLabel statusBar = new JLabel();
96 private JPanel panelMain = new JPanel();
98 private BorderLayout layoutMain = new BorderLayout();
100 private BorderLayout borderLayout1 = new BorderLayout();
102 private JPanel topPanel = new JPanel();
104 private JPanel bottomPanel = new JPanel();
106 private JSplitPane topBottomSplitPane = new JSplitPane();
108 private JList listItems = new JList(new DefaultListModel());
110 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
111 private BlogContentPane textDescription = new BlogContentPane();
113 // ADD IN JALVIEW BANNER FOR PRETTINESS
114 private BorderLayout borderLayout4 = new BorderLayout();
116 private BorderLayout borderLayout5 = new BorderLayout();
118 private ChannelListModel _channelModel = null;
120 private JList listChannels = new JList();
122 private Action exitAction = new Action()
126 public void actionPerformed(ActionEvent arg0)
136 parent.showNews(false);
142 public void setEnabled(boolean arg0)
148 public void removePropertyChangeListener(PropertyChangeListener arg0)
150 // TODO Auto-generated method stub
155 public void putValue(String arg0, Object arg1)
157 // TODO Auto-generated method stub
162 public boolean isEnabled()
164 // TODO Auto-generated method stub
169 public Object getValue(String arg0)
171 // TODO Auto-generated method stub
176 public void addPropertyChangeListener(PropertyChangeListener arg0)
178 // TODO Auto-generated method stub
183 private JFrame xf = null;
185 private JalviewDialog jd = null;
187 private JalviewDialog createDialog()
190 return jd = new JalviewDialog()
194 protected void raiseClosed()
198 Cache.log.info("News window closed.");
200 parent.showNews(false);
205 protected void okPressed()
207 // TODO Auto-generated method stub
212 protected void cancelPressed()
214 // TODO Auto-generated method stub
220 private JLabel lblChannels = new JLabel();
222 private List _updatableActions = new ArrayList();
224 private ItemReadTimer _itemTimer = null;
226 private JPopupMenu _popupItems = null;
228 private JPopupMenu _popupChannels = null;
230 private String lastm = "";
232 private boolean newsnew = false;
234 private Desktop parent = null;
241 // should we ignore fake gui events
242 private boolean updating = false;
244 public BlogReader(Desktop desktop)
246 Cache.log.info("Constructing news reader.");
249 _channelModel = new ChannelListModel();
250 // Construct our jalview news channel
251 Channel chan = new Channel();
252 chan.setURL(jalview.bin.Cache.getDefault(
254 jalview.bin.Cache.getDefault("www.jalview.org",
255 "http://www.jalview.org") + "/feeds/desktop/rss"));
257 _channelModel.addChannel(chan);
263 } catch (Exception e)
270 boolean setvisible = checkForNew(chan, true);
275 Cache.log.info("Will show jalview news automatically");
278 Cache.log.info("Completed construction of reader.");
283 * check if the news panel's container is visible
285 public boolean isVisible()
289 return xf != null && xf.isVisible();
291 return jd != null && jd.isVisible();
295 * display the container for the news panel
297 public void showNews()
299 final BlogReader me = this;
300 SwingUtilities.invokeLater(new Runnable()
305 Rectangle bounds = new Rectangle(5, 5, 550, 350);
309 xf.setContentPane(me);
310 xf.addWindowListener(new WindowAdapter()
312 public void windowClosing(WindowEvent e)
314 ActionEvent actionEvent = new ActionEvent(this,
315 ActionEvent.ACTION_FIRST, (String) exitAction
316 .getValue(Action.NAME));
317 exitAction.actionPerformed(actionEvent);
320 public void windowOpened(WindowEvent e)
324 me.setSize(new Dimension(550, 350));
330 bounds = new Rectangle(5, 5, 550, 350);
331 jd.initDialogFrame(me, false, false, "News from www.jalview.org",
332 bounds.width, bounds.height);
333 jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
334 Cache.log.info("Displaying news.");
342 * update hasnew flag and mark all new messages as unread.
344 private boolean checkForNew(Channel chan, boolean updateItems)
347 if (!updating || updateItems)
351 java.util.Date earliest = null;
354 earliest = new SimpleDateFormat("YYYY-MM-DD",MessageManager.getLocale()).parse(chan
355 .getHTTPLastModified());
356 } catch (Exception x)
360 if (chan != null && chan.getItems() != null)
362 Cache.log.debug("Scanning news items: newsnew=" + newsnew
363 + " and lastDate is " + lastDate);
364 for (Item i : (List<Item>) chan.getItems())
366 boolean isread = lastDate == null ? false
367 : (i.getPublishDate() != null && !lastDate.before(i
370 if (!updating || updateItems)
378 if (i.getPublishDate() != null && !i.isRead())
380 if (earliest == null || earliest.after(i.getPublishDate()))
382 earliest = i.getPublishDate();
387 if (!updateItems && !updating && lastDate == null)
394 java.util.Date lastDate = null;
396 private void loadLastM()
398 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
401 private void saveLastM(Item item)
405 if (item.getPublishDate() != null)
407 if (lastDate == null || item.getPublishDate().after(lastDate))
409 lastDate = item.getPublishDate();
413 if (_channelModel.getElementAt(0) != null)
415 checkForNew((Channel) _channelModel.getElementAt(0), false);
417 if (lastDate != null)
419 jalview.bin.Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
421 jalview.bin.Cache.log.info("Saved last read date as "
422 + jalview.bin.Cache.date_format.format(lastDate));
428 private void jbInit() throws Exception
430 setLayout(layoutMain);
431 panelMain.setLayout(borderLayout1);
432 topPanel.setLayout(borderLayout5);
433 bottomPanel.setLayout(borderLayout4);
434 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
435 topBottomSplitPane.setDividerLocation(100);
436 topBottomSplitPane.setTopComponent(topPanel);
437 topBottomSplitPane.setBottomComponent(bottomPanel);
438 JScrollPane spTextDescription = new JScrollPane(textDescription);
439 textDescription.setText("");
440 statusBar.setText(MessageManager.getString("label.status"));
441 buttonRefresh.addActionListener(new ActionListener()
445 public void actionPerformed(ActionEvent e)
450 add(statusBar, BorderLayout.SOUTH);
451 toolBar.add(buttonRefresh);
452 toolBar.addSeparator();
453 JLabel about = new JLabel(
454 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
456 toolBar.setFloatable(false);
457 add(toolBar, BorderLayout.NORTH);
458 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
459 add(panelMain, BorderLayout.CENTER);
460 JScrollPane spListItems = new JScrollPane(listItems);
462 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
463 topPanel.add(spListItems, BorderLayout.CENTER);
464 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
465 listChannels.setModel(_channelModel);
467 listItems.addMouseListener(new java.awt.event.MouseAdapter()
469 public void mouseClicked(MouseEvent e)
471 listItems_mouseClicked(e);
474 _popupItems = _buildItemsPopupMenu();
475 _popupChannels = _buildChannelsPopupMenu();
476 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
478 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
480 listItems.addMouseListener(popupAdapter);
481 listItems.setCellRenderer(new ItemsRenderer());
482 lblChannels.setText(MessageManager.getString("label.channels"));
485 private void postInit()
487 // clear the default hyperlink listener and replace with our own.
488 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
490 textDescription.removeHyperlinkListener(hll);
492 textDescription.addHyperlinkListener(new HyperlinkListener()
494 public void hyperlinkUpdate(HyperlinkEvent e)
496 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
498 Desktop.showUrl(e.getURL().toExternalForm());
503 listItems.addListSelectionListener(new ListSelectionListener()
505 public void valueChanged(ListSelectionEvent e)
507 if (e.getValueIsAdjusting() == false)
509 _itemsValueChanged(listItems);
513 listChannels.setSelectedIndex(1);
515 _updateToolbarButtons();
517 _itemTimer = new ItemReadTimer(listChannels, listItems);
518 _itemsValueChanged(listItems);
521 public class LaunchJvBrowserOnItem extends AbstractAction implements
524 JList _listItems = null;
526 public LaunchJvBrowserOnItem(JList listItems)
528 super("Open in Browser");
529 this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
530 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
531 _listItems = listItems;
534 public void actionPerformed(ActionEvent e)
536 Object o = _listItems.getSelectedValue();
537 if (o instanceof Item)
539 Item item = (Item) o;
541 _listItems.repaint();
543 Desktop.showUrl(item.getLink());
547 public void update(Object o)
550 if (_listItems == null || _listItems.getModel().getSize() == 0)
554 else if (_listItems.getSelectedIndex() == -1)
562 private JPopupMenu _buildItemsPopupMenu()
564 JPopupMenu popup = new JPopupMenu();
565 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
566 popup.addSeparator();
567 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
568 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
572 private JPopupMenu _buildChannelsPopupMenu()
574 JPopupMenu popup = new JPopupMenu();
575 popup.add(new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
576 popup.add(new JMenuItem(
577 new MarkChannelAsUnread(listChannels, listItems)));
581 private void initItems(Channel channel)
585 channel = new Channel();
587 if (!channel.isOpen() && channel.getURL() != null)
591 SimpleRSSParser.parse(channel);
592 } catch (Exception ex)
594 ex.printStackTrace();
597 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
599 Iterator iter = (channel.getItems() != null) ? channel.getItems()
600 .iterator() : Collections.EMPTY_LIST.iterator();
601 while (iter.hasNext())
603 itemsModel.addElement(iter.next());
605 if (itemsModel.getSize() > 0)
607 listItems.setSelectedIndex(0);
608 _itemsValueChanged(listItems);
610 setStatusBarText(channel.getURL());
614 private void _itemsValueChanged(JList itemList)
616 Item item = (Item) itemList.getSelectedValue();
619 if (itemList.getModel().getSize() > 0)
621 item = (Item) itemList.getModel().getElementAt(0);
629 itemList.setSelectedIndex(0);
633 if (_itemTimer != null)
635 // prefer a shorter delay than 5s
636 _itemTimer.setDelay(300);
638 _itemTimer.setLastItem(item);
639 final Item lastitem = item;
640 _itemTimer.addActionListener(new ActionListener()
644 public void actionPerformed(ActionEvent e)
651 setStatusBarText(item.getLink());
652 textDescription.setBlogText(item);
656 public void setStatusBarText(String text)
658 statusBar.setText(text);
661 private void _updateAllActions()
663 Iterator iter = _updatableActions.iterator();
664 while (iter.hasNext())
666 UpdatableAction action = (UpdatableAction) iter.next();
671 private void _updateToolbarButtons()
673 Map general = (Map) Main.getPreferences().get("general");
679 Component[] components = toolBar.getComponents();
680 for (int i = 0; i < components.length; i++)
682 Component component = components[i];
683 if (component instanceof JButton)
685 JButton button = (JButton) component;
686 if (Boolean.toString(false).equals(general.get("useToolBarText")))
688 // Remove the text if preferences state no toolbar text
691 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
693 button.setVerticalTextPosition(AbstractButton.BOTTOM);
694 button.setHorizontalTextPosition(AbstractButton.CENTER);
696 else if (Boolean.toString(true).equals(
697 general.get("radioTextRight")))
699 button.setVerticalTextPosition(AbstractButton.CENTER);
700 button.setHorizontalTextPosition(AbstractButton.RIGHT);
706 private void listItems_mouseClicked(MouseEvent e)
708 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
710 Item item = (Item) listItems.getSelectedValue();
713 if (_itemTimer != null)
718 Action action = new LaunchJvBrowserOnItem(listItems);
719 ActionEvent event = new ActionEvent(this,
720 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
721 action.actionPerformed(event);
726 * force the news panel to refresh
728 public void refreshNews()
732 initItems((Channel) _channelModel.getElementAt(0));
734 } catch (Exception x)
739 public static void main(String args[])
741 // this tests the detection of new news based on the last read date stored
742 // in jalview properties
743 jalview.bin.Cache.loadProperties(null);
744 jalview.bin.Cache.initLogger();
745 // test will advance read date each time
746 Calendar today = Calendar.getInstance(), lastread = Calendar
748 lastread.set(1983, 01, 01);
749 while (lastread.before(today))
751 Cache.setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED",
753 BlogReader me = new BlogReader();
754 System.out.println("Set last date to "
755 + jalview.bin.Cache.date_format.format(lastread.getTime()));
758 Cache.log.info("There is news to read.");
762 Cache.log.info("There is no new news.");
763 me.xf.setTitle("Testing : Last read is " + me.lastDate);
767 Cache.log.info("Waiting for closure.");
773 } catch (InterruptedException x)
777 } while (me.isVisible());
781 Cache.log.info("Still new news after reader displayed.");
783 if (lastread.getTime().before(me.lastDate))
785 Cache.log.info("The news was read.");
786 lastread.setTime(me.lastDate);
790 lastread.add(Calendar.MONTH, 1);
802 class ChannelsRenderer extends DefaultListCellRenderer
804 private final static Icon _icon = new ImageIcon(
805 Main.class.getResource("image/ComposeMail16.gif"));
807 public Component getListCellRendererComponent(JList list, Object value,
808 int index, boolean isSelected, boolean cellHasFocus)
810 JLabel component = (JLabel) super.getListCellRendererComponent(list,
811 value, index, isSelected, cellHasFocus);
812 component.setIcon(ChannelsRenderer._icon);
813 if (value instanceof Channel)
815 Channel channel = (Channel) value;
816 component.setText(MessageManager.formatMessage("label.channel_title_item_count", new String[]{channel.getTitle(),Integer.valueOf(channel.getUnreadItemCount()).toString()}));
817 component.setToolTipText(channel.getURL());
823 class ItemsRenderer extends DefaultListCellRenderer
825 private final static Icon _icon = new ImageIcon(
826 Main.class.getResource("image/ComposeMail16.gif"));
828 public Component getListCellRendererComponent(JList list, Object value,
829 int index, boolean isSelected, boolean cellHasFocus)
831 JLabel component = (JLabel) super.getListCellRendererComponent(list,
832 value, index, isSelected, cellHasFocus);
833 component.setIcon(ItemsRenderer._icon);
834 if (value instanceof Item)
836 Item item = (Item) value;
837 if (item.getPublishDate() != null)
839 component.setText(MessageManager.formatMessage("label.blog_item_published_on_date", new String[]{DateFormat.getDateInstance(DateFormat.LONG, MessageManager.getLocale()).format(item.getPublishDate()).toString(),item.getTitle()}));
841 component.setToolTipText(item.getLink());
844 component.setFont(component.getFont().deriveFont(Font.BOLD));
848 component.setFont(component.getFont().deriveFont(Font.PLAIN));