/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.look.ui; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Paint; import java.awt.Point; import java.awt.event.MouseEvent; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.QuadCurve2D; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.RowSorter; import javax.swing.RowSorter.SortKey; import javax.swing.SortOrder; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import com.rapidminer.gui.look.Colors; import com.rapidminer.gui.look.RapidLookAndFeel; import com.rapidminer.gui.look.RapidLookTools; import com.rapidminer.gui.tools.ExtendedJTableSorterModel; /** * The UI for table headers. * * @author Ingo Mierswa */ public class TableHeaderUI extends BasicTableHeaderUI { private static final Border HEADER_BORDER = BorderFactory.createEmptyBorder(0, 10, 0, 10); private static final int HEADER_HEIGHT = 31; private TableCellRenderer originalRenderer; private TableHeaderRenderer mainRenderer; private int highlightedColumn = -1; private int pressedColumn = -1; public static ComponentUI createUI(JComponent h) { return new TableHeaderUI(); } @Override public void installUI(JComponent c) { super.installUI(c); this.originalRenderer = this.header.getDefaultRenderer(); if (this.originalRenderer instanceof UIResource) { this.mainRenderer = new TableHeaderRenderer(); this.mainRenderer.setHorizontalAlignment(SwingConstants.CENTER); this.header.setDefaultRenderer(this.mainRenderer); } } @Override public void uninstallUI(JComponent c) { if (this.header.getDefaultRenderer() instanceof TableHeaderRenderer) { this.header.setDefaultRenderer(this.originalRenderer); this.mainRenderer = null; } super.uninstallUI(c); } @Override public void installDefaults() { super.installDefaults(); // some tables need a special header background so check if it was set Object bgObject = header.getClientProperty(RapidLookTools.PROPERTY_TABLE_HEADER_BACKGROUND); if (bgObject != null && bgObject instanceof Color) { header.setBackground((Color) bgObject); } } private void updateRolloverColumn(Point p) { if (this.header.getDraggedColumn() == null && this.header.contains(p)) { int col = this.header.columnAtPoint(p); if (col != this.highlightedColumn) { this.highlightedColumn = col; this.header.repaint(); } } } @Override protected MouseInputListener createMouseInputListener() { return new MouseInputHandler() { @Override public void mouseMoved(MouseEvent e) { super.mouseMoved(e); updateRolloverColumn(e.getPoint()); } @Override public void mouseEntered(MouseEvent e) { super.mouseEntered(e); updateRolloverColumn(e.getPoint()); } @Override public void mouseExited(MouseEvent e) { super.mouseExited(e); TableHeaderUI.this.highlightedColumn = -1; TableHeaderUI.this.header.repaint(); } @Override public void mousePressed(MouseEvent e) { super.mousePressed(e); if (TableHeaderUI.this.header.contains(e.getPoint())) { if (TableHeaderUI.this.header.getDraggedColumn() != null) { TableHeaderUI.this.pressedColumn = TableHeaderUI.this.header.columnAtPoint(e.getPoint()); } else { if (TableHeaderUI.this.header.getDraggedColumn() != null) { TableHeaderUI.this.pressedColumn = TableHeaderUI.this.header.getColumnModel().getColumnIndex( TableHeaderUI.this.header.getDraggedColumn()); } } } if (TableHeaderUI.this.header.getReorderingAllowed()) { TableHeaderUI.this.highlightedColumn = -1; } TableHeaderUI.this.header.repaint(); } @Override public void mouseDragged(MouseEvent e) { super.mouseDragged(e); if (TableHeaderUI.this.header.contains(e.getPoint())) { TableHeaderUI.this.pressedColumn = -1; } updateRolloverColumn(e.getPoint()); } @Override public void mouseReleased(MouseEvent e) { super.mouseReleased(e); if (TableHeaderUI.this.header.contains(e.getPoint())) { TableHeaderUI.this.pressedColumn = -1; } updateRolloverColumn(e.getPoint()); TableHeaderUI.this.header.repaint(); } }; } @Override public Dimension getPreferredSize(JComponent c) { return new Dimension((int) super.getPreferredSize(c).getWidth(), Math.max((int) super.getPreferredSize(c) .getHeight(), HEADER_HEIGHT)); } private class TableHeaderRenderer extends DefaultTableCellRenderer implements UIResource { private static final long serialVersionUID = -7300727448162015796L; private boolean rollOver; private boolean isPressed; private boolean isLeftmost; private boolean isRightmost; private int curCol = 0; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (table != null) { JTableHeader header = table.getTableHeader(); if (header != null) { setFont(header.getFont()); } } this.rollOver = column == TableHeaderUI.this.highlightedColumn; if (header != null) { curCol = column; this.isLeftmost = column == 0; this.isRightmost = column == header.getColumnModel().getColumnCount() - 1; } if (header != null && header.getDraggedColumn() != null) { this.isPressed = viewIndexForColumn(header.getDraggedColumn()) == column || column == TableHeaderUI.this.pressedColumn; } else { this.isPressed = false; } setText(value == null ? "" : value.toString()); setHorizontalAlignment(SwingConstants.LEFT); setHorizontalTextPosition(SwingConstants.LEADING); setBorder(HEADER_BORDER); return this; } @Override public Icon getIcon() { int modelCol = header.getTable().convertColumnIndexToModel(curCol); TableModel model = header.getTable().getModel(); if (model instanceof ExtendedJTableSorterModel) { ExtendedJTableSorterModel sortModel = (ExtendedJTableSorterModel) model; switch (sortModel.getSortingStatus(modelCol)) { case ExtendedJTableSorterModel.ASCENDING: return UIManager.getIcon("Table.ascendingSortIcon"); case ExtendedJTableSorterModel.DESCENDING: return UIManager.getIcon("Table.descendingSortIcon"); case ExtendedJTableSorterModel.NOT_SORTED: default: return null; } } else { SortKey sortKey = getSortKey(header.getTable().getRowSorter(), modelCol); SortOrder sortOrder = sortKey != null ? sortKey.getSortOrder() : SortOrder.UNSORTED; switch (sortOrder) { case ASCENDING: return UIManager.getIcon("Table.ascendingSortIcon"); case DESCENDING: return UIManager.getIcon("Table.descendingSortIcon"); case UNSORTED: default: return null; } } } @Override public Insets getInsets() { return new Insets(2, 4, 2, 3); } @Override public void paint(Graphics g) { int h = this.getHeight(); int w = this.getWidth(); Graphics2D g2 = (Graphics2D) g; if (this.isPressed) { g2.setColor(Colors.TABLE_HEADER_BACKGROUND_PRESSED); } else { if (this.rollOver) { g2.setColor(Colors.TABLE_HEADER_BACKGROUND_FOCUS); } else { Paint gp = new GradientPaint(0, 0, Colors.TABLE_HEADER_BACKGROUND_GRADIENT_START, 0, h, Colors.TABLE_HEADER_BACKGROUND_GRADIENT_END); g2.setPaint(gp); } } g2.fill(createHeaderShape(0, 0, w, h, isLeftmost, isRightmost)); g2.setColor(Colors.TABLE_HEADER_BORDER); g2.draw(createHeaderShape(0, 0, w, h, isLeftmost, isRightmost)); super.paint(g); } /** * Get the view column index of the given table column * * @param aColumn * @return */ private int viewIndexForColumn(TableColumn aColumn) { TableColumnModel cm = TableHeaderUI.this.header.getColumnModel(); for (int column = 0; column < cm.getColumnCount(); column++) { if (cm.getColumn(column) == aColumn) { return column; } } return -1; } /** * Tries to return the sort key for the given column. * * @param sorter * @param column * @return the sort key or {@code null} */ private SortKey getSortKey(RowSorter<? extends TableModel> sorter, int column) { if (sorter == null) { return null; } for (Object sortObj : sorter.getSortKeys()) { SortKey key = (SortKey) sortObj; if (key.getColumn() == column) { return key; } } return null; } } /** * Creates the shape for a table header. * * @param x * @param y * @param w * @param h * @param isLeftmost * @param isRightmost * @return */ public static Path2D createHeaderShape(int x, int y, int w, int h, boolean isLeftmost, boolean isRightmost) { double rTop = RapidLookAndFeel.CORNER_DEFAULT_RADIUS * 0.33; Path2D path = new Path2D.Double(); h -= 1; // middle columns are easy, just a rectangle without left border if (!isLeftmost && !isRightmost) { w -= 1; path.append(new Line2D.Double(x, y, x + w, y), true); path.append(new Line2D.Double(x + w, y, x + w, y + h), true); path.append(new Line2D.Double(x + w, y + h, x, y + h), true); return path; } // special case of single column if (isLeftmost && isRightmost) { w -= 1; path.append(new Line2D.Double(x, y + h - 1, x, y + rTop), true); QuadCurve2D curve = new QuadCurve2D.Double(x, y + rTop, x, y, x + rTop, y); path.append(curve, true); path.append(new Line2D.Double(x + rTop, y, x + w - rTop, y), true); curve = new QuadCurve2D.Double(x + w - rTop, y, x + w, y, x + w, y + rTop); path.append(curve, true); path.append(new Line2D.Double(x + w, y + rTop, x + w, y + h), true); path.append(new Line2D.Double(x + w, y + h, x, y + h), true); return path; } if (isLeftmost) { w -= 1; path.append(new Line2D.Double(x, y + h - 1, x, y + rTop), true); QuadCurve2D curve = new QuadCurve2D.Double(x, y + rTop, x, y, x + rTop, y); path.append(curve, true); path.append(new Line2D.Double(x + rTop, y, x + w, y), true); path.append(new Line2D.Double(x + w, y, x + w, y + h), true); path.append(new Line2D.Double(x + w, y + h, x, y + h), true); } else { w -= 1; path.append(new Line2D.Double(x, y, x + w - rTop, y), true); QuadCurve2D curve = new QuadCurve2D.Double(x + w - rTop, y, x + w, y, x + w, y + rTop); path.append(curve, true); path.append(new Line2D.Double(x + w, y + rTop, x + w, y + h), true); path.append(new Line2D.Double(x + w, y + h, x, y + h), true); } return path; } }