2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ 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.Dialog.ModalExclusionType;
29 import java.awt.Dimension;
31 import java.awt.Rectangle;
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.Date;
45 import java.util.Iterator;
46 import java.util.List;
49 import javax.swing.AbstractAction;
50 import javax.swing.AbstractButton;
51 import javax.swing.Action;
52 import javax.swing.DefaultListCellRenderer;
53 import javax.swing.DefaultListModel;
54 import javax.swing.Icon;
55 import javax.swing.ImageIcon;
56 import javax.swing.JButton;
57 import javax.swing.JFrame;
58 import javax.swing.JLabel;
59 import javax.swing.JList;
60 import javax.swing.JMenuItem;
61 import javax.swing.JPanel;
62 import javax.swing.JPopupMenu;
63 import javax.swing.JScrollPane;
64 import javax.swing.JSplitPane;
65 import javax.swing.JToolBar;
66 import javax.swing.ListSelectionModel;
67 import javax.swing.SwingUtilities;
68 import javax.swing.event.HyperlinkEvent;
69 import javax.swing.event.HyperlinkListener;
70 import javax.swing.event.ListSelectionEvent;
71 import javax.swing.event.ListSelectionListener;
73 import org.robsite.jswingreader.action.MarkChannelAsRead;
74 import org.robsite.jswingreader.action.MarkChannelAsUnread;
75 import org.robsite.jswingreader.action.MarkItemAsRead;
76 import org.robsite.jswingreader.action.MarkItemAsUnread;
77 import org.robsite.jswingreader.action.UpdatableAction;
78 import org.robsite.jswingreader.model.Channel;
79 import org.robsite.jswingreader.model.ChannelListModel;
80 import org.robsite.jswingreader.model.Item;
81 import org.robsite.jswingreader.model.SimpleRSSParser;
82 import org.robsite.jswingreader.ui.BlogContentPane;
83 import org.robsite.jswingreader.ui.ItemReadTimer;
84 import org.robsite.jswingreader.ui.Main;
85 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
88 * Blog reading window, adapted from JSwingReader's
89 * org.robsite.jswingreader.ui.MainWindow class
92 public class BlogReader extends JPanel
94 private JButton buttonRefresh = new JButton();
96 private JToolBar toolBar = new JToolBar();
98 private JLabel statusBar = new JLabel();
100 private JPanel panelMain = new JPanel();
102 private BorderLayout layoutMain = new BorderLayout();
104 private BorderLayout borderLayout1 = new BorderLayout();
106 private JPanel topPanel = new JPanel();
108 private JPanel bottomPanel = new JPanel();
110 private JSplitPane topBottomSplitPane = new JSplitPane();
112 private JList listItems = new JList(new DefaultListModel());
114 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
115 private BlogContentPane textDescription = new BlogContentPane();
117 // ADD IN JALVIEW BANNER FOR PRETTINESS
118 private BorderLayout borderLayout4 = new BorderLayout();
120 private BorderLayout borderLayout5 = new BorderLayout();
122 private ChannelListModel _channelModel = null;
124 private JList listChannels = new JList();
126 private Action exitAction = new Action()
130 public void actionPerformed(ActionEvent arg0)
140 parent.showNews(false);
146 public void setEnabled(boolean arg0)
152 public void removePropertyChangeListener(PropertyChangeListener arg0)
154 // TODO Auto-generated method stub
159 public void putValue(String arg0, Object arg1)
161 // TODO Auto-generated method stub
166 public boolean isEnabled()
168 // TODO Auto-generated method stub
173 public Object getValue(String arg0)
175 // TODO Auto-generated method stub
180 public void addPropertyChangeListener(PropertyChangeListener arg0)
182 // TODO Auto-generated method stub
187 private JFrame xf = null;
189 private JalviewDialog jd = null;
191 private JalviewDialog createDialog()
194 return jd = new JalviewDialog()
198 protected void raiseClosed()
202 Cache.log.debug("News window closed.");
204 parent.showNews(false);
209 protected void okPressed()
211 // TODO Auto-generated method stub
216 protected void cancelPressed()
218 // TODO Auto-generated method stub
224 private JLabel lblChannels = new JLabel();
226 private List _updatableActions = new ArrayList();
228 private ItemReadTimer _itemTimer = null;
230 private JPopupMenu _popupItems = null;
232 private JPopupMenu _popupChannels = null;
234 private String lastm = "";
236 private boolean newsnew = false;
238 private Desktop parent = null;
245 // should we ignore fake gui events
246 private boolean updating = false;
248 public BlogReader(Desktop desktop)
250 Cache.log.debug("Constructing news reader.");
253 _channelModel = new ChannelListModel();
254 // Construct our jalview news channel
255 Channel chan = new Channel();
257 jalview.bin.Cache.getDefault("JALVIEW_NEWS_RSS",
258 jalview.bin.Cache.getDefault("www.jalview.org",
259 "http://www.jalview.org")
260 + "/feeds/desktop/rss"));
262 _channelModel.addChannel(chan);
268 } catch (Exception e)
275 boolean setvisible = checkForNew(chan, true);
280 Cache.log.debug("Will show jalview news automatically");
283 Cache.log.debug("Completed construction of reader.");
288 * check if the news panel's container is visible
291 public boolean isVisible()
295 return xf != null && xf.isVisible();
297 return jd != null && jd.isVisible();
301 * display the container for the news panel
303 public void showNews()
305 final BlogReader me = this;
306 SwingUtilities.invokeLater(new Runnable()
311 Rectangle bounds = new Rectangle(5, 5, 550, 350);
315 xf.setContentPane(me);
316 xf.addWindowListener(new WindowAdapter()
319 public void windowClosing(WindowEvent e)
321 ActionEvent actionEvent = new ActionEvent(this,
322 ActionEvent.ACTION_FIRST,
323 (String) exitAction.getValue(Action.NAME));
324 exitAction.actionPerformed(actionEvent);
328 public void windowOpened(WindowEvent e)
332 me.setSize(new Dimension(550, 350));
338 bounds = new Rectangle(5, 5, 550, 350);
339 jd.initDialogFrame(me, false, false,
340 MessageManager.getString("label.news_from_jalview"),
341 bounds.width, bounds.height);
342 jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
343 Cache.log.debug("Displaying news.");
351 * update hasnew flag and mark all new messages as unread.
353 private boolean checkForNew(Channel chan, boolean updateItems)
356 if (!updating || updateItems)
360 java.util.Date earliest = null;
363 earliest = new SimpleDateFormat("YYYY-MM-DD")
364 .parse(chan.getHTTPLastModified());
365 } catch (Exception x)
368 if (chan != null && chan.getItems() != null)
370 Cache.log.debug("Scanning news items: newsnew=" + newsnew
371 + " and lastDate is " + lastDate);
372 for (Item i : (List<Item>) chan.getItems())
374 Date published = i.getPublishDate();
375 boolean isread = lastDate == null ? false
376 : (published != null && !lastDate.before(published));
378 if (!updating || updateItems)
386 if (published != null && !i.isRead())
388 if (earliest == null || earliest.after(published))
390 earliest = published;
395 if (!updateItems && !updating && lastDate == null)
402 java.util.Date lastDate = null;
404 private void loadLastM()
406 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
409 private void saveLastM(Item item)
413 if (item.getPublishDate() != null)
415 if (lastDate == null || item.getPublishDate().after(lastDate))
417 lastDate = item.getPublishDate();
421 if (_channelModel.getElementAt(0) != null)
423 checkForNew((Channel) _channelModel.getElementAt(0), false);
425 if (lastDate != null)
427 String formatted = Cache
428 .setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", lastDate);
429 Cache.log.debug("Saved last read date as " + formatted);
434 private void jbInit() throws Exception
436 setLayout(layoutMain);
437 panelMain.setLayout(borderLayout1);
438 topPanel.setLayout(borderLayout5);
439 bottomPanel.setLayout(borderLayout4);
440 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
441 topBottomSplitPane.setDividerLocation(100);
442 topBottomSplitPane.setTopComponent(topPanel);
443 topBottomSplitPane.setBottomComponent(bottomPanel);
444 JScrollPane spTextDescription = new JScrollPane(textDescription);
445 textDescription.setText("");
446 statusBar.setText(new StringBuffer("[")
447 .append(MessageManager.getString("label.status")).append("]")
449 buttonRefresh.addActionListener(new ActionListener()
453 public void actionPerformed(ActionEvent e)
458 add(statusBar, BorderLayout.SOUTH);
459 toolBar.add(buttonRefresh);
460 toolBar.addSeparator();
461 JLabel about = new JLabel(
462 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
464 toolBar.setFloatable(false);
465 add(toolBar, BorderLayout.NORTH);
466 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
467 add(panelMain, BorderLayout.CENTER);
468 JScrollPane spListItems = new JScrollPane(listItems);
470 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
471 topPanel.add(spListItems, BorderLayout.CENTER);
472 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
473 listChannels.setModel(_channelModel);
475 listItems.addMouseListener(new java.awt.event.MouseAdapter()
478 public void mouseClicked(MouseEvent e)
480 listItems_mouseClicked(e);
483 _popupItems = _buildItemsPopupMenu();
484 _popupChannels = _buildChannelsPopupMenu();
485 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
487 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
489 listItems.addMouseListener(popupAdapter);
490 listItems.setCellRenderer(new ItemsRenderer());
491 lblChannels.setText(MessageManager.getString("label.channels"));
494 private void postInit()
496 // clear the default hyperlink listener and replace with our own.
497 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
499 textDescription.removeHyperlinkListener(hll);
501 textDescription.addHyperlinkListener(new HyperlinkListener()
504 public void hyperlinkUpdate(HyperlinkEvent e)
506 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
508 Desktop.showUrl(e.getURL().toExternalForm());
513 listItems.addListSelectionListener(new ListSelectionListener()
516 public void valueChanged(ListSelectionEvent e)
518 if (e.getValueIsAdjusting() == false)
520 _itemsValueChanged(listItems);
524 listChannels.setSelectedIndex(1);
526 _updateToolbarButtons();
528 _itemTimer = new ItemReadTimer(listChannels, listItems);
529 _itemsValueChanged(listItems);
532 public class LaunchJvBrowserOnItem extends AbstractAction
533 implements UpdatableAction
535 JList _listItems = null;
537 public LaunchJvBrowserOnItem(JList listItems)
539 super("Open in Browser");
540 this.putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
541 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
542 _listItems = listItems;
546 public void actionPerformed(ActionEvent e)
548 Object o = _listItems.getSelectedValue();
549 if (o instanceof Item)
551 Item item = (Item) o;
553 _listItems.repaint();
555 Desktop.showUrl(item.getLink());
560 public void update(Object o)
563 if (_listItems == null || _listItems.getModel().getSize() == 0)
567 else if (_listItems.getSelectedIndex() == -1)
575 private JPopupMenu _buildItemsPopupMenu()
577 JPopupMenu popup = new JPopupMenu();
578 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
579 popup.addSeparator();
580 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
581 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
585 private JPopupMenu _buildChannelsPopupMenu()
587 JPopupMenu popup = new JPopupMenu();
589 new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
590 popup.add(new JMenuItem(
591 new MarkChannelAsUnread(listChannels, listItems)));
595 private void initItems(Channel channel)
599 channel = new Channel();
601 if (!channel.isOpen() && channel.getURL() != null)
605 SimpleRSSParser.parse(channel);
606 } catch (Exception ex)
608 ex.printStackTrace();
611 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
613 Iterator iter = (channel.getItems() != null)
614 ? channel.getItems().iterator()
615 : Collections.EMPTY_LIST.iterator();
616 while (iter.hasNext())
618 itemsModel.addElement(iter.next());
620 if (itemsModel.getSize() > 0)
622 listItems.setSelectedIndex(0);
623 _itemsValueChanged(listItems);
625 setStatusBarText(channel.getURL());
629 private void _itemsValueChanged(JList itemList)
631 Item item = (Item) itemList.getSelectedValue();
634 if (itemList.getModel().getSize() > 0)
636 item = (Item) itemList.getModel().getElementAt(0);
644 itemList.setSelectedIndex(0);
648 if (_itemTimer != null)
650 // prefer a shorter delay than 5s
651 _itemTimer.setDelay(300);
653 _itemTimer.setLastItem(item);
654 final Item lastitem = item;
655 _itemTimer.addActionListener(new ActionListener()
659 public void actionPerformed(ActionEvent e)
666 setStatusBarText(item.getLink());
667 textDescription.setBlogText(item);
671 public void setStatusBarText(String text)
673 statusBar.setText(text);
676 private void _updateAllActions()
678 Iterator iter = _updatableActions.iterator();
679 while (iter.hasNext())
681 UpdatableAction action = (UpdatableAction) iter.next();
686 private void _updateToolbarButtons()
688 Map general = (Map) Main.getPreferences().get("general");
694 Component[] components = toolBar.getComponents();
695 for (int i = 0; i < components.length; i++)
697 Component component = components[i];
698 if (component instanceof JButton)
700 JButton button = (JButton) component;
701 if (Boolean.toString(false).equals(general.get("useToolBarText")))
703 // Remove the text if preferences state no toolbar text
706 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
708 button.setVerticalTextPosition(AbstractButton.BOTTOM);
709 button.setHorizontalTextPosition(AbstractButton.CENTER);
711 else if (Boolean.toString(true)
712 .equals(general.get("radioTextRight")))
714 button.setVerticalTextPosition(AbstractButton.CENTER);
715 button.setHorizontalTextPosition(AbstractButton.RIGHT);
721 private void listItems_mouseClicked(MouseEvent e)
723 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
725 Item item = (Item) listItems.getSelectedValue();
728 if (_itemTimer != null)
733 Action action = new LaunchJvBrowserOnItem(listItems);
734 ActionEvent event = new ActionEvent(this,
735 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
736 action.actionPerformed(event);
741 * force the news panel to refresh
743 public void refreshNews()
747 initItems((Channel) _channelModel.getElementAt(0));
749 } catch (Exception x)
758 public static void main(String args[])
760 // this tests the detection of new news based on the last read date stored
761 // in jalview properties
762 jalview.bin.Cache.loadProperties(null);
763 jalview.bin.Cache.initLogger();
764 // test will advance read date each time
765 Calendar today = Calendar.getInstance(),
766 lastread = Calendar.getInstance();
767 lastread.set(1983, 01, 01);
768 while (lastread.before(today))
770 String formattedDate = Cache.setDateProperty(
771 "JALVIEW_NEWS_RSS_LASTMODIFIED", lastread.getTime());
772 BlogReader me = new BlogReader();
773 System.out.println("Set last date to " + formattedDate);
776 Cache.log.debug("There is news to read.");
780 Cache.log.debug("There is no new news.");
781 me.xf.setTitle("Testing : Last read is " + me.lastDate);
785 Cache.log.debug("Waiting for closure.");
791 } catch (InterruptedException x)
794 } while (me.isVisible());
798 Cache.log.debug("Still new news after reader displayed.");
800 if (lastread.getTime().before(me.lastDate))
802 Cache.log.debug("The news was read.");
803 lastread.setTime(me.lastDate);
807 lastread.add(Calendar.MONTH, 1);
819 class ChannelsRenderer extends DefaultListCellRenderer
821 private final static Icon _icon = new ImageIcon(
822 Main.class.getResource("image/ComposeMail16.gif"));
825 public Component getListCellRendererComponent(JList list, Object value,
826 int index, boolean isSelected, boolean cellHasFocus)
828 JLabel component = (JLabel) super.getListCellRendererComponent(list,
829 value, index, isSelected, cellHasFocus);
830 component.setIcon(ChannelsRenderer._icon);
831 if (value instanceof Channel)
833 Channel channel = (Channel) value;
834 component.setText(MessageManager
835 .formatMessage("label.channel_title_item_count", new String[]
836 { channel.getTitle(), Integer
837 .valueOf(channel.getUnreadItemCount()).toString() }));
838 component.setToolTipText(channel.getURL());
844 class ItemsRenderer extends DefaultListCellRenderer
846 private final static Icon _icon = new ImageIcon(
847 Main.class.getResource("image/ComposeMail16.gif"));
850 public Component getListCellRendererComponent(JList list, Object value,
851 int index, boolean isSelected, boolean cellHasFocus)
853 JLabel component = (JLabel) super.getListCellRendererComponent(list,
854 value, index, isSelected, cellHasFocus);
855 component.setIcon(ItemsRenderer._icon);
856 if (value instanceof Item)
858 Item item = (Item) value;
859 if (item.getPublishDate() != null)
861 component.setText(MessageManager.formatMessage(
862 "label.blog_item_published_on_date", new String[]
864 .getDateInstance(DateFormat.LONG,
865 MessageManager.getLocale())
866 .format(item.getPublishDate()).toString(),
869 component.setToolTipText(item.getLink());
872 component.setFont(component.getFont().deriveFont(Font.BOLD));
876 component.setFont(component.getFont().deriveFont(Font.PLAIN));