/** * 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(); } }