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
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
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
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
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
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
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 }