/* * Copyright 2004-2010 Institute of Software Technology and Interactive Systems, Vienna University of Technology * * 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.ifs.tuwien.ac.at/dm/somtoolbox/license.html * * 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. */ package at.tuwien.ifs.commons.gui.controls.swing.table; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.RowFilter; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableRowSorter; /** * @author Jakob Frank * @version $Id: ClassColorTableModel.java 3943 2010-11-22 14:22:07Z frank $ */ public class ClassColorTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; public static final int NAME_COLUMN_INDEX = 0; public static final int COLOR_COLUMN_INDEX = 1; public static final int SELECT_COLUMN_INDEX = 2; private static String[] columnTitles = { "Name", "Color", "Show" }; final private Color[] classColors; final private String[] classNames; final private boolean[] classSelected; private boolean selectionAllowed; public ClassColorTableModel() { this(new String[0], new Color[0]); } public ClassColorTableModel(String[] names, Color[] colors) { if (names.length != colors.length) { throw new IllegalArgumentException("names.length and colors.length differ!"); } classNames = names; classColors = colors; classSelected = new boolean[names.length]; for (int i = 0; i < classSelected.length; i++) { classSelected[i] = true; } selectionAllowed = true; } public void setAllSelected(boolean selected) { for (int i = 0; i < classSelected.length; i++) { classSelected[i] = selected; } fireTableDataChanged(); } public void setSelected(int rowIndex, boolean selected) { classSelected[rowIndex] = selected; fireTableDataChanged(); } /* (non-Javadoc) * @see javax.swing.table.TableModel#getRowCount() */ @Override public int getRowCount() { return classNames.length; } /* (non-Javadoc) * @see javax.swing.table.TableModel#getColumnCount() */ @Override public int getColumnCount() { return (selectionAllowed ? SELECT_COLUMN_INDEX : COLOR_COLUMN_INDEX) + 1; } /* (non-Javadoc) * @see javax.swing.table.TableModel#getColumnName(int) */ @Override public String getColumnName(int columnIndex) { return columnTitles[columnIndex]; } public void setColumnName(int columnIndex, String name) { columnTitles[columnIndex] = name; } /* (non-Javadoc) * @see javax.swing.table.TableModel#getColumnClass(int) */ @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case COLOR_COLUMN_INDEX: return Color.class; case SELECT_COLUMN_INDEX: return Boolean.class; default: return String.class; } } /* (non-Javadoc) * @see javax.swing.table.TableModel#isCellEditable(int, int) */ @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return columnIndex != NAME_COLUMN_INDEX; } /* (non-Javadoc) * @see javax.swing.table.TableModel#getValueAt(int, int) */ @Override public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case NAME_COLUMN_INDEX: return classNames[rowIndex]; case COLOR_COLUMN_INDEX: return classColors[rowIndex]; case SELECT_COLUMN_INDEX: return classSelected[rowIndex]; default: return null; } } /* (non-Javadoc) * @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int) */ @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { switch (columnIndex) { case COLOR_COLUMN_INDEX: classColors[rowIndex] = (Color) aValue; fireTableCellUpdated(rowIndex, columnIndex); break; case SELECT_COLUMN_INDEX: classSelected[rowIndex] = (Boolean) aValue; fireTableCellUpdated(rowIndex, columnIndex); break; } } public static void main(String[] args) { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel p = new JPanel(); p.setLayout(new BorderLayout()); String[] names = new String[] { "Rot", "Gelb", "GrĂ¼n", "Blau", "Rosa" }; Color[] colors = new Color[] { Color.red, Color.yellow, Color.green, Color.blue, Color.magenta }; final ClassColorTableModel model = new ClassColorTableModel(names, colors); final JTable table = createColorLegendTable(model); table.setFillsViewportHeight(true); table.setPreferredScrollableViewportSize(new Dimension(240, 120)); JScrollPane scr = new JScrollPane(table); p.add(scr, BorderLayout.CENTER); final JTextField txtFilter = new JTextField(); final TableRowSorter<ClassColorTableModel> sorter = new TableRowSorter<ClassColorTableModel>(model); table.setRowSorter(sorter); txtFilter.getDocument().addDocumentListener(new DocumentListener() { private void actionPerformed(DocumentEvent e) { RowFilter<ClassColorTableModel, Integer> rf = null; try { final Matcher matcher = Pattern.compile(txtFilter.getText(), Pattern.CASE_INSENSITIVE).matcher(""); rf = new RowFilter<ClassColorTableModel, Integer>() { @Override public boolean include( javax.swing.RowFilter.Entry<? extends ClassColorTableModel, ? extends Integer> entry) { matcher.reset(entry.getModel().getClassName(entry.getIdentifier())); return matcher.find(); } }; } catch (java.util.regex.PatternSyntaxException ex) { return; } sorter.setRowFilter(rf); } @Override public void removeUpdate(DocumentEvent e) { actionPerformed(e); } @Override public void insertUpdate(DocumentEvent e) { actionPerformed(e); } @Override public void changedUpdate(DocumentEvent e) { } }); p.add(txtFilter, BorderLayout.NORTH); final JToggleButton tb = new JToggleButton("Selectable"); tb.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { model.selectionAllowed = tb.isSelected(); model.fireTableStructureChanged(); } }); p.add(tb, BorderLayout.SOUTH); f.setContentPane(p); f.pack(); f.setVisible(true); } /** * Creates a ColorLegendTable to be used with the required default listeners added. * * @param theModel the {@link ClassColorTableModel} * @return a JTable with nice default settings */ public static JTable createColorLegendTable(ClassColorTableModel theModel) { final JTable table = new JTable(); table.setModel(theModel); table.setDefaultRenderer(Color.class, new ColorCellRenderer(true)); table.setDefaultEditor(Color.class, new ColorCellEditor()); /* * Handle clicks on the 3rd column header (booleans) */ table.getTableHeader().addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 1) { TableColumnModel cm = table.getColumnModel(); int tColIndex = cm.getColumnIndexAtX(e.getX()); if (tColIndex < 0) { return; } if (table.getModel() instanceof ClassColorTableModel) { ClassColorTableModel cctm = (ClassColorTableModel) table.getModel(); int colIndex = table.convertColumnIndexToModel(tColIndex); if (colIndex == ClassColorTableModel.SELECT_COLUMN_INDEX) { cctm.setAllSelected(!cctm.isAllSelected()); } } } } }); /* * Use a custom HeaderRenderer (Checkbox in Header) */ table.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer() { private static final long serialVersionUID = 1L; private JCheckBox box = null; private JLabel lbl = null; private ClassColorTableModel model = null; @Override public Component getTableCellRendererComponent(final JTable tables, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (model == null || !tables.getModel().equals(model)) { lbl = null; box = null; if (tables.getModel() instanceof ClassColorTableModel) { model = (ClassColorTableModel) tables.getModel(); } else { model = null; } } if (model == null || table.convertColumnIndexToModel(column) != ClassColorTableModel.SELECT_COLUMN_INDEX) { if (lbl == null) { lbl = new JLabel(value.toString()); lbl.setBorder(UIManager.getBorder("TableHeader.cellBorder")); lbl.setHorizontalAlignment(SwingConstants.CENTER); lbl.setHorizontalTextPosition(SwingConstants.LEADING); lbl.setFont(tables.getTableHeader().getFont()); lbl.setForeground(tables.getTableHeader().getForeground()); lbl.setBackground(tables.getTableHeader().getBackground()); lbl.setEnabled(tables.getTableHeader().isEnabled()); } lbl.setText(value.toString()); return lbl; } else { if (box == null) { box = new JCheckBox(value.toString()); box.setSelected(model.isAllSelected()); box.setBorder(UIManager.getBorder("TableHeader.cellBorder")); box.setBorderPainted(true); box.setHorizontalAlignment(SwingConstants.CENTER); box.setHorizontalTextPosition(SwingConstants.LEADING); box.setFont(tables.getTableHeader().getFont()); box.setForeground(tables.getTableHeader().getForeground()); box.setBackground(tables.getTableHeader().getBackground()); box.setEnabled(tables.getTableHeader().isEnabled()); model.addTableModelListener(new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { box.setSelected(model.isAllSelected()); table.getTableHeader().revalidate(); table.getTableHeader().repaint(); } }); } return box; } } }); /* * Use a context menu to allow multi-selection */ table.addMouseListener(new MouseAdapter() { private JPopupMenu pop = null; private ClassColorTableModel model = null; private void setSelectedClassesVisible(boolean visible) { if (model != null) { for (int i : table.getSelectedRows()) { model.setSelected(table.convertRowIndexToModel(i), visible); } } } private void triggerPopup(MouseEvent e) { if (model == null || !table.getModel().equals(model)) { if (table.getModel() instanceof ClassColorTableModel) { model = (ClassColorTableModel) table.getModel(); } else { model = null; } } if (model != null && model.isSelectionAllowed() && e.isPopupTrigger() && table.getSelectedRowCount() > 0) { if (pop == null) { pop = new JPopupMenu(); JMenuItem selAll = new JMenuItem("Show selected classes"); selAll.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setSelectedClassesVisible(true); } }); JMenuItem hidAll = new JMenuItem("Hide selected classes"); hidAll.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setSelectedClassesVisible(false); } }); pop.add(selAll); pop.add(hidAll); } pop.show(e.getComponent(), e.getX(), e.getY()); } } @Override public void mousePressed(MouseEvent e) { triggerPopup(e); } @Override public void mouseReleased(MouseEvent e) { triggerPopup(e); } }); /* * Fix column widths */ fixColumnWidth(table, SELECT_COLUMN_INDEX, true, true); fixColumnWidth(table, COLOR_COLUMN_INDEX, true, false); fixColumnWidth(table, NAME_COLUMN_INDEX, true, false); return table; } private static void fixColumnWidth(JTable table, int columnIndex, boolean setMin, boolean setMax) { final TableColumn column = table.getColumnModel().getColumn(columnIndex); TableCellRenderer hRenderer = column.getHeaderRenderer(); if (hRenderer == null) { hRenderer = table.getTableHeader().getDefaultRenderer(); } final Component hRendererComponent = hRenderer.getTableCellRendererComponent(table, columnTitles[columnIndex], false, false, -1, columnIndex); int contentWidth = 0; for (int i = 0; i < table.getRowCount(); i++) { TableCellRenderer cRenderer = column.getCellRenderer(); if (cRenderer == null) { cRenderer = table.getDefaultRenderer(table.getModel().getColumnClass(columnIndex)); } final Component cRendererConponent = cRenderer.getTableCellRendererComponent(table, table.getModel().getValueAt(i, columnIndex), false, false, i, columnIndex); contentWidth = Math.max(contentWidth, cRendererConponent.getPreferredSize().width); } final int headerWidth = Math.max(hRendererComponent.getPreferredSize().width, contentWidth); column.setPreferredWidth((int) Math.ceil(headerWidth * 1.2f)); if (setMin) { column.setMinWidth((int) Math.ceil(headerWidth * 1.2f)); } if (setMax) { column.setMaxWidth((int) Math.ceil(headerWidth * 1.2f)); } } public boolean isAllSelected() { boolean res = true; for (boolean element : classSelected) { res &= element; if (!res) { break; } } return res; } public String[] getSelectedClasses() { ArrayList<String> l = new ArrayList<String>(); for (int i = 0; i < classNames.length; i++) { if (classSelected[i]) { l.add(classNames[i]); } } return l.toArray(new String[l.size()]); } public int[] getSelectedClassIndices() { ArrayList<Integer> l = new ArrayList<Integer>(); for (int i = 0; i < classNames.length; i++) { if (classSelected[i]) { l.add(i); } } int[] r = new int[l.size()]; for (int i = 0; i < r.length; i++) { r[i] = l.get(i).intValue(); } return r; } public Color[] getColors() { // return classColors; Color[] c = new Color[classColors.length]; for (int i = 0; i < c.length; i++) { if (classSelected[i]) { c[i] = classColors[i]; } else { c[i] = new Color(0, 0, 0, 0); } } return c; } public Color getColor(int row) { return classColors[row]; } public String[] getClassNames() { return classNames; } public String getClassName(int row) { return classNames[row]; } /** * @return the longest ClassName */ public String getLongestName() { String s = ""; int l = 0; for (String className : classNames) { if (className.length() > l) { s = className; l = s.length(); } } return s; } public boolean isSelectionAllowed() { return selectionAllowed; } public void setSelectionAllowed(boolean selectionAllowed) { this.selectionAllowed = selectionAllowed; fireTableStructureChanged(); } public static BufferedImage asBufferedImage(ClassColorTableModel theModel) { return asBufferedImage(theModel, false); } /** * @param theModel The model to paint * @param showAllRows paint all rows or just selected * @return a buffered image. */ public static BufferedImage asBufferedImage(ClassColorTableModel theModel, boolean showAllRows) { final ClassColorTableModel m = new ClassColorTableModel(theModel.getClassNames(), theModel.getColors()); m.setSelected(theModel.getSelectedClassIndices()); m.setSelectionAllowed(false); JTable table = new JTable(m); table.setDefaultRenderer(Color.class, new ColorCellRenderer(false)); if (!showAllRows) { TableRowSorter<ClassColorTableModel> sorter = new TableRowSorter<ClassColorTableModel>(m); table.setRowSorter(sorter); sorter.setRowFilter(new RowFilter<ClassColorTableModel, Integer>() { @Override public boolean include( javax.swing.RowFilter.Entry<? extends ClassColorTableModel, ? extends Integer> entry) { return m.classSelected[entry.getIdentifier().intValue()]; } }); } fixColumnWidth(table, NAME_COLUMN_INDEX, true, true); fixColumnWidth(table, COLOR_COLUMN_INDEX, true, true); Dimension size = table.getPreferredSize(); table.setSize(size); // FIXME: For some reason this does not work... BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bi.createGraphics(); table.paint(g); return bi; } public void setSelected(int[] selectedClassIndices) { setAllSelected(false); for (int i = 0; i < selectedClassIndices.length; i++) { classSelected[selectedClassIndices[i]] = true; } fireTableDataChanged(); } }