/**
* Copyright 1999-2009 The Pegadi Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A display model for article lists.
* @author Jørgen Binningsbø <jb@underdusken.no>
* @author Håvard Wigtil <havardw at pvv.org>
*/
package org.pegadi.articlelist;
import org.pegadi.model.Article;
import org.pegadi.model.ArticleStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
/**
* A groupable and sortable list of articles. <p>
* <p/>
* Implemented by creating a sorted JTable for each group of
* article. The user can choose between different rules for grouping
* (@see Grouper) and sorting (@see GenericComparator)
*
* @author <a href="mailto:jb@underdusken.no">Jørgen Binningsbø</a>
* @version $Id$
* @see JTable
*/
public class ArticleList extends JPanel implements Scrollable {
protected Comparator comparator;
protected Comparator defaultComparator = new ArticleByArticleNameComparator();
protected Grouper grouper;
protected Grouper defaultGrouper = new DepartmentGrouper();
protected List<Article> baseArticles;
protected GroupedArticles groupedArticles;
protected ArticleTableModel[] groupModels;
protected JTable[] groupTables;
protected JLabel[] groupLabels;
protected JLabel[] groupSums;
protected ListSelectionListener[] listSelListeners;
protected ArticleSelectionListener[] artSelListeners;
protected ArticleSelectionListener artSelListener;
protected Article selectedArticle;
// two variables for creating a custom listener on this object
protected ChangeEvent changeEvent = null;
protected EventListenerList listenerList = new EventListenerList();
/**
* Translatable string resources.
*/
protected static ResourceBundle strings = ResourceBundle.getBundle("org.pegadi.articlelist.ArticleListStrings");
protected Vector actionListeners;
/**
* Mouse adapter for when a popup menu is set.
*/
protected MouseAdapter popupListener;
/**
* The popup menu.
*/
protected JPopupMenu popupMenu;
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* Constructor.
* Constructs a new ArticleList with all columns, with the given Comparator.
*/
public ArticleList(List<Article> articles, Comparator comparator) {
this();
init(articles, comparator, defaultGrouper);
}
public ArticleList(List<Article> articles, Grouper grouper) {
super();
init(articles, defaultComparator, grouper);
}
/**
* Contructs a new ArticleList with the default Comparator (@see
* ArticleNameComparator) and Grouper (@see NoGrouper)
*
* @param articles an <code>List<Article></code> value
*/
public ArticleList(List<Article> articles) {
this();
init(articles, defaultComparator, defaultGrouper);
}
/**
* Constructs a new ArticleList without any articles. The
* programmer should take care to call regroup() afterwards with
* the articles she would like to put into the list.
*/
public ArticleList() {
super();
this.grouper = defaultGrouper;
this.comparator = defaultComparator;
this.actionListeners = new Vector(2, 2);
}
/**
* Creates a new <code>init</code> instance.
*
* @param articles an <code>Article[]</code> value
* @param comparator a <code>Comparator</code> value
*/
protected void init(List<Article> articles, Comparator comparator, Grouper grouper) {
if (articles != null && articles.size() > 0) {
this.comparator = comparator;
this.baseArticles = articles;
this.grouper = grouper;
groupedArticles = grouper.group(baseArticles.toArray(new Article[baseArticles.size()]), comparator);
int numLists = groupedArticles.getNumberOfGroups();
groupModels = new ArticleTableModel[numLists];
groupTables = new JTable[numLists];
groupLabels = new JLabel[numLists];
groupSums = new JLabel[numLists];
// listSelListeners = new ListSelectionListener[numLists];
artSelListeners = new ArticleSelectionListener[numLists];
artSelListener = new ArticleSelectionListener(-2);
validate();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
MouseAdapter ml = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
table_mouseClicked(e);
}
};
MouseAdapter columnListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
column_mouseClicked(e);
}
};
ArticleStatusRenderer statusRenderer = new ArticleStatusRenderer();
MessageFormat msgFormat = new MessageFormat(strings.getString("character_count"));
Font fontBold = new Font("Arial", Font.BOLD, 14);
Font fontItalic = new Font("Arial", Font.ITALIC, 14);
for (int i = 0; i < groupedArticles.getNumberOfGroups(); i++) {
// first, create the group header
groupLabels[i] = new JLabel(groupedArticles.getGroupHeader(i));
groupLabels[i].setHorizontalAlignment(SwingConstants.LEFT);
groupLabels[i].setFont(fontBold);
GridBagConstraints c = new GridBagConstraints();
JPanel labelPane = new JPanel(new GridBagLayout());
Dimension dim = labelPane.getMaximumSize();
dim.height = (int) (fontBold.getSize() * 1.8);
c.insets = new Insets(5, 0, 0, 0);
labelPane.setMaximumSize(dim);
labelPane.add(groupLabels[i], c);
this.add(labelPane);
// then, create and fill the JTable
ArrayList tempArrayList = groupedArticles.getGroupOfArticles(i);
Article[] tempArticles = (Article[])
tempArrayList.toArray(new Article[tempArrayList.size()]);
groupModels[i] = new ArticleTableModel(tempArticles);
groupTables[i] = new JTable();
groupTables[i].setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
groupTables[i].setModel(groupModels[i]);
groupTables[i].addMouseListener(ml);
groupTables[i].getTableHeader().addMouseListener(columnListener);
groupTables[i].setDefaultRenderer(ArticleStatus.class, statusRenderer);
TableColumnModel colMod = groupTables[i].getColumnModel();
for (int j = 0; j < colMod.getColumnCount(); j++) {
if (j == 0)
colMod.getColumn(j).setPreferredWidth(140);
else if (j == 1)
colMod.getColumn(j).setPreferredWidth(90);
else if (j == 2)
colMod.getColumn(j).setPreferredWidth(120);
else if (j == 3)
colMod.getColumn(j).setPreferredWidth(100);
else if (j == 4)
colMod.getColumn(j).setPreferredWidth(110);
else if (j == 5)
colMod.getColumn(j).setPreferredWidth(60);
else if (j == 6)
colMod.getColumn(j).setPreferredWidth(40);
else if (j == 7)
colMod.getColumn(j).setPreferredWidth(60);
else if (j == 8)
colMod.getColumn(j).setPreferredWidth(70);
else if (j == 9)
colMod.getColumn(j).setPreferredWidth(30);
}
// listSelListeners[i] = new ListSelectionListener();
artSelListeners[i] = new ArticleSelectionListener(i);
ListSelectionModel lsm = groupTables[i].getSelectionModel();
lsm.addListSelectionListener(artSelListeners[i]);
this.add(groupTables[i].getTableHeader());
this.add(groupTables[i]);
// finally, print a sum for this group
Iterator it = groupedArticles.getGroupOfArticles(i).iterator();
int wanted = 0;
int actual = 0;
float pages = 0;
while (it.hasNext()) {
Article a = (Article) it.next();
wanted += a.getWantedNumberOfCharacters();
actual += a.getCurrentNumberOfCharacters();
pages += a.getWantedNumberOfPages();
}
Object[] msgObj = {
actual,
wanted,
pages
};
groupSums[i] = new JLabel(msgFormat.format(msgObj));
groupSums[i].setHorizontalAlignment(SwingConstants.LEFT);
groupSums[i].setFont(fontItalic);
JPanel groupSumsPane = new JPanel(new GridBagLayout());
c = new GridBagConstraints();
dim = groupSumsPane.getMaximumSize();
dim.height = 20;
groupSumsPane.setMaximumSize(dim);
groupSumsPane.add(groupSums[i], c);
this.add(groupSumsPane, c);
}
if (popupMenu != null) {
setPopupMenu(popupMenu);
}
}
// empty list
else {
this.baseArticles = null;
JLabel nullLabel = new JLabel();
nullLabel.setForeground(Color.red);
nullLabel.setText(strings.getString("empty_list"));
this.add(nullLabel);
}
validate();
}
class ArticleStatusRenderer implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
JLabel l = null;
if (value instanceof ArticleStatus) {
ArticleStatus status = (ArticleStatus) value;
l = new JLabel(status.getName());
l.setForeground(Color.black);
l.setOpaque(true);
l.setBackground(status.getColor());
Font f = l.getFont();
l.setFont(new Font(f.getName(), Font.PLAIN, f.getSize()));
Color c = status.getColor();
if (isSelected) {
if ((c.getRed() == 255) && (c.getGreen() == 255) && (c.getBlue() == 255))
l.setBackground(new Color(200, 200, 255));
else
l.setBackground(status.getColor().darker());
}
}
return l;
}
}
public void regroup(List<Article> newArticles) {
removeAll();
init(newArticles, comparator, grouper);
}
public void regroup(Grouper grouper) {
this.grouper = grouper;
removeAll();
init(this.baseArticles, this.comparator, grouper);
}
public void resort(Comparator comparator) {
this.comparator = comparator;
removeAll();
init(baseArticles, comparator, grouper);
}
public void regroup(Grouper grouper, Comparator comparator) {
this.comparator = comparator;
this.grouper = grouper;
removeAll();
init(baseArticles, comparator, grouper);
}
public static Comparator[] getAvailableComparators() {
return new Comparator[]{new ArticleByArticleNameComparator(strings.getString("comparator_name")),
new ArticleByArticleTypeComparator(strings.getString("comparator_type")),
new ArticleByStatusComparator(strings.getString("comparator_status")),
new ArticleByJournalistComparator(strings.getString("comparator_journalist")),
new ArticleByWantedCharactersComparator(strings.getString("comparator_characters")),
new ArticleByPhotographerComparator(strings.getString("comparator_photographer"))};
}
public void setComparator(Comparator comparator) {
this.comparator = comparator;
}
public static Grouper[] getAvailableGroupers() {
Grouper[] availGroupers = {new NoGrouper(strings.getString("grouper_nogrouper")),
new DepartmentGrouper(strings.getString("grouper_department")),
new ArticleTypeGrouper(strings.getString("grouper_article")),
new JournalistGrouper(strings.getString("grouper_journalist")),
new PublicationGrouper(strings.getString("grouper_publication"))};
return availGroupers;
}
public Grouper getCurrentGrouper() {
return this.grouper;
}
/**
* Returns the selected article
*/
public Article getSelectedArticle() {
return selectedArticle;
}
public boolean hasSelection() {
return selectedArticle != null;
}
/**
* Add a popup context menu. As this is a standard menu, listeners will
* receive an action event, and must then query this component to find
* the currently selected article.
*
* @param popup The menu.
*/
public void setPopupMenu(JPopupMenu popup) {
this.popupMenu = popup;
// Remove old popup listener, if any
if (popupListener != null && groupedArticles != null) {
this.removeMouseListener(popupListener);
for (int i = 0; i < groupedArticles.getNumberOfGroups(); i++) {
groupTables[i].removeMouseListener(popupListener);
groupLabels[i].removeMouseListener(popupListener);
groupSums[i].removeMouseListener(popupListener);
}
popupListener = null;
}
popupListener = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
};
this.addMouseListener(popupListener);
if (groupedArticles != null) {
for (int i = 0; i < groupedArticles.getNumberOfGroups(); i++) {
groupTables[i].addMouseListener(popupListener);
groupLabels[i].addMouseListener(popupListener);
groupSums[i].addMouseListener(popupListener);
}
}
}
/**
* Called to determine if a popup menu should be shown.
*
* @param e The mouse event that caused this call.
*/
protected void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
if (e.getSource() instanceof JTable) {
JTable source = (JTable) e.getSource();
int row = source.rowAtPoint(e.getPoint());
source.clearSelection();
source.addRowSelectionInterval(row, row);
} else {
// Clear selection
for (JTable groupTable : groupTables) {
groupTable.clearSelection();
}
selectedArticle = null;
}
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
public void addChangeListener(ChangeListener l) {
listenerList.add(ChangeListener.class, l);
}
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
}
protected void fireStateChanged() {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
if (changeEvent == null) {
changeEvent = new ChangeEvent(this);
}
((ChangeListener) listeners[i + 1]).stateChanged(changeEvent);
}
}
}
protected void column_mouseClicked(MouseEvent e) {
JTableHeader th = (JTableHeader) e.getSource();
JTable table = th.getTable();
TableColumnModel columnModel = table.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = table.convertColumnIndexToModel(viewColumn);
if (column != -1) {
ArticleTableModel tm = (ArticleTableModel) table.getModel();
switch (tm.getColumnOrder(column)) {
case 0: {
comparator = new ArticleByArticleNameComparator(strings.getString("comparator_name"));
break;
}
case 1: {
if (e.isControlDown()) {
grouper = new ArticleTypeGrouper(strings.getString("grouper_article"));
} else {
comparator = new ArticleByArticleTypeComparator(strings.getString("comparator_type"));
}
break;
}
case 2: {
if (e.isControlDown())
grouper = new JournalistGrouper(strings.getString("grouper_journalist"));
else
comparator = new ArticleByJournalistComparator(strings.getString("comparator_journalist"));
break;
}
case 3: {
if (e.isControlDown()) {
} else {
comparator = new ArticleByPhotographerComparator(strings.getString("comparator_photographer"));
}
break;
}
case 4: {
break;
}
case 5: {
break;
}
case 6: {
break;
}
case 7: {
if (e.isControlDown()) {
} else {
comparator = new ArticleByStatusComparator(strings.getString("comparator_status"));
}
break;
}
case 8: {
if (e.isControlDown()) {
grouper = new PublicationGrouper(strings.getString("grouper_publication"));
} else {
}
break;
}
}
regroup(grouper, comparator);
}
revalidate();
repaint();
}
protected void table_mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) {
Object source = e.getSource();
int rowIndex = ((JTable) source).rowAtPoint(e.getPoint());
if (rowIndex != -1) {
TableModel model = ((JTable) source).getModel();
if (model != null && model instanceof ArticleTableModel) {
Article art = ((ArticleTableModel) model).getArticleAtRow(rowIndex);
int type = (e.isControlDown()) ? 1 : 0;
fireActionEvent(type);
}
}
}
}
public void addActionListener(ActionListener l) {
actionListeners.addElement(l);
}
public void removeActionListener(ActionListener l) {
actionListeners.removeElement(l);
}
protected void fireActionEvent(int type) {
ActionEvent ev = new ActionEvent(this, type, "Article");
synchronized (actionListeners) {
for (int i = 0; i < actionListeners.size(); i++) {
((ActionListener) actionListeners.elementAt(i)).actionPerformed(ev);
}
}
}
public void updateColumns(int[] columns) {
if (baseArticles != null) {
for (ArticleTableModel groupModel : groupModels) {
groupModel.updateColumns(columns);
}
}
}
// methods from the Scrollable-interface
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(0, 0);
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 20;
}
public boolean getScrollableTracksViewportHeight() {
Component parent = getParent();
return parent instanceof JViewport && parent.getHeight() > getPreferredSize().height;
}
public boolean getScrollableTracksViewportWidth() {
Component parent = getParent();
return parent instanceof JViewport && parent.getWidth() > getPreferredSize().width;
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 10;
}
public class ArticleSelectionListener implements ListSelectionListener {
protected int tableNo = -1;
public ArticleSelectionListener(int value) {
tableNo = value;
}
public int getTableNo() {
return tableNo;
}
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting())
return;
ListSelectionModel lsm =
(ListSelectionModel) e.getSource();
if (lsm.isSelectionEmpty()) {
//no rows are selected - do nothing
} else {
// first, unselect the other tables:
for (int i = 0; i < groupTables.length; i++)
if (i != tableNo)
groupTables[i].clearSelection();
// then, update selectedArticle to the new selection
int selectedRow = lsm.getMinSelectionIndex();
selectedArticle = groupModels[tableNo].getArticleAtRow(selectedRow);
fireStateChanged();
}
}
}
public void generateArticleListAsXML(org.xml.sax.ContentHandler handler) throws SAXException {
int groupCount = groupedArticles.getNumberOfGroups();
Attributes emptyAttrs = new AttributesImpl();
handler.startDocument();
handler.startElement("", "articlelist", "", emptyAttrs);
for (int i = 0; i < groupCount; i++) {
String head = groupedArticles.getGroupHeader(i);
AttributesImpl groupAttrs = new AttributesImpl();
groupAttrs.addAttribute("", "name", "", "", head);
groupAttrs.addAttribute("", "grouper", "", "", grouper.getClass().getName());
handler.startElement("", "group", "", groupAttrs);
for (Object o : groupedArticles.getGroupOfArticles(i)) {
Article a = (Article) o;
AttributesImpl articleAttrs = new AttributesImpl();
articleAttrs.addAttribute("", "name", "", "", a.getName());
//Journalist
if (a.getJournalist() != null)
articleAttrs.addAttribute("", "journalist", "", "", a.getJournalist().getName());
//Photographer
if (a.getPhotographer() != null)
articleAttrs.addAttribute("", "photographer", "", "", a.getPhotographer().getName());
if (a.getSection() != null)
articleAttrs.addAttribute("", "department", "", "", a.getSection().getName());
// Publication
if (a.getPublication() != null)
articleAttrs.addAttribute("", "publication", "", "", a.getPublication().getName());
// ArticleStatus
if (a.getArticleStatus() != null)
articleAttrs.addAttribute("", "articleStatus", "", "", a.getArticleStatus().getName());
// ArticleType
if (a.getArticleType() != null)
articleAttrs.addAttribute("", "articleType", "", "", a.getArticleType().getName());
// current # charachters
articleAttrs.addAttribute("", "currentCharacters", "", "", a.getCurrentNumberOfCharacters() + "");
// wanted # charachters
articleAttrs.addAttribute("", "wantedCharacters", "", "", a.getWantedNumberOfCharacters() + "");
handler.startElement("", "article", "", articleAttrs);
// Notes
String notes = a.getDescription();
if (notes != null && notes.length() > 0) {
StringTokenizer st = new StringTokenizer(notes, "\n");
handler.startElement("", "description", "", emptyAttrs);
while (st.hasMoreTokens()) {
String p = st.nextToken();
if (p.trim().length() > 0) {
handler.startElement("", "p", "", new AttributesImpl(emptyAttrs));
char[] chars = p.toCharArray();
handler.characters(chars, 0, chars.length);
handler.endElement("", "p", "");
}
}
handler.endElement("", "description", "");
}
handler.endElement("", "article", "");
}
handler.endElement("", "group", "");
}
handler.endElement("", "articlelist", "");
handler.endDocument();
}
}