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.bin.Console;
25 import jalview.util.MessageManager;
27 import java.awt.BorderLayout;
28 import java.awt.Component;
29 import java.awt.Dialog.ModalExclusionType;
30 import java.awt.Dimension;
32 import java.awt.Rectangle;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.KeyEvent;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.WindowAdapter;
38 import java.awt.event.WindowEvent;
39 import java.beans.PropertyChangeListener;
40 import java.text.DateFormat;
41 import java.text.SimpleDateFormat;
42 import java.util.ArrayList;
43 import java.util.Calendar;
44 import java.util.Collections;
45 import java.util.Date;
46 import java.util.Iterator;
47 import java.util.List;
50 import javax.swing.AbstractAction;
51 import javax.swing.AbstractButton;
52 import javax.swing.Action;
53 import javax.swing.DefaultListCellRenderer;
54 import javax.swing.DefaultListModel;
55 import javax.swing.Icon;
56 import javax.swing.ImageIcon;
57 import javax.swing.JButton;
58 import javax.swing.JFrame;
59 import javax.swing.JLabel;
60 import javax.swing.JList;
61 import javax.swing.JMenuItem;
62 import javax.swing.JPanel;
63 import javax.swing.JPopupMenu;
64 import javax.swing.JScrollPane;
65 import javax.swing.JSplitPane;
66 import javax.swing.JToolBar;
67 import javax.swing.ListSelectionModel;
68 import javax.swing.SwingUtilities;
69 import javax.swing.event.HyperlinkEvent;
70 import javax.swing.event.HyperlinkListener;
71 import javax.swing.event.ListSelectionEvent;
72 import javax.swing.event.ListSelectionListener;
74 import org.robsite.jswingreader.action.MarkChannelAsRead;
75 import org.robsite.jswingreader.action.MarkChannelAsUnread;
76 import org.robsite.jswingreader.action.MarkItemAsRead;
77 import org.robsite.jswingreader.action.MarkItemAsUnread;
78 import org.robsite.jswingreader.action.UpdatableAction;
79 import org.robsite.jswingreader.model.Channel;
80 import org.robsite.jswingreader.model.ChannelListModel;
81 import org.robsite.jswingreader.model.Item;
82 import org.robsite.jswingreader.model.SimpleRSSParser;
83 import org.robsite.jswingreader.ui.BlogContentPane;
84 import org.robsite.jswingreader.ui.ItemReadTimer;
85 import org.robsite.jswingreader.ui.Main;
86 import org.robsite.jswingreader.ui.util.ContextMenuMouseAdapter;
89 * Blog reading window, adapted from JSwingReader's
90 * org.robsite.jswingreader.ui.MainWindow class
93 public class BlogReader extends JPanel
95 private JButton buttonRefresh = new JButton();
97 private JToolBar toolBar = new JToolBar();
99 private JLabel statusBar = new JLabel();
101 private JPanel panelMain = new JPanel();
103 private BorderLayout layoutMain = new BorderLayout();
105 private BorderLayout borderLayout1 = new BorderLayout();
107 private JPanel topPanel = new JPanel();
109 private JPanel bottomPanel = new JPanel();
111 private JSplitPane topBottomSplitPane = new JSplitPane();
113 private JList listItems = new JList(new DefaultListModel());
115 // SWITCH IN JALVIEW HTML VIEWER PANE HERE
116 private BlogContentPane textDescription = new BlogContentPane();
118 // ADD IN JALVIEW BANNER FOR PRETTINESS
119 private BorderLayout borderLayout4 = new BorderLayout();
121 private BorderLayout borderLayout5 = new BorderLayout();
123 private ChannelListModel _channelModel = null;
125 private JList listChannels = new JList();
127 private Action exitAction = new Action()
131 public void actionPerformed(ActionEvent arg0)
141 parent.showNews(false);
147 public void setEnabled(boolean arg0)
153 public void removePropertyChangeListener(PropertyChangeListener arg0)
155 // TODO Auto-generated method stub
160 public void putValue(String arg0, Object arg1)
162 // TODO Auto-generated method stub
167 public boolean isEnabled()
169 // TODO Auto-generated method stub
174 public Object getValue(String arg0)
176 // TODO Auto-generated method stub
181 public void addPropertyChangeListener(PropertyChangeListener arg0)
183 // TODO Auto-generated method stub
188 private JFrame xf = null;
190 private JalviewDialog jd = null;
192 private JalviewDialog createDialog()
195 return jd = new JalviewDialog()
199 protected void raiseClosed()
203 Console.debug("News window closed.");
205 parent.showNews(false);
210 protected void okPressed()
212 // TODO Auto-generated method stub
217 protected void cancelPressed()
219 // TODO Auto-generated method stub
225 private JLabel lblChannels = new JLabel();
227 private List _updatableActions = new ArrayList();
229 private ItemReadTimer _itemTimer = null;
231 private JPopupMenu _popupItems = null;
233 private JPopupMenu _popupChannels = null;
235 private String lastm = "";
237 private boolean newsnew = false;
239 private Desktop parent = null;
246 // should we ignore fake gui events
247 private boolean updating = false;
249 public BlogReader(Desktop desktop)
251 Console.debug("Constructing news reader.");
254 _channelModel = new ChannelListModel();
255 // Construct our jalview news channel
256 Channel chan = new Channel();
257 chan.setURL(Cache.getDefault("JALVIEW_NEWS_RSS",
258 Cache.getDefault("www.jalview.org", "https://www.jalview.org")
259 + "/feeds/desktop/rss"));
261 _channelModel.addChannel(chan);
267 } catch (Exception e)
274 if (!Cache.getDefault("NONEWS", false))
276 boolean setvisible = checkForNew(chan, true);
281 Console.debug("Will show jalview news automatically");
285 Console.debug("Completed construction of reader.");
290 * check if the news panel's container is visible
293 public boolean isVisible()
297 return xf != null && xf.isVisible();
299 return jd != null && jd.isVisible();
303 * display the container for the news panel
305 public void showNews()
307 final BlogReader me = this;
308 SwingUtilities.invokeLater(new Runnable()
313 Rectangle bounds = new Rectangle(5, 5, 550, 350);
317 xf.setContentPane(me);
318 xf.addWindowListener(new WindowAdapter()
321 public void windowClosing(WindowEvent e)
323 ActionEvent actionEvent = new ActionEvent(this,
324 ActionEvent.ACTION_FIRST,
325 (String) exitAction.getValue(Action.NAME));
326 exitAction.actionPerformed(actionEvent);
330 public void windowOpened(WindowEvent e)
334 me.setSize(new Dimension(550, 350));
340 bounds = new Rectangle(5, 5, 550, 350);
341 jd.initDialogFrame(me, false, false,
342 MessageManager.getString("label.news_from_jalview"),
343 bounds.width, bounds.height);
344 jd.frame.setModalExclusionType(ModalExclusionType.NO_EXCLUDE);
345 Console.debug("Displaying news.");
353 * update hasnew flag and mark all new messages as unread.
355 private boolean checkForNew(Channel chan, boolean updateItems)
358 if (!updating || updateItems)
362 java.util.Date earliest = null;
365 earliest = new SimpleDateFormat("YYYY-MM-DD")
366 .parse(chan.getHTTPLastModified());
367 } catch (Exception x)
370 if (chan != null && chan.getItems() != null)
372 Console.debug("Scanning news items: newsnew=" + newsnew
373 + " and lastDate is " + lastDate);
374 for (Item i : (List<Item>) chan.getItems())
376 Date published = i.getPublishDate();
377 boolean isread = lastDate == null ? false
378 : (published != null && !lastDate.before(published));
380 if (!updating || updateItems)
388 if (published != null && !i.isRead())
390 if (earliest == null || earliest.after(published))
392 earliest = published;
397 if (!updateItems && !updating && lastDate == null)
404 java.util.Date lastDate = null;
406 private void loadLastM()
408 lastDate = Cache.getDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED");
411 private void saveLastM(Item item)
415 if (item.getPublishDate() != null)
417 if (lastDate == null || item.getPublishDate().after(lastDate))
419 lastDate = item.getPublishDate();
423 if (_channelModel.getElementAt(0) != null)
425 checkForNew((Channel) _channelModel.getElementAt(0), false);
427 if (lastDate != null)
429 String formatted = Cache
430 .setDateProperty("JALVIEW_NEWS_RSS_LASTMODIFIED", lastDate);
431 Console.debug("Saved last read date as " + formatted);
436 private void jbInit() throws Exception
438 setLayout(layoutMain);
439 panelMain.setLayout(borderLayout1);
440 topPanel.setLayout(borderLayout5);
441 bottomPanel.setLayout(borderLayout4);
442 topBottomSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
443 topBottomSplitPane.setDividerLocation(100);
444 topBottomSplitPane.setTopComponent(topPanel);
445 topBottomSplitPane.setBottomComponent(bottomPanel);
446 JScrollPane spTextDescription = new JScrollPane(textDescription);
447 textDescription.setText("");
448 statusBar.setText(new StringBuffer("[")
449 .append(MessageManager.getString("label.status")).append("]")
451 buttonRefresh.addActionListener(new ActionListener()
455 public void actionPerformed(ActionEvent e)
460 add(statusBar, BorderLayout.SOUTH);
461 toolBar.add(buttonRefresh);
462 toolBar.addSeparator();
463 JLabel about = new JLabel(
464 "brought to you by JSwingReader (jswingreader.sourceforge.net)");
466 toolBar.setFloatable(false);
467 add(toolBar, BorderLayout.NORTH);
468 panelMain.add(topBottomSplitPane, BorderLayout.CENTER);
469 add(panelMain, BorderLayout.CENTER);
470 JScrollPane spListItems = new JScrollPane(listItems);
472 .setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
473 topPanel.add(spListItems, BorderLayout.CENTER);
474 bottomPanel.add(spTextDescription, BorderLayout.CENTER);
475 listChannels.setModel(_channelModel);
477 listItems.addMouseListener(new java.awt.event.MouseAdapter()
480 public void mouseClicked(MouseEvent e)
482 listItems_mouseClicked(e);
485 _popupItems = _buildItemsPopupMenu();
486 _popupChannels = _buildChannelsPopupMenu();
487 ContextMenuMouseAdapter popupAdapter = new ContextMenuMouseAdapter(
489 ContextMenuMouseAdapter popupChannelsAdapter = new ContextMenuMouseAdapter(
491 listItems.addMouseListener(popupAdapter);
492 listItems.setCellRenderer(new ItemsRenderer());
493 lblChannels.setText(MessageManager.getString("label.channels"));
496 private void postInit()
498 // clear the default hyperlink listener and replace with our own.
499 for (HyperlinkListener hll : textDescription.getHyperlinkListeners())
501 textDescription.removeHyperlinkListener(hll);
503 textDescription.addHyperlinkListener(new HyperlinkListener()
506 public void hyperlinkUpdate(HyperlinkEvent e)
508 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
510 Desktop.showUrl(e.getURL().toExternalForm());
515 listItems.addListSelectionListener(new ListSelectionListener()
518 public void valueChanged(ListSelectionEvent e)
520 if (e.getValueIsAdjusting() == false)
522 _itemsValueChanged(listItems);
526 listChannels.setSelectedIndex(1);
528 _updateToolbarButtons();
530 _itemTimer = new ItemReadTimer(listChannels, listItems);
531 _itemsValueChanged(listItems);
534 public class LaunchJvBrowserOnItem extends AbstractAction
535 implements UpdatableAction
537 JList _listItems = null;
539 public LaunchJvBrowserOnItem(JList listItems)
541 super("Open in Browser");
542 this.putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
543 this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
544 _listItems = listItems;
548 public void actionPerformed(ActionEvent e)
550 Object o = _listItems.getSelectedValue();
551 if (o instanceof Item)
553 Item item = (Item) o;
555 _listItems.repaint();
557 Desktop.showUrl(item.getLink());
562 public void update(Object o)
565 if (_listItems == null || _listItems.getModel().getSize() == 0)
569 else if (_listItems.getSelectedIndex() == -1)
577 private JPopupMenu _buildItemsPopupMenu()
579 JPopupMenu popup = new JPopupMenu();
580 popup.add(new JMenuItem(new LaunchJvBrowserOnItem(listItems)));
581 popup.addSeparator();
582 popup.add(new JMenuItem(new MarkItemAsRead(listItems)));
583 popup.add(new JMenuItem(new MarkItemAsUnread(listItems)));
587 private JPopupMenu _buildChannelsPopupMenu()
589 JPopupMenu popup = new JPopupMenu();
591 new JMenuItem(new MarkChannelAsRead(listChannels, listItems)));
592 popup.add(new JMenuItem(
593 new MarkChannelAsUnread(listChannels, listItems)));
597 private void initItems(Channel channel)
601 channel = new Channel();
603 if (!channel.isOpen() && channel.getURL() != null)
607 SimpleRSSParser.parse(channel);
608 } catch (Exception ex)
610 ex.printStackTrace();
613 DefaultListModel itemsModel = (DefaultListModel) listItems.getModel();
615 Iterator iter = (channel.getItems() != null)
616 ? channel.getItems().iterator()
617 : Collections.EMPTY_LIST.iterator();
618 while (iter.hasNext())
620 itemsModel.addElement(iter.next());
622 if (itemsModel.getSize() > 0)
624 listItems.setSelectedIndex(0);
625 _itemsValueChanged(listItems);
627 setStatusBarText(channel.getURL());
631 private void _itemsValueChanged(JList itemList)
633 Item item = (Item) itemList.getSelectedValue();
636 if (itemList.getModel().getSize() > 0)
638 item = (Item) itemList.getModel().getElementAt(0);
646 itemList.setSelectedIndex(0);
650 if (_itemTimer != null)
652 // prefer a shorter delay than 5s
653 _itemTimer.setDelay(300);
655 _itemTimer.setLastItem(item);
656 final Item lastitem = item;
657 _itemTimer.addActionListener(new ActionListener()
661 public void actionPerformed(ActionEvent e)
668 setStatusBarText(item.getLink());
669 textDescription.setBlogText(item);
673 public void setStatusBarText(String text)
675 statusBar.setText(text);
678 private void _updateAllActions()
680 Iterator iter = _updatableActions.iterator();
681 while (iter.hasNext())
683 UpdatableAction action = (UpdatableAction) iter.next();
688 private void _updateToolbarButtons()
690 Map general = (Map) Main.getPreferences().get("general");
696 Component[] components = toolBar.getComponents();
697 for (int i = 0; i < components.length; i++)
699 Component component = components[i];
700 if (component instanceof JButton)
702 JButton button = (JButton) component;
703 if (Boolean.toString(false).equals(general.get("useToolBarText")))
705 // Remove the text if preferences state no toolbar text
708 if (Boolean.toString(true).equals(general.get("radioTextBelow")))
710 button.setVerticalTextPosition(AbstractButton.BOTTOM);
711 button.setHorizontalTextPosition(AbstractButton.CENTER);
713 else if (Boolean.toString(true)
714 .equals(general.get("radioTextRight")))
716 button.setVerticalTextPosition(AbstractButton.CENTER);
717 button.setHorizontalTextPosition(AbstractButton.RIGHT);
723 private void listItems_mouseClicked(MouseEvent e)
725 if (e.getClickCount() == 2 && e.getModifiersEx() == MouseEvent.NOBUTTON)
727 Item item = (Item) listItems.getSelectedValue();
730 if (_itemTimer != null)
735 Action action = new LaunchJvBrowserOnItem(listItems);
736 ActionEvent event = new ActionEvent(this,
737 ActionEvent.ACTION_PERFORMED, "LaunchBrowserOnItem");
738 action.actionPerformed(event);
743 * force the news panel to refresh
745 public void refreshNews()
749 initItems((Channel) _channelModel.getElementAt(0));
751 } catch (Exception x)
760 public static void main(String args[])
762 // this tests the detection of new news based on the last read date stored
763 // in jalview properties
764 Cache.loadProperties(null);
765 Console.initLogger();
766 // test will advance read date each time
767 Calendar today = Calendar.getInstance(),
768 lastread = Calendar.getInstance();
769 lastread.set(1983, 01, 01);
770 while (lastread.before(today))
772 String formattedDate = Cache.setDateProperty(
773 "JALVIEW_NEWS_RSS_LASTMODIFIED", lastread.getTime());
774 BlogReader me = new BlogReader();
775 System.out.println("Set last date to " + formattedDate);
778 Console.debug("There is news to read.");
782 Console.debug("There is no new news.");
783 me.xf.setTitle("Testing : Last read is " + me.lastDate);
787 Console.debug("Waiting for closure.");
793 } catch (InterruptedException x)
796 } while (me.isVisible());
800 Console.debug("Still new news after reader displayed.");
802 if (lastread.getTime().before(me.lastDate))
804 Console.debug("The news was read.");
805 lastread.setTime(me.lastDate);
809 lastread.add(Calendar.MONTH, 1);
821 class ChannelsRenderer extends DefaultListCellRenderer
823 private final static Icon _icon = new ImageIcon(
824 Main.class.getResource("image/ComposeMail16.gif"));
827 public Component getListCellRendererComponent(JList list, Object value,
828 int index, boolean isSelected, boolean cellHasFocus)
830 JLabel component = (JLabel) super.getListCellRendererComponent(list,
831 value, index, isSelected, cellHasFocus);
832 component.setIcon(ChannelsRenderer._icon);
833 if (value instanceof Channel)
835 Channel channel = (Channel) value;
836 component.setText(MessageManager
837 .formatMessage("label.channel_title_item_count", new String[]
838 { channel.getTitle(), Integer
839 .valueOf(channel.getUnreadItemCount()).toString() }));
840 component.setToolTipText(channel.getURL());
846 class ItemsRenderer extends DefaultListCellRenderer
848 private final static Icon _icon = new ImageIcon(
849 Main.class.getResource("image/ComposeMail16.gif"));
852 public Component getListCellRendererComponent(JList list, Object value,
853 int index, boolean isSelected, boolean cellHasFocus)
855 JLabel component = (JLabel) super.getListCellRendererComponent(list,
856 value, index, isSelected, cellHasFocus);
857 component.setIcon(ItemsRenderer._icon);
858 if (value instanceof Item)
860 Item item = (Item) value;
861 if (item.getPublishDate() != null)
863 component.setText(MessageManager.formatMessage(
864 "label.blog_item_published_on_date", new String[]
866 .getDateInstance(DateFormat.LONG,
867 MessageManager.getLocale())
868 .format(item.getPublishDate()).toString(),
871 component.setToolTipText(item.getLink());
874 component.setFont(component.getFont().deriveFont(Font.BOLD));
878 component.setFont(component.getFont().deriveFont(Font.PLAIN));