View Javadoc

1   package org.robsite.extension.rss;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Component;
5   import java.awt.Dimension;
6   import java.awt.EventQueue;
7   import java.awt.Font;
8   
9   import java.text.DateFormat;
10  import java.text.SimpleDateFormat;
11  
12  import java.util.Comparator;
13  import java.util.Date;
14  
15  import javax.swing.AbstractListModel;
16  import javax.swing.BorderFactory;
17  import javax.swing.DefaultListCellRenderer;
18  import javax.swing.Icon;
19  import javax.swing.ImageIcon;
20  import javax.swing.JEditorPane;
21  import javax.swing.JFrame;
22  import javax.swing.JList;
23  import javax.swing.JPanel;
24  import javax.swing.JScrollPane;
25  import javax.swing.JSplitPane;
26  import javax.swing.JTable;
27  import javax.swing.SwingConstants;
28  import javax.swing.UIManager;
29  import javax.swing.event.HyperlinkEvent;
30  import javax.swing.event.HyperlinkListener;
31  import javax.swing.event.ListSelectionEvent;
32  import javax.swing.event.ListSelectionListener;
33  import javax.swing.table.AbstractTableModel;
34  import javax.swing.text.html.HTMLDocument;
35  import javax.swing.text.html.StyleSheet;
36  
37  import oracle.ide.Ide;
38  
39  import oracle.javatools.controls.nicetable.NiceTable;
40  import oracle.javatools.controls.nicetable.NiceTableCellRenderer;
41  import oracle.javatools.controls.nicetable.NiceTableHeaderRenderer;
42  import oracle.javatools.controls.nicetable.NiceTableModel;
43  
44  import org.robsite.extension.rss.model.Channel;
45  import org.robsite.extension.rss.model.ChannelEvent;
46  import org.robsite.extension.rss.model.ChannelListener;
47  import org.robsite.extension.rss.model.Channels;
48  import org.robsite.extension.rss.model.Item;
49  import org.robsite.extension.rss.model.SimpleRSSParser;
50  import org.robsite.extension.rss.util.ListEvent;
51  import org.robsite.extension.rss.util.ListListener;
52  
53  /***
54   * The main panel that displays news.
55   * 
56   * @author brian_duff@sourceforge.net
57   * @version $Revision: 1.1.1.1 $
58   */
59  public class NewsFeedPanel extends JPanel
60  {
61    private JSplitPane _splitPane = new JSplitPane();
62    private NiceTable _table = new NiceTable();
63    private JList _channelList = new JList();
64    private JEditorPane _statusBar = new JEditorPane();
65    private JEditorPane _noChannelLabel = new JEditorPane();
66    private JScrollPane _tableScrollPane;
67    
68    private boolean _isBusy;
69    
70    public NewsFeedPanel()
71    {
72      _table.setAutoResizeMode( NiceTable.AUTO_RESIZE_OFF );
73    
74      setLayout( new BorderLayout() );
75      add( _splitPane, BorderLayout.CENTER );
76      
77      _splitPane.setOrientation( JSplitPane.HORIZONTAL_SPLIT );
78      
79      JScrollPane sp = new JScrollPane( _channelList );
80      sp.setBorder( BorderFactory.createEmptyBorder() );
81      _splitPane.setLeftComponent( sp );
82      
83      _tableScrollPane = new JScrollPane( _table );
84      _tableScrollPane.setBorder( BorderFactory.createEmptyBorder() );
85      _splitPane.setRightComponent( _tableScrollPane );
86      
87      _splitPane.setBorder( null );
88      
89      _channelList.addListSelectionListener( 
90        new ListSelectionListener() 
91        {
92          public void valueChanged( ListSelectionEvent lse )
93          {
94            listSelectionChanged();
95          }
96        }
97      );
98      _channelList.setCellRenderer( new ChannelListCellRenderer() );
99      _table.setDefaultRenderer( Boolean.class, new ReadToggleRenderer() );
100     _table.setDefaultRenderer( Item.class, new ItemRenderer() );
101     _table.setDefaultRenderer( Date.class, new ItemDateRenderer() );
102     
103     Font labelFont = UIManager.getFont( "Label.font" );
104     
105     _statusBar.setPreferredSize( new Dimension( 0, 
106       _statusBar.getFontMetrics( labelFont ).getHeight() + 2
107     ) );
108     
109     add( _statusBar, BorderLayout.SOUTH );
110     
111     _statusBar.setContentType( "text/html" );
112     _statusBar.setOpaque( false );
113     _statusBar.setEditable( false );
114 //    _statusBar.setHighlighter( null );
115     configureEditorPaneStyle( _statusBar, labelFont );
116     
117     _noChannelLabel.setContentType( "text/html" );
118     _noChannelLabel.setBackground( UIManager.getColor( "window" ) );
119     _noChannelLabel.setOpaque( true );
120     _noChannelLabel.setEditable( false );
121 
122 
123     configureEditorPaneStyle( _noChannelLabel, labelFont );
124     
125     HyperlinkListener listener = new HyperlinkListener() {
126       public void hyperlinkUpdate( HyperlinkEvent event )
127       {
128         if ( event.getEventType() == HyperlinkEvent.EventType.ACTIVATED )
129         {
130           if ( "rssaction:RSSSettings".equals( event.getDescription() ) )
131           {
132             Ide.getSettings().showDialog( 
133               Ide.getMainWindow(), new String[] { "RSS News" }
134             );
135           }
136           else if ( "rssaction:ProxySettings".equals( event.getDescription() ) )
137           {
138             Ide.getSettings().showDialog(
139               Ide.getMainWindow(), new String[] { "Web Browser and Proxy" }
140             );
141           }
142         }
143       }
144       
145     };
146     _noChannelLabel.addHyperlinkListener( listener );
147     _statusBar.addHyperlinkListener( listener );
148     _statusBar.setBorder(
149       BorderFactory.createMatteBorder( 1, 0, 0, 0, 
150          UIManager.getColor( "controlShadow" ) )
151     );
152     
153     
154   }
155   
156   private void configureEditorPaneStyle( JEditorPane pane, Font font )
157   {
158     StyleSheet stylesheet = ((HTMLDocument) pane.getDocument()).getStyleSheet();
159     stylesheet.addRule(
160       "body {"
161       + "font-size: " + font.getSize() + "pt;"
162       + "font-family: " + font.getFamily() + ";"
163       + "font-weight: " + (font.isBold() ? "bold" : "normal") + ";"
164       + "font-style: " + (font.isItalic() ? "italic" : "normal") + ";"
165       + "color: #" + Integer.toHexString(_noChannelLabel.getForeground().getRGB() & 0xFFFFFF)
166       + "}"
167     );    
168   }
169   
170   public NiceTable getItemTable()
171   {
172     return _table;
173   }
174   
175   public JList getChannelList()
176   {
177     return _channelList;
178   }
179   
180   public Item[] getSelectedItems()
181   {
182     int[] rows = _table.getSelectedRowsInModel();
183     if ( rows == null )
184     {
185       return new Item[0];
186     }
187     Item[] items = new Item[ rows.length ];
188     for ( int i=0; i < rows.length; i++ )
189     {
190       items[ i ] = ((ChannelTableModel)_table.getBaseModel()).getItem( rows[ i ] );
191     }
192     
193     return items;
194   }
195   
196   public void setBusy( boolean isBusy )
197   {
198     if ( isBusy != _isBusy )
199     {
200       _isBusy = isBusy;
201       
202       if ( isBusy )
203       {
204         setStatusText( "Checking for news..." );
205       }
206       else
207       {
208         //setStatusText( "" );
209       }
210       firePropertyChange( "Busy", !isBusy, isBusy );
211     }
212   }
213   
214   public boolean isBusy()
215   {
216     return _isBusy;
217   }
218 
219   public void setStatusText( String text )
220   {
221     _statusBar.setText( text );
222   }
223   
224   private class ChannelsListModel extends AbstractListModel
225     implements ListListener
226   {
227     private final Channels _channels;
228   
229     public ChannelsListModel( Channels channels )
230     {
231       _channels = channels;
232       
233       _channels.addListListener( this );
234     }
235   
236     public Channels getChannels()
237     {
238       return _channels;
239     }
240   
241     public int getSize()
242     {
243       return _channels.getSubscribedChannels().size();
244     }
245     
246     public Object getElementAt( int index )
247     {
248       return _channels.getSubscribedChannels().get( index );
249     }
250     
251     public void itemsAdded( final ListEvent event )
252     {
253       EventQueue.invokeLater( new Runnable() {
254         public void run()
255         {
256           fireIntervalAdded( ChannelsListModel.this, event.getStartIndex(), 
257             event.getEndIndex() );
258         }
259       });
260     }
261     
262     public void itemsRemoved( final ListEvent event )
263     {
264       EventQueue.invokeLater( new Runnable() {
265         public void run()
266         {
267           fireIntervalRemoved( ChannelsListModel.this, event.getStartIndex(), 
268             event.getEndIndex() );
269         }
270       });
271       
272       
273       
274     }  
275     
276     public void itemsChanged( final ListEvent event )
277     {
278       EventQueue.invokeLater( new Runnable() {
279         public void run()
280         {
281           fireContentsChanged( ChannelsListModel.this, event.getStartIndex(), 
282             event.getEndIndex() );
283         }
284       });
285     }     
286   };  
287   
288   public void setChannels( final Channels channels )
289   {
290 
291     _channelList.setModel(  new ChannelsListModel( channels ) );
292     
293     if ( _channelList.getModel().getSize() > 0 )
294     {
295       _channelList.setSelectedIndex( 0);
296     }
297     else
298     {
299       setSelectedChannel( null );
300     }
301   }
302   
303   public Channels getChannels()
304   {
305     return ((ChannelsListModel)_channelList.getModel()).getChannels();
306   }
307   
308   private void listSelectionChanged()
309   {
310     Channel channel = (Channel) _channelList.getSelectedValue();
311     setSelectedChannel( channel );
312   }
313   
314   public Channel getSelectedChannel()
315   {
316     return (Channel) _channelList.getSelectedValue();
317   }
318   
319   private void setSelectedChannel( Channel channel )
320   {
321     if ( channel != null )
322     {
323       if ( _splitPane.getRightComponent() != _tableScrollPane )
324       {
325         _splitPane.setRightComponent( _tableScrollPane );
326         _splitPane.validate();
327       }
328       
329       ChannelTableModel model;
330       if ( _table.getBaseModel() instanceof ChannelTableModel )
331       {
332         model = (ChannelTableModel) _table.getBaseModel();
333         model.setChannel( channel );
334       }
335       else
336       {
337         model = new ChannelTableModel( channel );
338         _table.setModel( model );
339         _table.getTableHeader().getColumnModel().getColumn( 1 ).setHeaderRenderer(
340           new ReadToggleColumnHeaderRenderer() 
341         );
342       }
343       _table.setSortColumn( 2, false );
344       _table.autoSizeColumnsToFit();
345       _table.repaint();
346     }
347     else
348     {
349       setNoChannelLabelText();
350       if ( _splitPane.getRightComponent() != _noChannelLabel )
351       {
352         _splitPane.setRightComponent( _noChannelLabel );
353         _splitPane.validate();
354       }      
355       
356       // If there are no channels, provide a hyperlink to subscribe.
357 
358     }
359   }
360   
361   private void setNoChannelLabelText()
362   {
363     if ( getChannels().getSubscribedChannels().size() == 0 )
364     {
365       _noChannelLabel.setText( "<html><center>To read news, you must <a href=\"rssaction:RSSSettings\">subscribe to a channel</a></center></html>" );
366     }
367     else
368     {
369       _noChannelLabel.setText( "<html><center>To read news, you must select a channel</center></html>" );
370     }    
371   }
372   
373   
374   private class ChannelTableModel extends AbstractTableModel 
375     implements NiceTableModel, ChannelListener
376   {
377     private Channel _channel;
378     
379     ChannelTableModel( Channel channel )
380     {
381       setChannel( channel );
382     }
383     
384     public void setChannel( Channel channel )
385     {
386       if ( _channel != null )
387       {
388         _channel.removeChannelListener( this );
389       }
390       _channel = channel;
391       super.fireTableDataChanged();
392       _channel.addChannelListener( this );
393     }
394     
395     // ChannelListener interface
396     public void itemsChanged( ChannelEvent ce )
397     {
398       if ( ce.getItemIndex() == -1 )
399       {
400         super.fireTableDataChanged();
401       }
402       else
403       {
404         int row = ce.getItemIndex();
405         for ( int i=0; i < getColumnCount(); i++ )
406         {
407           fireTableCellUpdated( row, i );
408         }
409       }
410     }
411     
412   
413     public int getColumnCount()
414     {
415       return 3;
416     }
417     
418     public int getRowCount()
419     {
420       if ( _channel == null || _channel.getItems() == null ) 
421       {
422         return 0;
423       }
424       return _channel.getItems().size();
425     }
426     
427     public String getColumnName( int column )
428     {
429       switch ( column )
430       {
431         case 1: 
432           return " ";
433         case 2:
434           return "Date";
435         case 0:
436           return "Title";
437       }
438       return super.getColumnName( column );
439     }
440     
441     public Object getValueAt( int row, int column )
442     {
443       Item item = (Item) _channel.getItems().get( row );
444       
445       switch ( column )
446       {
447         case 1:
448           return Boolean.valueOf( item.isRead() );
449         case 2:
450           return item.getPublishDate();
451         case 0:
452           return item;
453       }
454       
455       return null;
456     }
457     
458     public Class getColumnClass( int column )
459     {
460       switch ( column )
461       {
462         case 1:
463           return Boolean.class;
464         case 2:
465           return Date.class;
466         case 0:
467           return Item.class;
468       }
469       
470       return super.getColumnClass( column );
471     }
472     
473     public Comparator getColumnSortComparator( int column )
474     {
475       return null;
476     }
477     
478     public Item getItem ( int row )
479     {
480       return (Item) getValueAt( row, 0 );
481     }
482     
483     public int getColumnAlignment( int column )
484     {
485       switch ( column )
486       {
487         case 1:
488           return SwingConstants.CENTER;
489         case 2:
490           return SwingConstants.LEFT;
491         case 0:
492           return SwingConstants.LEFT;
493       }
494       return 0;
495     }
496   }
497  
498   private class ChannelListCellRenderer extends DefaultListCellRenderer
499   {
500     private Font m_boldFont;
501     private Font m_normalFont;
502     
503     public ChannelListCellRenderer()
504     {
505       super();
506       m_normalFont = getFont();
507       m_boldFont = new Font( m_normalFont.getName(), Font.BOLD, 
508         m_normalFont.getSize() );
509     }
510   
511     public Component getListCellRendererComponent(
512       JList list,
513       Object value,
514       int index,
515       boolean isSelected,
516       boolean cellHasFocus)
517     {
518       Component c = super.getListCellRendererComponent( list, value, index, 
519         isSelected, cellHasFocus );
520       
521       Channel channel = (Channel) value;
522       if ( channel.getUnreadItemCount() > 0 )
523       {
524         setIcon( FOLDER_ICON_NEW );
525         setFont( m_boldFont );
526         setText( String.valueOf( value ) + " ("+ 
527           String.valueOf( channel.getUnreadItemCount() ) + ")" );
528       }
529       else
530       {
531         setIcon( FOLDER_ICON );
532         setFont( m_normalFont );
533       }
534       
535       return c;
536       
537     }
538   }
539   
540   private class ReadToggleColumnHeaderRenderer extends NiceTableHeaderRenderer
541   {
542     public Component getTableCellRendererComponent(
543       JTable param1, Object param2, boolean param3, boolean param4, int param5, int param6)
544     {
545       Component c = super.getTableCellRendererComponent( param1, param2, 
546         param3, param4, param5, param6 );
547       
548       setIcon( READ_TOGGLE_COLUMN_ICON );
549       
550       return c;
551       
552     }
553     
554   }
555 
556   private class ItemDateRenderer extends NiceTableCellRenderer
557   {
558     private Font m_normalFont;
559     private Font m_boldFont;
560     private DateFormat m_dateFormat;
561     
562     ItemDateRenderer()
563     {
564       super();
565       
566       m_normalFont = getFont();
567       m_boldFont = new Font(
568         m_normalFont.getName(), Font.BOLD, m_normalFont.getSize()
569       );
570       m_dateFormat = SimpleDateFormat.getDateTimeInstance();
571     }
572     
573     public Component getTableCellRendererComponent( JTable table, 
574       Object value, boolean param3, boolean param4, int row, int column)
575     {
576       Component c = super.getTableCellRendererComponent( table, 
577         value, param3, param4, row, column );
578         
579       Date date = (Date) value;
580       // Get the model row for this view row
581       int modelRow = ((NiceTable)table).convertRowToModelIndex( row );
582       Item item = (Item) ((NiceTable)table).getBaseModel().getValueAt( modelRow, 0 );
583       if ( item.isRead() )
584       {
585         setFont( m_normalFont );
586       }
587       else
588       {
589         setFont( m_boldFont );
590       }
591 
592       if ( date == null )
593       {
594         // Lame
595         setText( m_dateFormat.format( new Date() ) );
596       }
597       else
598       {
599         setText( m_dateFormat.format( date ) );
600       }
601       return c;
602     }
603     
604   }
605   
606   private class ItemRenderer extends NiceTableCellRenderer
607   {
608     private Font m_normalFont;
609     private Font m_boldFont;
610     
611     ItemRenderer()
612     {
613       super();
614       
615       m_normalFont = getFont();
616       m_boldFont = new Font(
617         m_normalFont.getName(), Font.BOLD, m_normalFont.getSize()
618       );
619     }
620     
621     public Component getTableCellRendererComponent( JTable table, 
622       Object value, boolean param3, boolean param4, int row, int column)
623     {
624       Component c = super.getTableCellRendererComponent( table, 
625         value, param3, param4, row, column );
626         
627       Item item = (Item) value;
628       if ( item.isRead() )
629       {
630         setIcon( ITEM_ICON );
631         setFont( m_normalFont );
632       }
633       else
634       {
635         setIcon( ITEM_UNREAD_ICON );
636         setFont( m_boldFont );
637       }
638     
639       return c;
640     }
641     
642   }
643   
644   private class ReadToggleRenderer extends NiceTableCellRenderer
645   {
646     public Component getTableCellRendererComponent( JTable table, 
647       Object value, boolean param3, boolean param4, int row, int column)
648     {
649       Component c = super.getTableCellRendererComponent( table, 
650         value, param3, param4, row, column );
651         
652       Boolean read = (Boolean) value;
653       setIcon ( read.booleanValue() ? READ_MESSAGE_DOT_ICON : 
654         UNREAD_MESSAGE_DOT_ICON );
655       setText( "" );
656     
657       return c;
658     }
659     
660   }
661   
662   private static final Icon FOLDER_ICON = 
663     new ImageIcon( NewsFeedPanel.class.getResource( "folder-closed.png" ) );
664   private static final Icon FOLDER_ICON_NEW =
665     new ImageIcon( NewsFeedPanel.class.getResource( "folder-new-closed.png" ) );
666   
667   private static final Icon READ_MESSAGE_DOT_ICON =
668     new ImageIcon( NewsFeedPanel.class.getResource( "readmail.gif" ) );
669   private static final Icon UNREAD_MESSAGE_DOT_ICON =
670     new ImageIcon( NewsFeedPanel.class.getResource( "unreadmail.png" ) );
671     
672   private static final Icon ITEM_ICON =
673     new ImageIcon( NewsFeedPanel.class.getResource( "message-news.png" ) );
674   private static final Icon ITEM_UNREAD_ICON =
675     new ImageIcon( NewsFeedPanel.class.getResource( "message-news-new.png" ) );
676     
677   private static final Icon READ_TOGGLE_COLUMN_ICON =
678     new ImageIcon( NewsFeedPanel.class.getResource( "readcol.png" ) );
679 }