Source code: com/fm/gui/fmContentPanel.java
1 /****************************************************************************
2 * Copyright (c) 2003 Andrew Duka | aduka@users.sourceforge.net
3 * All right reserved.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 ****************************************************************************/
11 package com.fm.gui;
12
13 import com.fm.transform.htmlTransformer;
14 import com.fm.transform.fmTransformException;
15 import com.fm.transform.Transformer;
16 import com.fm.rss.rssChannel;
17 import com.fm.rss.rssItem;
18 import com.fm.rss.rssDateHandler;
19 import com.fm.rss.comparator.*;
20 import com.fm.rss.filter.RssItemFilter;
21 import com.fm.rss.filter.rssFilterFactory;
22 import com.fm.update.fmEvent;
23
24
25 import javax.swing.*;
26 import javax.swing.Timer;
27 import javax.swing.tree.TreePath;
28 import javax.swing.table.*;
29 import javax.swing.event.ListSelectionListener;
30 import javax.swing.event.ListSelectionEvent;
31 import java.awt.*;
32 import java.awt.event.*;
33 import java.util.*;
34
35 /**
36 * Contains general channel information and items representation
37 */
38 public class fmContentPanel extends JPanel implements ActionListener
39 {
40 /** Top panel with channel title, creation date, show mode */
41 private JPanel infoPane;
42 /** Navigation and settings panel */
43 private JPanel navPane;
44
45 /** txt/html content pane for the non splitted view */
46 private JEditorPane htmlContentPane;
47 /** Content panel for splitted view mode */
48 private fmSplitViewPane splitViewPane;
49
50 /** Default transformer */
51 private Transformer transformer;
52
53 /** Current channel (last channel set with setChannel) */
54 private rssChannel currentChannel;
55 /** Channel currently being displayed */
56 private rssChannel activeChannel;
57
58 /** Lock flag for setChannel method */
59 private boolean setChannelLocked;
60
61 /**
62 fmHyperlinkRouter
63
64 /** Browse history vector */
65 private Vector browseHistory;
66 private static int HISTORY_MAX_SIZE = 10;
67 private int historyPointer;
68
69 // Browse buttons
70 private JButton nextButton;
71 private JButton prevButton;
72
73 // Category tree (required for the GUI components synching)
74 private fmCategoryTree categoryTree;
75
76
77 // TITLES
78 private JLabel channelTitle;
79
80 public static String DEFAULT_GREETING = "<h3>Hello!</h3>For more information please visit " +
81 "<a href='http://feedman.sourceforge.net'>feedMan web site</a>" +
82 " or open <a href='http://channel/1'>help channel</a>.";
83
84
85 Properties runtimeProperties;
86
87 /**
88 * Default constructor
89 */
90 public fmContentPanel(Properties props,
91 fmHyperlinkRouter linkRouter,
92 fmCategoryTree tree)
93 {
94 super();
95
96 this.setLayout(new BorderLayout());
97
98 // current runtitme properties
99 runtimeProperties = props;
100
101 // category tree
102 categoryTree = tree;
103
104 // building up default transformer
105 final int showMode;
106
107 if (runtimeProperties.containsKey(fmGuiConstants.DEFAULT_SHOW_FILTER))
108 {
109 showMode = Integer.parseInt(runtimeProperties.getProperty(fmGuiConstants.DEFAULT_SHOW_FILTER));
110 }
111 else
112 {
113 showMode = Integer.parseInt(fmGuiConstants.ALL_SHOW_FILTER);
114 runtimeProperties.put(fmGuiConstants.DEFAULT_SHOW_FILTER, fmGuiConstants.ALL_SHOW_FILTER);
115 }
116
117 setChannelLocked = false;
118
119 // init browse history
120 browseHistory = new Vector();
121 historyPointer = -1;
122
123 // default titles
124 channelTitle = new JLabel("");
125 channelTitle.setFont(fmUITheme.CHANNEL_TITLE_FONT);
126
127 // INFO PANEL (channel title, date created, etc)
128 infoPane = new JPanel(new BorderLayout());
129 Dimension infoDim = new Dimension(300,40);
130
131 infoPane.setPreferredSize(infoDim);
132 infoPane.setMinimumSize(infoDim);
133 infoPane.setBackground(fmUITheme.LIGHT_BACKGROUND_COLOR);
134 infoPane.add(channelTitle, BorderLayout.CENTER);
135
136 // CONTENT VIEW PANELS
137 htmlContentPane = null;
138 splitViewPane = null;
139
140
141 // Option panel (will be added to infoPanel)
142 JPanel showOptPanel = new JPanel();
143 showOptPanel.setBackground(fmUITheme.LIGHT_BACKGROUND_COLOR);
144 showOptPanel.add(Box.createHorizontalStrut(15));
145 showOptPanel.add(new JLabel(fmUITheme.getString("label.show")));
146
147
148
149
150 ///////////////////////////////////////////////////////////////////////
151 // SHOW FILTERS
152 final HashMap showFilters = new HashMap();
153 RssItemFilter f;
154 {
155 // SORTING FILTER
156 f = rssFilterFactory.createSortingFilter(rssFilterFactory.DATE_COMPARATOR);
157 f.setReverseMode(true);
158 showFilters.put(fmGuiConstants.ALL_SHOW_FILTER,f);
159
160 // SHOW UNREAD FILTER
161 f = rssFilterFactory.createReadFilter();
162 f.setReverseMode(true);
163 showFilters.put(fmGuiConstants.UNREAD_SHOW_FILTER,f);
164
165 // SHOW LAST 5 FILTER
166 f = rssFilterFactory.createReverseItemCountFilter(5, rssFilterFactory.DATE_COMPARATOR);
167 f.setReverseMode(true);
168 showFilters.put(fmGuiConstants.LAST5_SHOW_FILTER,f);
169
170 // SHOW LAST 10 FILTER
171 f = rssFilterFactory.createReverseItemCountFilter(10, rssFilterFactory.DATE_COMPARATOR);
172 f.setReverseMode(true);
173 showFilters.put(fmGuiConstants.LAST10_SHOW_FILTER,f);
174 }
175
176 // DEFAULT TRANSFORMER
177 f = (RssItemFilter) showFilters.get((new Integer(showMode)).toString());
178 transformer = new htmlTransformer(f);
179 transformer.setParamValue(htmlTransformer.CONTENT_FONT,
180 fmUITheme.CONTENT_FONT);
181
182
183 // SHOW PROPERTIES
184 final JComboBox showCombo = new JComboBox(new Object[]{fmUITheme.getString("label.show.all"),
185 fmUITheme.getString("label.show.new"),
186 fmUITheme.getString("label.show.last5"),
187 fmUITheme.getString("label.show.last10")});
188
189 ///////////////////////////////////////////////////////////////////////
190 // CHANGE SHOW MODE LISTENER
191 showCombo.addItemListener(new ItemListener() {
192 public void itemStateChanged(ItemEvent e) {
193 String sItem = (String) showCombo.getSelectedItem();
194 RssItemFilter f;
195 if (sItem.equals(fmUITheme.getString("label.show.all"))) {
196 f = (RssItemFilter) showFilters.get(fmGuiConstants.ALL_SHOW_FILTER);
197 runtimeProperties.put(fmGuiConstants.DEFAULT_SHOW_FILTER, fmGuiConstants.ALL_SHOW_FILTER);
198 }
199 else if (sItem.equals(fmUITheme.getString("label.show.new"))) {
200 f = (RssItemFilter) showFilters.get(fmGuiConstants.UNREAD_SHOW_FILTER);
201 runtimeProperties.put(fmGuiConstants.DEFAULT_SHOW_FILTER, fmGuiConstants.UNREAD_SHOW_FILTER);
202 }
203 else if (sItem.equals(fmUITheme.getString("label.show.last5"))) {
204 f = (RssItemFilter) showFilters.get(fmGuiConstants.LAST5_SHOW_FILTER);
205 runtimeProperties.put(fmGuiConstants.DEFAULT_SHOW_FILTER, fmGuiConstants.LAST5_SHOW_FILTER);
206 }
207 else {
208 f = (RssItemFilter) showFilters.get(fmGuiConstants.LAST10_SHOW_FILTER);
209 runtimeProperties.put(fmGuiConstants.DEFAULT_SHOW_FILTER, fmGuiConstants.LAST10_SHOW_FILTER);
210 }
211
212
213 if (transformer != null)
214 transformer.setItemFilter(f);
215
216 if (splitViewPane != null)
217 splitViewPane.setItemFilter(f);
218
219 if (htmlContentPane != null) {
220 try {
221 htmlContentPane.setText((String)transformer.transform(activeChannel));
222 }
223 catch (fmTransformException te) {
224 htmlContentPane.setText(fmUITheme.getLocalizedStrings().getString("msg.cant_transform"));
225 }
226 }
227 }
228 });
229 showCombo.setSelectedIndex(showMode);
230
231
232
233 // L&F
234 showCombo.setMaximumSize(showCombo.getPreferredSize());
235 showCombo.setAlignmentX(RIGHT_ALIGNMENT);
236
237 showOptPanel.add(showCombo);
238 infoPane.add(showOptPanel, BorderLayout.EAST);
239 infoPane.setVisible(false);
240 infoPane.setBorder(fmUITheme.FM_THIN_BORDER);
241
242 add(infoPane, BorderLayout.NORTH);
243
244 ///////////////////////////////////////////////////////////////////////
245 // CARD PANE: contains two panes with different content views
246 final JPanel cardPane = new JPanel(new CardLayout());
247 splitViewPane = new fmSplitViewPane(transformer, linkRouter);
248 splitViewPane.setBorder(BorderFactory.createEmptyBorder());
249
250
251 ///////////////////////////////////////////////////////////////////////
252 // TXT/HTML VEW PANEL
253 htmlContentPane = new JEditorPane("text/html",
254 fmContentPanel.DEFAULT_GREETING);
255
256 htmlContentPane.setEditable(false);
257 htmlContentPane.setAutoscrolls(false);
258 htmlContentPane.addHyperlinkListener(linkRouter);
259
260 // Setting default view mode if not set before
261 if (runtimeProperties.containsKey(fmGuiConstants.PREFFERED_VIEW_MODE) == false)
262 runtimeProperties.put(fmGuiConstants.PREFFERED_VIEW_MODE,
263 fmGuiConstants.SOLID_VIEW_MODE);
264
265 boolean isSplittedPreffered = runtimeProperties.
266 getProperty(fmGuiConstants.PREFFERED_VIEW_MODE).
267 equals(fmGuiConstants.SPLITTED_VIEW_MODE);
268
269 // Order of the view panes depends on preffered view mode
270 if (isSplittedPreffered) {
271 cardPane.add(splitViewPane, "splitted");
272 cardPane.add(new JScrollPane(htmlContentPane),"solid");
273 }
274 else {
275 cardPane.add(new JScrollPane(htmlContentPane),"solid");
276 cardPane.add(splitViewPane, "splitted");
277 }
278
279 // adding view panes
280 add(cardPane, BorderLayout.CENTER);
281
282 ///////////////////////////////////////////////////////////////////////
283 // NAVIGATION PANEL
284 navPane = new JPanel();
285 navPane.setLayout(new BoxLayout(navPane,BoxLayout.X_AXIS));
286 navPane.setBackground(fmUITheme.LIGHT_BACKGROUND_COLOR);
287
288 Dimension navDim = new Dimension(200,35);
289 navPane.setPreferredSize(navDim);
290 navPane.setMinimumSize(navDim);
291
292 // first gap
293 navPane.add(Box.createHorizontalStrut(15));
294
295
296 // HISTORY BUTTONS
297
298 // LISTENER
299 ActionListener historyButtonListener = new ActionListener() {
300 public void actionPerformed (ActionEvent e) {
301 if ((JButton)e.getSource() == nextButton)
302 showNextChannel();
303 else if ((JButton)e.getSource() == prevButton)
304 showPreviousChannel();
305 }
306 };
307
308 prevButton = fmUITheme.getButtonInstance(fmUITheme.NAVIGATION_PREV_ICON,
309 "button.nav_prev.tooltip",
310 fmUITheme.LIGHT_BACKGROUND_COLOR,
311 Component.LEFT_ALIGNMENT);
312 prevButton.addActionListener(historyButtonListener);
313 prevButton.setEnabled(false);
314
315 navPane.add(prevButton);
316 navPane.add(Box.createHorizontalStrut(5));
317
318 // GO FORWARD HISTORY BUTTON ACTION
319 nextButton = fmUITheme.getButtonInstance(fmUITheme.NAVIGATION_NEXT_ICON,
320 "button.nav_next.tooltip",
321 fmUITheme.LIGHT_BACKGROUND_COLOR,
322 Component.LEFT_ALIGNMENT);
323 nextButton.addActionListener(historyButtonListener);
324 nextButton.setEnabled(false);
325 navPane.add(nextButton);
326
327
328 // horizonatl glue
329 navPane.add(Box.createHorizontalGlue());
330
331
332 // CHANGE VIEW BUTTON
333 JButton button;
334 if (isSplittedPreffered) {
335 button = fmUITheme.getButtonInstance(fmUITheme.SPLITTED_VIEW_ICON,
336 "button.splitted_view.tooltip",
337 fmUITheme.LIGHT_BACKGROUND_COLOR,
338 Component.CENTER_ALIGNMENT);
339 }
340 else {
341 button = fmUITheme.getButtonInstance(fmUITheme.SOLID_VIEW_ICON,
342 "button.solid_view.tooltip",
343 fmUITheme.LIGHT_BACKGROUND_COLOR,
344 Component.CENTER_ALIGNMENT);
345 }
346
347 final JButton prevItemButton = fmUITheme.getButtonInstance(fmUITheme.NAVIGATION_UP_ICON,
348 "button.nav_up.tooltip",
349 fmUITheme.LIGHT_BACKGROUND_COLOR,
350 Component.RIGHT_ALIGNMENT);
351
352 final JButton nextItemButton = fmUITheme.getButtonInstance(fmUITheme.NAVIGATION_DOWN_ICON,
353 "button.nav_down.tooltip",
354 fmUITheme.LIGHT_BACKGROUND_COLOR,
355 Component.RIGHT_ALIGNMENT);
356
357 ///////////////////////////////////////////////////////////////////////
358 //ADDING VIEW MODE BUTTON
359 JLabel tLabel = new JLabel(fmUITheme.getString("label.view_mode"));
360 tLabel.setAlignmentX(CENTER_ALIGNMENT);
361 navPane.add(tLabel);
362 navPane.add(button);
363 navPane.add(Box.createHorizontalGlue());
364
365 // VIEW MODE ACTION LISTENER
366 button.addActionListener(new ActionListener() {
367 public void actionPerformed(ActionEvent e)
368 {
369 ((CardLayout)cardPane.getLayout()).next(cardPane);
370 if (splitViewPane.isVisible()) {
371 ((JButton)e.getSource()).setIcon(fmUITheme.SPLITTED_VIEW_ICON);
372 runtimeProperties.put(fmGuiConstants.PREFFERED_VIEW_MODE,
373 fmGuiConstants.SPLITTED_VIEW_MODE);
374 nextItemButton.setEnabled(true);
375 prevItemButton.setEnabled(true);
376 }
377 else {
378 ((JButton)e.getSource()).setIcon(fmUITheme.SOLID_VIEW_ICON);
379 runtimeProperties.put(fmGuiConstants.PREFFERED_VIEW_MODE,
380 fmGuiConstants.SOLID_VIEW_MODE);
381 nextItemButton.setEnabled(false);
382 prevItemButton.setEnabled(false);
383 }
384 }
385 });
386
387
388 ///////////////////////////////////////////////////////////////////////
389 // UP AND DOWN BUTTONS
390 prevItemButton.addActionListener(new ActionListener() {
391 public void actionPerformed(ActionEvent e) {
392 splitViewPane.showPreviousItem();
393 }
394 });
395 prevItemButton.setEnabled(isSplittedPreffered);
396 navPane.add(prevItemButton);
397 navPane.add(Box.createHorizontalStrut(5));
398
399
400 nextItemButton.addActionListener(new ActionListener() {
401 public void actionPerformed(ActionEvent e) {
402 splitViewPane.showNextItem();
403 }
404 });
405 nextItemButton.setEnabled(isSplittedPreffered);
406 navPane.add(nextItemButton);
407 navPane.add(Box.createHorizontalStrut(15));
408
409 navPane.setVisible(false);
410 add(navPane, BorderLayout.SOUTH);
411 }
412
413
414 /**
415 * Set channel to display
416 *
417 * <p>This method will update browse history and set specified channel
418 * as current channel for further operations only if setChannelLocked flag
419 * is false</p>
420 *
421 * @param channel rssChannel entity
422 */
423 public void setChannel(rssChannel channel)
424 {
425 if (setChannelLocked)
426 return;
427
428 // SAVING TO HISTORY
429 if (currentChannel != null)
430 addChannelToHistory(currentChannel);
431
432 if (browseHistory.size() > 0)
433 prevButton.setEnabled(true);
434
435 currentChannel = channel;
436
437 showChannel(channel);
438
439 navPane.setVisible(true);
440 infoPane.setVisible(true);
441 }
442
443 /**
444 * Show specified channel using content view panels
445 *
446 * @param channel rssChannel instance (if null current activeChannel will be used)
447 */
448 private void showChannel(rssChannel channel)
449 {
450 try
451 {
452 if (channel != null)
453 activeChannel = channel;
454
455 splitViewPane.setChannel(activeChannel);
456 htmlContentPane.setText((String)transformer.transform(activeChannel));
457 channelTitle.setText(" " + activeChannel.getTitle());
458 runtimeProperties.put(fmGuiConstants.LAST_OPEN_CHANNEL_ID, (new Integer(channel.getID())).toString());
459 }
460 catch(fmTransformException te)
461 {
462 te.printStackTrace();
463 htmlContentPane.setText(fmUITheme.getLocalizedStrings().getString("msg.cant_transform"));
464 }
465 }
466
467 /**
468 * Puts specified object into history Vector.
469 * If history size exceeds HISTORY_MAX_SIZE value, the head entry
470 * will be removed and the current value will be inserted into
471 * the tail (FIFO)
472 *
473 * @param ch channel to put into history
474 */
475 public void addChannelToHistory(rssChannel ch)
476 {
477 if (browseHistory.size() >= HISTORY_MAX_SIZE)
478 browseHistory.removeElementAt(0);
479
480 browseHistory.addElement(ch);
481 historyPointer = browseHistory.size();
482 }
483
484
485 /**
486 * Show previous channel in the history
487 *
488 */
489 public void showPreviousChannel()
490 {
491 if (browseHistory.size() == 0)
492 return;
493 else
494 {
495 if (historyPointer > 0)
496 --historyPointer;
497
498 if (historyPointer == 0)
499 prevButton.setEnabled(false);
500
501 rssChannel ch = (rssChannel) browseHistory.get(historyPointer);
502
503 if (ch == null)
504 return;
505
506 fmTreeNode tNode = categoryTree.findChannelNode(categoryTree.getRootNode(),
507 ch.getID());
508 if (tNode != null)
509 {
510 setChannelLocked = true;
511 categoryTree.setSelectionPath(new TreePath(tNode.getPath()));
512 // Unlocknig setChannel and history
513 setChannelLocked = false;
514 }
515
516 showChannel(ch);
517 nextButton.setEnabled(true);
518 }
519 }
520
521 /**
522 * Show next channel in the history
523 *
524 */
525 public void showNextChannel()
526 {
527 int brSize = browseHistory.size();
528
529 if (historyPointer < brSize)
530 ++historyPointer;
531
532 rssChannel ch;
533
534 if (historyPointer == brSize) {
535 ch = currentChannel;
536 nextButton.setEnabled(false);
537 }
538 else {
539 ch = (rssChannel) browseHistory.get(historyPointer);
540 }
541
542 if (ch == null) // emtpy history or error
543 return;
544
545 fmTreeNode tNode = categoryTree.findChannelNode(categoryTree.getRootNode(),
546 ch.getID());
547 if (tNode != null)
548 {
549 // locking history because setSelectionPath will cause setChannel and will
550 // change browse history in this way.
551 setChannelLocked = true;
552 categoryTree.setSelectionPath(new TreePath(tNode.getPath()));
553 // Unlocknig setChannel and history
554 setChannelLocked = false;
555 }
556 showChannel(ch);
557 prevButton.setEnabled(true);
558 }
559
560 /** Show previous channel's item.
561 *
562 * Does nothing if split view pane isn't visible
563 */
564 public void showPreviousItem()
565 {
566 if (splitViewPane.isVisible())
567 splitViewPane.showPreviousItem();
568 }
569
570 /** Show next channel's item.
571 *
572 * Does nothing if split view pane isn't visible
573 */
574 public void showNextItem()
575 {
576 if (splitViewPane.isVisible())
577 splitViewPane.showNextItem();
578 }
579
580
581
582
583 /**
584 * Renderer for table header
585 */
586 public class fmHeaderTableHeaderRenderer extends DefaultTableCellRenderer
587 {
588 public fmHeaderTableHeaderRenderer() { super(); }
589
590 public Component getTableCellRendererComponent(JTable table,
591 Object value,
592 boolean isSelected,
593 boolean hasFocus,
594 int row,
595 int column)
596 {
597 RssItemFilter filter = ((fmSplitViewTableModel) table.getModel()).getFilter();
598 RssItemComparator c = filter.getItemComparator();
599 JButton icon = fmUITheme.getButtonInstance((filter.isInReverseMode()) ? fmUITheme.SORT_UP_ICON : fmUITheme.SORT_DOWN_ICON,
600 null,
601 fmUITheme.LIGHT_BACKGROUND_COLOR,
602 Component.RIGHT_ALIGNMENT);
603
604 // L&F
605
606
607 // DEFAULT VALUE
608 setValue(value);
609 setAlignmentX(LEFT_ALIGNMENT);
610 JPanel a = new JPanel(new BorderLayout());
611 a.add(this, BorderLayout.WEST);
612 a.setBorder(fmUITheme.FM_THIN_BORDER);
613 a.setBackground(fmUITheme.LIGHT_BACKGROUND_COLOR);
614 a.setForeground(fmUITheme.SELECTED_TEXT_FOREGROUND);
615
616 switch (column) {
617 case fmGuiConstants.TITLE_COLUMN_POSITION: // TITLE COLUMN
618 if (c instanceof rssTitleComparator)
619 a.add(icon,BorderLayout.EAST);
620 break;
621
622 case fmGuiConstants.DATE_COLUMN_POSITION: // DATE COLUMN
623 if (c instanceof rssDateComparator)
624 a.add(icon,BorderLayout.EAST);
625 break;
626
627 case fmGuiConstants.AUTHOR_COLUMN_POSITION: // AUTHOR COLUMN
628 if (c instanceof rssAuthorComparator)
629 a.add(icon,BorderLayout.EAST);
630 break;
631
632 case fmGuiConstants.SUBJECT_COLUMN_POSITION: // SUBJECT COLUMN
633 if (c instanceof rssSubjectComparator)
634 a.add(icon,BorderLayout.EAST);
635 break;
636 }
637
638 return a;
639 }
640 }
641
642 /**
643 * Renderer for table cells
644 */
645 public class fmHeaderTableCellRenderer extends DefaultTableCellRenderer
646 {
647 public fmHeaderTableCellRenderer() { super(); }
648
649 public Component getTableCellRendererComponent(JTable table,
650 Object value,
651 boolean isSelected,
652 boolean hasFocus,
653 int row,
654 int column)
655 {
656
657 rssItem item = (rssItem) ((fmSplitViewTableModel) table.getModel()).getObjectAt(row);
658 // VALUE
659 setValue(value);
660 // FONT
661 if (item.isFresh) {
662 setFont(table.getFont().deriveFont(Font.BOLD));
663 }
664 else {
665 setFont(table.getFont());
666 }
667
668 int borderS = Math.round(getFont().getSize() / 4);
669 setBorder(BorderFactory.createEmptyBorder(borderS, borderS, borderS, borderS));
670
671
672 // BACKGROUND
673 if (isSelected) {
674 setBackground(fmUITheme.SELECTION_BACKGROUND_COLOR);
675 setForeground(fmUITheme.SELECTED_TEXT_FOREGROUND);
676 }
677 else {
678 setBackground(table.getBackground());
679 }
680
681 return this;
682 }
683 }
684
685
686 /** Extends default JTable
687 *
688 */
689 private class fmHeaderTable extends JTable
690 {
691
692 fmHeaderTableCellRenderer cellRenderer;
693
694 /**
695 * Constructor
696 */
697 public fmHeaderTable()
698 {
699 super();
700 cellRenderer = new fmHeaderTableCellRenderer();
701 }
702
703 /**
704 * Constructor
705 */
706 public fmHeaderTable(fmSplitViewTableModel model)
707 {
708 super(model);
709 cellRenderer = new fmHeaderTableCellRenderer();
710 }
711
712 /**
713 * Rreturns renderer for the specified cell
714 *
715 * @param row
716 * @param column
717 * @return
718 */
719 public TableCellRenderer getCellRenderer(int row, int column)
720 {
721 if (dataModel instanceof fmSplitViewTableModel)
722 {
723 // FRESH ITEM
724 return cellRenderer;
725 }
726
727 // return default renderer
728 return (TableCellRenderer)defaultRenderersByColumnClass.get(dataModel.getColumnClass(column));
729
730 }
731 }
732
733 /**
734 * Represents spiltted view
735 */
736 private class fmSplitViewPane extends JPanel
737 {
738 /** Displays channel posts's titles, dates, authors */
739 //private fmSplitViewTableModel headerTableModel;
740 private fmHeaderTable headerTable;
741 /** Displays post contents */
742 private JEditorPane contentPane;
743 /** Default transformer */
744 private Transformer transformer;
745
746 public fmSplitViewPane(Transformer trf, fmHyperlinkRouter router)
747 {
748 super(new CardLayout());
749
750 transformer = trf;
751
752 JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
753 splitPane.setBorder(fmUITheme.FM_THIN_BORDER);
754 splitPane.setBackground(fmUITheme.LIGHT_BACKGROUND_COLOR);
755
756 // CONTENT
757 contentPane = new JEditorPane("text/html", fmContentPanel.DEFAULT_GREETING);
758 contentPane.setEditable(false);
759 contentPane.setBorder(BorderFactory.createEmptyBorder());
760
761 // Hyperlink router
762 contentPane.addHyperlinkListener(router);
763
764 // HEADERS
765 fmSplitViewTableModel headerTableModel = new fmSplitViewTableModel(null,
766 transformer.getItemFilter());
767 headerTable = new fmHeaderTable(headerTableModel);
768 headerTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
769 headerTable.setAutoscrolls(true);
770 headerTable.getTableHeader().setBackground(fmUITheme.LIGHT_BACKGROUND_COLOR);
771
772 ////////////////////////////////////////////////////////////
773 // FRESHNESS TIMER
774 final Timer freshnessTimer = new Timer(2000, new ActionListener(){
775 public void actionPerformed(ActionEvent e) {
776 int selected = headerTable.getSelectedRow();
777 if (selected > -1) {
778 rssItem tI = (rssItem) ((fmSplitViewTableModel) headerTable.getModel()).getObjectAt(selected);
779 if (tI != null) {
780 tI.isFresh = false;
781 ((fmSplitViewTableModel) headerTable.getModel()).fireTableRowsUpdated(selected,selected);
782 categoryTree.repaint();
783 }
784 }
785 ((Timer)e.getSource()).stop();
786 }
787 });
788 ///////////////////////////////////////////////////////////////////
789
790 ///////////////////////////////////////////////////////////////////
791 // SELECTION LISTENER
792 headerTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
793 public void valueChanged(ListSelectionEvent e) {
794 int selected = headerTable.getSelectedRow();
795
796 // stopping previous timer run, which belongs to another item
797 freshnessTimer.stop();
798
799 fmSplitViewTableModel tModel = ((fmSplitViewTableModel)headerTable.getModel());
800 if (selected > -1) {
801 showItem((rssItem) tModel.getObjectAt(selected));
802 }
803 else if (headerTable.getRowCount() > 0) { //setting to first item if channel has been changed
804 headerTable.getSelectionModel().setSelectionInterval(0,0);
805 }
806 freshnessTimer.start();
807 }
808 });
809
810 // L&F
811 headerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
812 headerTable.setShowGrid(false);
813 headerTable.setSelectionBackground(fmUITheme.SELECTION_BACKGROUND_COLOR);
814 headerTable.setSelectionForeground(fmUITheme.BLACK_COLOR);
815
816 ////////////////////////////////////////////////////////////////////////
817 // SCROLL PANE FOR HEADER TABLE
818 JScrollPane headerScroll = new JScrollPane(headerTable);
819 headerScroll.getViewport().setBackground(fmUITheme.WHITE_COLOR);
820
821 ////////////////////////////////////////////////////////////////////////
822 // SETTING UP COLUMN WIDTHS
823 Dimension tDim = headerScroll.getPreferredSize();
824 int fCWidth = (int) Math.round(tDim.width / 2.2); //
825 int sCWidth = (int) Math.round(tDim.width / 3.3); // 30%
826 // todo remove hardcoded staff
827 headerTable.getColumnModel().getColumn(0).setPreferredWidth(fCWidth);
828 headerTable.getColumnModel().getColumn(1).setPreferredWidth(sCWidth);
829
830
831
832 ////////////////////////////////////////////////////////////////////////
833 // MOUSE LISTENER FOR TABLE COLUMN HEADERS
834 MouseAdapter headerMouseListener = new MouseAdapter() {
835 public void mouseClicked(MouseEvent e) {
836 TableColumnModel columnModel = headerTable.getColumnModel();
837 int column = headerTable.convertColumnIndexToModel(columnModel.getColumnIndexAtX(e.getX()));
838 if (column != -1) {
839 ((fmSplitViewTableModel) headerTable.getModel()).sortByColumn(column);
840 }
841 }
842 };
843
844 headerTable.getTableHeader().addMouseListener(headerMouseListener);
845 headerTable.getTableHeader().setDefaultRenderer(new fmHeaderTableHeaderRenderer());
846
847 splitPane.setTopComponent(headerScroll);
848 splitPane.setBottomComponent(new JScrollPane(contentPane));
849 splitPane.setDividerLocation(200); // Default divider location
850 add(splitPane, "");
851 }
852
853 /**
854 * Sets channel for display
855 *
856 * @param ch
857 */
858 public void setChannel(rssChannel ch)
859 {
860 headerTable.getSelectionModel().clearSelection();
861 ((fmSplitViewTableModel)headerTable.getModel()).setChannel(ch);
862
863 if (ch.getItemVector().size() > 0) {
864 headerTable.getSelectionModel().setSelectionInterval(0,0); //auto selecting first item
865 }
866 else {
867 contentPane.setText("");
868 }
869 repaint();
870 }
871
872 /**
873 * Set current item filter and update accordingly
874 *
875 * @param f
876 */
877 public void setItemFilter(RssItemFilter f) {
878 ((fmSplitViewTableModel) headerTable.getModel()).setFilter(f);
879 }
880
881 /**
882 * Transform and display specified channel item
883 */
884 public void showItem(rssItem item) {
885 try {
886 contentPane.setText((String) transformer.transform(item));
887 }
888 catch (fmTransformException fme) {
889 fme.printStackTrace();
890 contentPane.setText(fmUITheme.getLocalizedStrings().getString("msg.cant_transform"));
891 }
892 }
893
894 /**
895 * Show next channel item.
896 * Note that this implementation does looped displaying:
897 * if current item is last the first item will be shown instead of nothing
898 */
899 public void showNextItem() {
900 int selected = headerTable.getSelectedRow();
901 if (selected < (headerTable.getRowCount() - 1))
902 ++selected;
903 else
904 selected = 0;
905
906 headerTable.setRowSelectionInterval(selected,selected);
907 headerTable.scrollRectToVisible(headerTable.getCellRect(selected,0,true));
908 showItem((rssItem) ((fmSplitViewTableModel)headerTable.getModel()).
909 getObjectAt(selected));
910 }
911
912 /**
913 * Show previous channel item.
914 * Note that this implementation does looped displaying:
915 * if current item is last the first item will be shown instead of nothing
916 */
917 public void showPreviousItem() {
918 int selected = headerTable.getSelectedRow();
919 if (selected > 0)
920 --selected;
921 else
922 selected = (headerTable.getRowCount() - 1);
923
924 headerTable.setRowSelectionInterval(selected,selected);
925 headerTable.scrollRectToVisible(headerTable.getCellRect(selected,0,true));
926 showItem((rssItem) ((fmSplitViewTableModel)headerTable.getModel()).
927 getObjectAt(selected));
928 }
929
930 /**
931 * Overrides parent's method
932 */
933 public void performLookAndFeelUpdate() {
934 headerTable.setFont(fmUITheme.HEADERS_FONT);
935 contentPane.setFont(fmUITheme.CONTENT_FONT);
936 repaint();
937 }
938 }
939
940 /**
941 * Updates L&F settings using runtime properties and fmUITheme settings
942 */
943 public void performLookAndFeelUpdate() {
944 transformer.setParamValue(htmlTransformer.CONTENT_FONT,
945 fmUITheme.CONTENT_FONT);
946 splitViewPane.performLookAndFeelUpdate();
947 showChannel(activeChannel); // regenerates view panel contents
948 }
949
950
951 /**
952 * Action listener's methods
953 */
954 public void actionPerformed(ActionEvent e)
955 {
956 if (e.getActionCommand().equals(fmEvent.UPDATE_FINISHED))
957 {
958 rssChannel ch = (rssChannel)e.getSource();
959
960 if (((fmEvent)e).getStatus() == fmEvent.SUCCESS_STATUS)
961 {
962 if (ch.getID() == activeChannel.getID())
963 showChannel(ch);
964
965 }
966 }
967
968 }
969
970 }