All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.swingx.JXTableHeader Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
/*
 * $Id$
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.logging.Logger;

import javax.swing.JTable;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import org.jdesktop.swingx.event.TableColumnModelExtListener;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.plaf.TableHeaderAddon;
import org.jdesktop.swingx.sort.SortController;
import org.jdesktop.swingx.table.TableColumnExt;

/**
 * TableHeader with extended functionality if associated Table is of
 * type JXTable.

* *

Extended user interaction

* *
    *
  • Supports column pack (== auto-resize to exactly fit the contents) * on double-click in resize region. *
  • Configurable to resort a column on the second click of a mouseClicked event * (feature request #271-swingx) *
  • Does its best to not sort if the mouse click happens in the resize region. *
  • Supports horizontal auto-scroll if a column is dragged outside visible rectangle. * This feature is enabled if the autoscrolls property is true. The default is false * (because of Issue #788-swingx which still isn't fixed for jdk1.6). *
* * Note: extended sort and resize related functionality is fully effective only if the header's * table is of type JXTable and has control over the row sorter, that is the row sorter * is of type SortController. * *

Extended functionality

* *
    *
  • Listens to TableColumn propertyChanges to update itself accordingly. *
  • Supports per-column header ToolTips. *
  • Guarantees reasonable minimal height > 0 for header preferred height. *
* * * @author Jeanette Winzenburg * * @see JXTable#toggleSortOrder(int) * @see JXTable#resetSortOrder() * @see SortGestureRecognizer */ public class JXTableHeader extends JTableHeader implements TableColumnModelExtListener { @SuppressWarnings("unused") private static final Logger LOG = Logger.getLogger(JXTableHeader.class .getName()); static { LookAndFeelAddons.contribute(new TableHeaderAddon()); } private transient PropertyChangeListener tablePropertyChangeListener; private boolean resortsOnDoubleClick; private Point popupTriggerLocation; /** * Constructs a JTableHeader with a default * TableColumnModel. * * @see #createDefaultColumnModel */ public JXTableHeader() { super(); } /** * Constructs a JTableHeader which is initialized with * cm as the column model. If cm is * null this method will initialize the table header with a * default TableColumnModel. * * @param columnModel the column model for the table * @see #createDefaultColumnModel */ public JXTableHeader(TableColumnModel columnModel) { super(columnModel); } /** * {@inheritDoc}

* Sets the associated JTable. Enables enhanced header * features if table is of type JXTable.

* * PENDING: who is responsible for synching the columnModel? */ @Override public void setTable(JTable table) { uninstallTable(); super.setTable(table); installTable(); // setColumnModel(table.getColumnModel()); // the additional listening option makes sense only if the table // actually is a JXTable if (getXTable() != null) { installHeaderListener(); } else { uninstallHeaderListener(); } } /** * Installs the table.

* This implemenation synchs enabled state and installs the PropertyChangeListener. */ protected void installTable() { updateEnabledFromTable(); if (getTable() == null) return; getTable().addPropertyChangeListener(getTablePropertyChangeListener()); } /** * Synchs the header's enabled with the table's enabled property. */ protected void updateEnabledFromTable() { setEnabled(getTable() != null ? getTable().isEnabled() : true); } /** * Uninstalls the table.

* This implementation uninstalls the PropertyChangeListener. */ protected void uninstallTable() { if (getTable() == null) return; getTable().removePropertyChangeListener(getTablePropertyChangeListener()); } /** * Implements TableColumnModelExt to allow internal update after * column property changes.

* * This implementation triggers a resizeAndRepaint on every propertyChange which * doesn't already fire a "normal" columnModelEvent. * * @param event change notification from a contained TableColumn. * @see #isColumnEvent(PropertyChangeEvent) * @see TableColumnModelExtListener * * */ @Override public void columnPropertyChange(PropertyChangeEvent event) { if (isColumnEvent(event)) return; resizeAndRepaint(); } /** * Returns a boolean indicating if a property change event received * from column changes is expected to be already broadcasted by the * core TableColumnModel.

* * This implementation returns true for notification of width, preferredWidth * and visible properties, false otherwise. * * @param event the PropertyChangeEvent received as TableColumnModelExtListener. * @return a boolean to decide whether the same event triggers a * base columnModelEvent. */ protected boolean isColumnEvent(PropertyChangeEvent event) { return "width".equals(event.getPropertyName()) || "preferredWidth".equals(event.getPropertyName()) || "visible".equals(event.getPropertyName()); } //---------------------- enhanced component popup support /** * {@inheritDoc}

* * Overridden for bookkeeping: the given event location is * stored for later access. * * @see #getPopupTriggerLocation() */ @Override public Point getPopupLocation(MouseEvent event) { updatePopupTrigger(event); return super.getPopupLocation(event); } /** * Handles internal bookkeeping related to popupLocation, called from * getPopupLocation.

* * This implementation stores the mouse location as popupTriggerLocation. * * @param event the event that triggered the showing of the * componentPopup, might be null if triggered by keyboard */ protected void updatePopupTrigger(MouseEvent event) { Point old = getPopupTriggerLocation(); // note: getPoint creates a new Point on each call, safe to use as-is popupTriggerLocation = event != null ? event.getPoint() : null; firePropertyChange("popupTriggerLocation", old, getPopupTriggerLocation()); } /** * Returns the location of the mouseEvent that triggered the * showing of the ComponentPopupMenu. * * @return the location of the mouseEvent that triggered the * last showing of the ComponentPopup, or null if it was * triggered by keyboard. */ public Point getPopupTriggerLocation() { return popupTriggerLocation != null ? new Point(popupTriggerLocation) : null; } /** * {@inheritDoc}

* * Overridden to respect the column tooltip, if available. * * @return the column tooltip of the column at the mouse position * if not null or super if not available. */ @Override public String getToolTipText(MouseEvent event) { String columnToolTipText = getColumnToolTipText(event); return columnToolTipText != null ? columnToolTipText : super.getToolTipText(event); } /** * Returns the column tooltip of the column at the position * of the MouseEvent, if a tooltip is available. * * @param event the mouseEvent representing the mouse location. * @return the column tooltip of the column below the mouse location, * or null if not available. */ protected String getColumnToolTipText(MouseEvent event) { TableColumnExt columnExt = getColumnExt(event.getPoint()); return columnExt != null ? getColumnToolTipText(columnExt) : null; } /** * Returns the column tooltip for the given column or null if * none available.

* * This implementation returns the toolTipText property of the * given column. * * @param columnExt the column to return the tooltip for, must not be null * @return the tooltip for the column or null if none available. * * @see #getColumnToolTipText(MouseEvent) * @see #getToolTipText(MouseEvent) */ protected String getColumnToolTipText(TableColumnExt columnExt) { return columnExt.getToolTipText(); } /** * Returns the tableColumn at the given location or null if * the location is outside or the column is not of type * TableColumnExt. * * @param point the location to return the column for * @return the tableColumn at the location or null */ public TableColumnExt getColumnExt(Point point) { int column = columnAtPoint(point); if (column < 0) return null; // fix for issue 1560: must access the model to get the column // to make the tooltip work in stand-alone headers TableColumn tableColumn = getColumnModel().getColumn(column); return tableColumn instanceof TableColumnExt ? (TableColumnExt) tableColumn : null; } /** * Returns the associated table if it is of type JXTable, or null if not. * * @return the associated table if of type JXTable or null if not. */ public JXTable getXTable() { if (!(getTable() instanceof JXTable)) return null; return (JXTable) getTable(); } /** * Returns the resortsOnDoubleClick property. * * @return a flag indicating whether or not the second click in a mouseClicked * event should toggle the sort order again. * * @see #setResortsOnDoubleClick(boolean) */ public boolean getResortsOnDoubleClick() { return getXTable() != null && resortsOnDoubleClick; } /** * Sets the resortsOnDoubleClick property. If enabled, the second click * of a mouseClicked event will toggle the sort order again if the * column has been unsorted before. This is introduced to support * feature request #271-swingx. It is effective only if the coupled table * is of type JXTable and has full control about its RowSorter's properties. * * The default value is false. * * @param resortsOnDoubleClick a boolean indicating whether or not the * second click in a mouseClicked event should resort the column. * * @see #getResortsOnDoubleClick() */ public void setResortsOnDoubleClick(boolean resortsOnDoubleClick) { boolean old = getResortsOnDoubleClick(); this.resortsOnDoubleClick = resortsOnDoubleClick; firePropertyChange("resortsOnDoubleClick", old, getResortsOnDoubleClick()); } /** * Returns the TableCellRenderer to use for the column with the given index. This * implementation returns the column's header renderer if available or this header's * default renderer if not. * * @param columnIndex the index in view coordinates of the column * @return the renderer to use for the column, guaranteed to be not null. */ public TableCellRenderer getCellRenderer(int columnIndex) { TableCellRenderer renderer = getColumnModel().getColumn(columnIndex).getHeaderRenderer(); return renderer != null ? renderer : getDefaultRenderer(); } /** * {@inheritDoc}

* * Overridden to adjust for a reasonable minimum height. Done to fix Issue 334-swingx, * which actually is a core issue misbehaving in returning a zero height * if the first column has no text. * * @see #getPreferredSize(Dimension) * @see #getMinimumHeight(int). * */ @Override public Dimension getPreferredSize() { Dimension pref = super.getPreferredSize(); pref = getPreferredSize(pref); pref.height = getMinimumHeight(pref.height); return pref; } /** * Returns a preferred size which is adjusted to the maximum of all * header renderers' height requirement. * * @param pref an initial preferred size * @return the initial preferred size with its height property adjusted * to the maximum of all renderers preferred height requirement. * * @see #getPreferredSize() * @see #getMinimumHeight(int) */ protected Dimension getPreferredSize(Dimension pref) { int height = pref.height; for (int i = 0; i < getColumnModel().getColumnCount(); i++) { TableCellRenderer renderer = getCellRenderer(i); Component comp = renderer.getTableCellRendererComponent(table, getColumnModel().getColumn(i).getHeaderValue(), false, false, -1, i); height = Math.max(height, comp.getPreferredSize().height); } pref.height = height; return pref; } /** * Returns a reasonable minimal preferred height for the header. This is * meant as a last straw if all header values are null, renderers report 0 as * their preferred height.

* * This implementation returns the default header renderer's preferred height as measured * with a dummy value if the input height is 0, otherwise returns the height * unchanged. * * @param height the initial height. * @return a reasonable minimal preferred height. * * @see #getPreferredSize() * @see #getPreferredSize(Dimension) */ protected int getMinimumHeight(int height) { if ((height == 0)) { // && (getXTable() != null) // && getXTable().isColumnControlVisible()){ TableCellRenderer renderer = getDefaultRenderer(); Component comp = renderer.getTableCellRendererComponent(getTable(), "dummy", false, false, -1, -1); height = comp.getPreferredSize().height; } return height; } /** * @inherited

* * Overridden to fire a propertyChange for draggedColumn. */ @Override public void setDraggedColumn(TableColumn column) { if (getDraggedColumn() == column) return; TableColumn old = getDraggedColumn(); super.setDraggedColumn(column); firePropertyChange("draggedColumn", old, getDraggedColumn()); } /** * @inherited

* * Overridden to fire a propertyChange for resizingColumn. */ @Override public void setResizingColumn(TableColumn aColumn) { if (getResizingColumn() == aColumn) return; TableColumn old = getResizingColumn(); super.setResizingColumn(aColumn); firePropertyChange("resizingColumn", old, getResizingColumn()); } /** * {@inheritDoc}

* * Overridden to scroll the table to keep the dragged column visible. * This side-effect is enabled only if the header's autoscroll property is * true and the associated table is of type JXTable.

* * The autoscrolls is disabled by default. With or without - core * issue #6503981 has weird effects (for jdk 1.6 - 1.6u3) on a plain * JTable as well as a JXTable, fixed in 1.6u4. * */ @Override public void setDraggedDistance(int distance) { int old = getDraggedDistance(); super.setDraggedDistance(distance); // fire because super doesn't firePropertyChange("draggedDistance", old, getDraggedDistance()); if (!getAutoscrolls() || (getXTable() == null)) return; TableColumn column = getDraggedColumn(); // fix for #788-swingx: don't try to scroll if we have no dragged column // as doing will confuse the horizontalScrollEnabled on the JXTable. if (column != null) { getXTable().scrollColumnToVisible(getViewIndexForColumn(column)); } } /** * Returns the the dragged column if and only if, a drag is in process and * the column is visible, otherwise returns null. * * @return the dragged column, if a drag is in process and the column is * visible, otherwise returns null * @see #getDraggedDistance */ @Override public TableColumn getDraggedColumn() { return isVisible(draggedColumn) ? draggedColumn : null; } /** * Checks and returns the column's visibility. * * @param column the TableColumn to check * @return a boolean indicating if the column is visible */ private boolean isVisible(TableColumn column) { return getViewIndexForColumn(column) >= 0; } /** * Returns the (visible) view index for the table column * or -1 if not visible or not contained in this header's * columnModel. * * * @param aColumn the TableColumn to find the view index for * @return the view index of the given table column or -1 if not visible * or not contained in the column model. */ private int getViewIndexForColumn(TableColumn aColumn) { if (aColumn == null) return -1; TableColumnModel cm = getColumnModel(); for (int column = 0; column < cm.getColumnCount(); column++) { if (cm.getColumn(column) == aColumn) { return column; } } return -1; } /** * Returns the PropertyChangeListener to register on the owning table, * lazily created. * * @return the PropertyChangeListener to use on the owning table. */ protected PropertyChangeListener getTablePropertyChangeListener() { if (tablePropertyChangeListener == null) { tablePropertyChangeListener = createTablePropertyChangeListener(); } return tablePropertyChangeListener; } /** * Creates and returns the PropertyChangeListener to register on the * owning table.

* * This implementation synchs the header's enabled properties with the * table's enabled. * * @return the PropertyChangeListener to register on the owning table. */ protected PropertyChangeListener createTablePropertyChangeListener() { PropertyChangeListener l = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if ("enabled".equals(evt.getPropertyName())) { updateEnabledFromTable(); } } }; return l; } /** * Creates and installs header listeners to service the extended functionality. * This implementation creates and installs a custom mouse input listener. */ protected void installHeaderListener() { if (headerListener == null) { headerListener = new HeaderListener(); addMouseListener(headerListener); addMouseMotionListener(headerListener); } } /** * Uninstalls header listeners to service the extended functionality. * This implementation uninstalls a custom mouse input listener. */ protected void uninstallHeaderListener() { if (headerListener != null) { removeMouseListener(headerListener); removeMouseMotionListener(headerListener); headerListener = null; } } private MouseInputListener headerListener; /** * A MouseListener implementation to support enhanced tableHeader functionality. * * Supports column "packing" by double click in resize region. Works around * core issue #6862170 (must not sort column by click into resize region). *

* * Note that the logic is critical, mostly because it must be independent of * sequence of listener notification. So we check whether or not a pressed * happens in the resizing region in both pressed and released, taking the * header's resizingColumn property as a marker. The inResize flag can only * be turned on in those. At the end of the released, we check if we are * in resize and disable core sorting - which happens in clicked - if appropriate. * In our clicked we hook the pack action (happens only on double click) * and reset the resizing region flag always. Pressed (and all other methods) * restore sorting enablement. *

* * Supports resort on double click if enabled in the JXTableHeader (Issue #271-swingx). * * Is fully effective only if JXTable has control over the row sorter, that is * if the row sorter is of type SortController. * */ private class HeaderListener implements MouseInputListener, Serializable { private TableColumn cachedResizingColumn; private SortOrder[] cachedSortOrderCycle; private int sortColumn = -1; /** * Packs column on double click in resize region. Resorts * column on double click if enabled and not in resize region. */ @Override public void mouseClicked(MouseEvent e) { if (shouldIgnore(e)) { return; } doResize(e); doDoubleSort(e); uncacheResizingColumn(); } private void doDoubleSort(MouseEvent e) { if (!hasCachedSortColumn() || e.getClickCount() % 2 == 1) return; getXTable().toggleSortOrder(sortColumn); uncacheSortColumn(); } private boolean hasCachedSortColumn() { return sortColumn >= 0; } /** * Resets sort enablement always, set resizing marker if available. */ @Override public void mousePressed(MouseEvent e) { resetToggleSortOrder(e); if (shouldIgnore(e)) { return; } cacheResizingColumn(e); } /** * Sets resizing marker if available, disables table sorting if in * resize region and sort gesture (aka: single click). */ @Override public void mouseReleased(MouseEvent e) { if (shouldIgnore(e)) { return; } cacheResizingColumn(e); cacheSortColumn(e); if (isInResizeRegion(e) && e.getClickCount() % 2 == 1) { disableToggleSortOrder(e); } } private void cacheSortColumn(MouseEvent e) { if (!canCacheSortColumn(e)) uncacheSortColumn(); if (e.getClickCount() % 2 == 1) { int column = columnAtPoint(e.getPoint()); if (column >= 0) { int primarySortIndex = getXTable().getSortedColumnIndex(); if (primarySortIndex == column) { column = -1; } } sortColumn = column; } } private void uncacheSortColumn() { sortColumn = -1; } private boolean canCacheSortColumn(MouseEvent e) { if (hasSortController() && !isInResizeRegion(e) && getResortsOnDoubleClick()) { return true; } return false; } /** * Returns a boolean indication if the mouse event should be ignored. * Here: returns true if table not enabled or not an event from the left mouse * button. * * @param e * @return */ private boolean shouldIgnore(MouseEvent e) { return !SwingUtilities.isLeftMouseButton(e) || !table.isEnabled(); } /** * Packs caches resizing column on double click, if available. Does nothing * otherwise. * * @param e */ private void doResize(MouseEvent e) { if (e.getClickCount() != 2) return; int column = getViewIndexForColumn(cachedResizingColumn); if (column >= 0) { (getXTable()).packColumn(column, 5); } } /** * * @param e */ private void disableToggleSortOrder(MouseEvent e) { if (!hasSortController()) return; SortController controller = (SortController) getXTable().getRowSorter(); cachedSortOrderCycle = controller.getSortOrderCycle(); controller.setSortOrderCycle(); } /** * @return */ private boolean hasSortController() { return (getXTable().getRowSorter() instanceof SortController); } /** * */ private void resetToggleSortOrder(MouseEvent e) { if (cachedSortOrderCycle == null) return; ((SortController) getXTable().getRowSorter()).setSortOrderCycle(cachedSortOrderCycle); cachedSortOrderCycle = null; } /** * Caches the resizing column if set. Does nothing if null. * * @param e */ private void cacheResizingColumn(MouseEvent e) { TableColumn column = getResizingColumn(); if (column != null) { cachedResizingColumn = column; } } /** * Sets the cached resizing column to null. */ private void uncacheResizingColumn() { cachedResizingColumn = null; } /** * Returns true if the mouseEvent happened in the resizing region. * * @param e * @return */ private boolean isInResizeRegion(MouseEvent e) { return cachedResizingColumn != null; // inResize; } @Override public void mouseEntered(MouseEvent e) { } /** * Resets all cached state. */ @Override public void mouseExited(MouseEvent e) { uncacheSortColumn(); uncacheResizingColumn(); resetToggleSortOrder(e); } /** * Resets all cached state. */ @Override public void mouseDragged(MouseEvent e) { uncacheSortColumn(); uncacheResizingColumn(); resetToggleSortOrder(e); } /** * Resets all cached state. */ @Override public void mouseMoved(MouseEvent e) { uncacheSortColumn(); resetToggleSortOrder(e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy