* [ 1821870 ] Add column visibility control to grid
This commit is contained in:
parent
e33b46da0d
commit
7b15ae945d
|
@ -3,5 +3,6 @@
|
||||||
<classpathentry kind="src" path="src"/>
|
<classpathentry kind="src" path="src"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||||
<classpathentry kind="lib" path="/tools/lib/looks-2.0.4.jar"/>
|
<classpathentry kind="lib" path="/tools/lib/looks-2.0.4.jar"/>
|
||||||
|
<classpathentry kind="lib" path="/tools/lib/swingx-0.9.0.jar"/>
|
||||||
<classpathentry kind="output" path="build"/>
|
<classpathentry kind="output" path="build"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|
|
@ -0,0 +1,857 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Product: Adempiere ERP & CRM Smart Business Solution *
|
||||||
|
* Copyright (C) 2007 Adempiere, Inc. All Rights Reserved. *
|
||||||
|
* This program is free software; you can redistribute it and/or modify it *
|
||||||
|
* under the terms version 2 of the GNU General Public License as published *
|
||||||
|
* by the Free Software Foundation. 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 General Public License for more details. *
|
||||||
|
* You should have received a copy of the GNU General Public License along *
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||||
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
||||||
|
*****************************************************************************/
|
||||||
|
package org.compiere.swing;
|
||||||
|
|
||||||
|
import org.jdesktop.swingx.VerticalLayout;
|
||||||
|
import org.jdesktop.swingx.action.AbstractActionExt;
|
||||||
|
import org.jdesktop.swingx.action.ActionContainerFactory;
|
||||||
|
import org.jdesktop.swingx.table.ColumnControlPopup;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
import javax.swing.event.TableColumnModelEvent;
|
||||||
|
import javax.swing.event.TableColumnModelListener;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
import javax.swing.table.TableColumnModel;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ItemEvent;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code and description adapted from SwingX ColumnControlButton class.
|
||||||
|
*
|
||||||
|
* A component to allow interactive customization of <code>CTable</code>'s
|
||||||
|
* columns.
|
||||||
|
* It's main purpose is to allow toggling of table columns' visibility.
|
||||||
|
* Additionally, arbitrary configuration actions can be exposed.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* This component is installed in the <code>CTable</code>'s
|
||||||
|
* trailing corner, if enabled:
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* table.setColumnControlVisible(true);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* From the perspective of a <code>CTable</code>, the component's behaviour is
|
||||||
|
* opaque. Typically, the button's action is to popup a component for user
|
||||||
|
* interaction. <p>
|
||||||
|
*
|
||||||
|
* This class is responsible for handling/providing/updating the lists of
|
||||||
|
* actions and to keep all action's state in synch with Table-/Column state.
|
||||||
|
* The visible behaviour of the popup is delegated to a
|
||||||
|
* <code>ColumnControlPopup</code>. <p>
|
||||||
|
*
|
||||||
|
* @see CTable#setColumnControl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class CColumnControlButton extends JButton {
|
||||||
|
/** Marker to auto-recognize actions which should be added to the popup. */
|
||||||
|
public static final String COLUMN_CONTROL_MARKER = "column.";
|
||||||
|
/** exposed for testing. */
|
||||||
|
protected ColumnControlPopup popup;
|
||||||
|
// TODO: the table reference is a potential leak?
|
||||||
|
/** The table which is controlled by this. */
|
||||||
|
private CTable table;
|
||||||
|
/** Listener for table property changes. */
|
||||||
|
private PropertyChangeListener tablePropertyChangeListener;
|
||||||
|
/** Listener for table's columnModel. */
|
||||||
|
TableColumnModelListener columnModelListener;
|
||||||
|
/** the list of actions for column menuitems.*/
|
||||||
|
private List<ColumnVisibilityAction> columnVisibilityActions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a column control button for the table. The button
|
||||||
|
* uses the given icon and has no text.
|
||||||
|
* @param table the <code>JTable</code> controlled by this component
|
||||||
|
* @param icon the <code>Icon</code> to show
|
||||||
|
*/
|
||||||
|
public CColumnControlButton(CTable table, Icon icon) {
|
||||||
|
super();
|
||||||
|
init();
|
||||||
|
// JW: icon LF dependent?
|
||||||
|
setAction(createControlAction(icon));
|
||||||
|
installTable(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateUI() {
|
||||||
|
super.updateUI();
|
||||||
|
// JW: icon LF dependent?
|
||||||
|
setMargin(new Insets(1, 2, 2, 1)); // Make this LAF-independent
|
||||||
|
getColumnControlPopup().updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the popup component's visibility. This method is
|
||||||
|
* called by this control's default action. <p>
|
||||||
|
*
|
||||||
|
* Here: delegates to getControlPopup().
|
||||||
|
*/
|
||||||
|
public void togglePopup() {
|
||||||
|
getColumnControlPopup().toggleVisibility(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyComponentOrientation(ComponentOrientation o) {
|
||||||
|
super.applyComponentOrientation(o);
|
||||||
|
getColumnControlPopup().applyComponentOrientation(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------- Action in synch with column properties
|
||||||
|
/**
|
||||||
|
* A specialized <code>Action</code> which takes care of keeping in synch with
|
||||||
|
* TableColumn state.
|
||||||
|
*
|
||||||
|
* NOTE: client must call releaseColumn if this action is no longer needed!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ColumnVisibilityAction extends AbstractActionExt {
|
||||||
|
|
||||||
|
private TableColumn column;
|
||||||
|
|
||||||
|
private PropertyChangeListener columnListener;
|
||||||
|
|
||||||
|
/** flag to distinguish selection changes triggered by
|
||||||
|
* column's property change from those triggered by
|
||||||
|
* user interaction. Hack around #212-swingx.
|
||||||
|
*/
|
||||||
|
private boolean fromColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a action synched to the table column.
|
||||||
|
*
|
||||||
|
* @param column the <code>TableColumn</code> to keep synched to.
|
||||||
|
*/
|
||||||
|
public ColumnVisibilityAction(TableColumn column) {
|
||||||
|
super((String) null);
|
||||||
|
setStateAction();
|
||||||
|
installColumn(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases all references to the synched <code>TableColumn</code>.
|
||||||
|
* Client code must call this method if the
|
||||||
|
* action is no longer needed. After calling this action must not be
|
||||||
|
* used any longer.
|
||||||
|
*/
|
||||||
|
public void releaseColumn() {
|
||||||
|
column.removePropertyChangeListener(columnListener);
|
||||||
|
column = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void itemStateChanged(final ItemEvent e) {
|
||||||
|
if ((e.getStateChange() == ItemEvent.DESELECTED)
|
||||||
|
//JW: guarding against 1 leads to #212-swingx: setting
|
||||||
|
// column visibility programatically fails if
|
||||||
|
// the current column is the second last visible
|
||||||
|
// guarding against 0 leads to hiding all columns
|
||||||
|
// by deselecting the menu item.
|
||||||
|
&& (table.getColumnCount() <= 1)
|
||||||
|
// JW Fixed #212: basically implemented Rob's idea to distinguish
|
||||||
|
// event sources instead of unconditionally reselect
|
||||||
|
// not entirely sure if the state transitions are completely
|
||||||
|
// defined but all related tests are passing now.
|
||||||
|
&& !fromColumn) {
|
||||||
|
reselect();
|
||||||
|
} else {
|
||||||
|
setSelected(e.getStateChange() == ItemEvent.SELECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSelected(boolean newValue) {
|
||||||
|
super.setSelected(newValue);
|
||||||
|
if (!newValue) {
|
||||||
|
if (table.isColumnVisible(column)) {
|
||||||
|
table.setColumnVisibility(column, newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!table.isColumnVisible(column)) {
|
||||||
|
table.setColumnVisibility(column, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing. Synch from action state to TableColumn state
|
||||||
|
* is done in itemStateChanged.
|
||||||
|
*/
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchs selected property to visible. This
|
||||||
|
* is called on change of tablecolumn's <code>visible</code> property.
|
||||||
|
*
|
||||||
|
* @param visible column visible state to synch to.
|
||||||
|
*/
|
||||||
|
private void updateFromColumnVisible(boolean visible) {
|
||||||
|
fromColumn = true;
|
||||||
|
setSelected(visible);
|
||||||
|
fromColumn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchs name property to value. This is called on change of
|
||||||
|
* tableColumn's <code>headerValue</code> property.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
private void updateFromColumnHeader(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
this.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
setName(String.valueOf(value));
|
||||||
|
this.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforces selected to <code>true</code>. Called if user interaction
|
||||||
|
* tried to de-select the last single visible column.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private void reselect() {
|
||||||
|
firePropertyChange("selected", null, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- init
|
||||||
|
private void installColumn(TableColumn column) {
|
||||||
|
this.column = column;
|
||||||
|
column.addPropertyChangeListener(getColumnListener());
|
||||||
|
updateFromColumnHeader(column.getHeaderValue());
|
||||||
|
// #429-swing: actionCommand must be string
|
||||||
|
if (column.getIdentifier() != null) {
|
||||||
|
setActionCommand(column.getIdentifier().toString());
|
||||||
|
}
|
||||||
|
boolean visible = table.isColumnVisible(column);
|
||||||
|
updateFromColumnVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the listener to column's property changes. The listener
|
||||||
|
* is created lazily if necessary.
|
||||||
|
*
|
||||||
|
* @return the <code>PropertyChangeListener</code> listening to
|
||||||
|
* <code>TableColumn</code>'s property changes, guaranteed to be
|
||||||
|
* not <code>null</code>.
|
||||||
|
*/
|
||||||
|
protected PropertyChangeListener getColumnListener() {
|
||||||
|
if (columnListener == null) {
|
||||||
|
columnListener = createPropertyChangeListener();
|
||||||
|
}
|
||||||
|
return columnListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns the listener to column's property changes.
|
||||||
|
* Subclasses are free to roll their own.
|
||||||
|
* <p>
|
||||||
|
* Implementation note: this listener reacts to column's
|
||||||
|
* <code>visible</code> and <code>headerValue</code> properties and
|
||||||
|
* calls the respective <code>updateFromXX</code> methodes.
|
||||||
|
*
|
||||||
|
* @return the <code>PropertyChangeListener</code> to use with the
|
||||||
|
* column
|
||||||
|
*/
|
||||||
|
protected PropertyChangeListener createPropertyChangeListener() {
|
||||||
|
return new PropertyChangeListener() {
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if ("visible".equals(evt.getPropertyName())) {
|
||||||
|
updateFromColumnVisible((Boolean) evt.getNewValue());
|
||||||
|
} else if ("headerValue".equals(evt.getPropertyName())) {
|
||||||
|
updateFromColumnHeader(evt.getNewValue());
|
||||||
|
if (evt.getNewValue() == null)
|
||||||
|
populatePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------- the popup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default implementation of ColumnControlPopup.
|
||||||
|
* It uses a JPopupMenu with
|
||||||
|
* MenuItems corresponding to the Actions as
|
||||||
|
* provided by the ColumnControlButton.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DefaultColumnControlPopup implements ColumnControlPopup {
|
||||||
|
private JPopupMenu popupMenu;
|
||||||
|
private JScrollPane scroller;
|
||||||
|
private JPanel panelMenus;
|
||||||
|
|
||||||
|
//------------------ public methods to control visibility status
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void updateUI() {
|
||||||
|
SwingUtilities.updateComponentTreeUI(getPopupMenu());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel getPanelMenu() {
|
||||||
|
if (panelMenus == null) {
|
||||||
|
panelMenus = new JPanel();
|
||||||
|
panelMenus.setLayout(new VerticalLayout());
|
||||||
|
panelMenus.setBackground(UIManager.getColor("MenuItem.background"));
|
||||||
|
panelMenus.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
}
|
||||||
|
return panelMenus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JScrollPane getScroller() {
|
||||||
|
if (scroller == null) {
|
||||||
|
scroller = createScroller();
|
||||||
|
scroller.getVerticalScrollBar().setFocusable( false );
|
||||||
|
scroller.setViewportView(getPanelMenu());
|
||||||
|
}
|
||||||
|
return scroller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void toggleVisibility(JComponent owner) {
|
||||||
|
JPopupMenu popupMenu = getPopupMenu();
|
||||||
|
JPanel panel = getPanelMenu();
|
||||||
|
if (popupMenu.isVisible()) {
|
||||||
|
popupMenu.setVisible(false);
|
||||||
|
} else if (panel.getComponentCount() > 0) {
|
||||||
|
JScrollPane scroller = getScroller();
|
||||||
|
panel.validate();
|
||||||
|
|
||||||
|
Dimension pSize = table.getParent().getSize();
|
||||||
|
Dimension size = panel.getPreferredSize();
|
||||||
|
if (size.height >= pSize.height) {
|
||||||
|
scroller.setPreferredSize(new Dimension(size.width, pSize.height - 30));
|
||||||
|
} else {
|
||||||
|
scroller.setPreferredSize(size);
|
||||||
|
}
|
||||||
|
popupMenu.setPopupSize(new Dimension(scroller.getPreferredSize().width + 20,
|
||||||
|
scroller.getPreferredSize().height - 20));
|
||||||
|
|
||||||
|
Dimension buttonSize = owner.getSize();
|
||||||
|
int xPos = owner.getComponentOrientation().isLeftToRight() ? buttonSize.width
|
||||||
|
- popupMenu.getPreferredSize().width
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
popupMenu.show(owner,
|
||||||
|
xPos, buttonSize.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void applyComponentOrientation(ComponentOrientation o) {
|
||||||
|
getPopupMenu().applyComponentOrientation(o);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------- public methods to manipulate popup contents.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void removeAll() {
|
||||||
|
getPanelMenu().removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void addVisibilityActionItems(
|
||||||
|
List<? extends AbstractActionExt> actions) {
|
||||||
|
addItems(new ArrayList<Action>(actions));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void addAdditionalActionItems(List<? extends Action> actions) {
|
||||||
|
if (actions.size() == 0)
|
||||||
|
return;
|
||||||
|
addSeparator();
|
||||||
|
addItems(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------- internal helpers to manipulate popups content
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here: creates and adds a menuItem to the popup for every
|
||||||
|
* Action in the list. Does nothing if
|
||||||
|
* if the list is empty.
|
||||||
|
*
|
||||||
|
* PRE: actions != null.
|
||||||
|
*
|
||||||
|
* @param actions a list containing the actions to add to the popup.
|
||||||
|
* Must not be null.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void addItems(List<? extends Action> actions) {
|
||||||
|
ActionContainerFactory factory = new ActionContainerFactory(null);
|
||||||
|
for (Action action : actions) {
|
||||||
|
AbstractActionExt a = (AbstractActionExt)action;
|
||||||
|
if (action.isEnabled()) {
|
||||||
|
if (a.isStateAction())
|
||||||
|
addItem(createCheckBox((AbstractActionExt)action));
|
||||||
|
else {
|
||||||
|
addItem(factory.createButton(action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JCheckBox createCheckBox(AbstractActionExt action) {
|
||||||
|
JCheckBox c = new JCheckBox(action);
|
||||||
|
c.setSelected(action.isSelected());
|
||||||
|
c.addItemListener(action);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a separator to the popup.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void addSeparator() {
|
||||||
|
getPanelMenu().add(Box.createVerticalStrut(2));
|
||||||
|
getPanelMenu().add(new JSeparator());
|
||||||
|
getPanelMenu().add(Box.createVerticalStrut(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param item the menuItem to add to the popup.
|
||||||
|
*/
|
||||||
|
protected void addItem(AbstractButton item) {
|
||||||
|
getPanelMenu().add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the popup to add menuitems. Guaranteed to be != null.
|
||||||
|
*/
|
||||||
|
protected JPopupMenu getPopupMenu() {
|
||||||
|
if (popupMenu == null) {
|
||||||
|
popupMenu = new JPopupMenu();
|
||||||
|
popupMenu.removeAll();
|
||||||
|
popupMenu.setLayout(new BorderLayout());
|
||||||
|
popupMenu.add(getScroller(), BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
return popupMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the scroll pane which houses the scrollable list.
|
||||||
|
*/
|
||||||
|
protected JScrollPane createScroller() {
|
||||||
|
JScrollPane sp = new JScrollPane( null,
|
||||||
|
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
|
||||||
|
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
|
||||||
|
sp.setHorizontalScrollBar(null);
|
||||||
|
sp.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns to popup component for user interaction. Lazily
|
||||||
|
* creates the component if necessary.
|
||||||
|
*
|
||||||
|
* @return the ColumnControlPopup for showing the items, guaranteed
|
||||||
|
* to be not <code>null</code>.
|
||||||
|
* @see #createColumnControlPopup()
|
||||||
|
*/
|
||||||
|
protected ColumnControlPopup getColumnControlPopup() {
|
||||||
|
if (popup == null) {
|
||||||
|
popup = createColumnControlPopup();
|
||||||
|
}
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to return a <code>ColumnControlPopup</code>.
|
||||||
|
* Subclasses can override to hook custom implementations.
|
||||||
|
*
|
||||||
|
* @return the <code>ColumnControlPopup</code> used.
|
||||||
|
*/
|
||||||
|
protected ColumnControlPopup createColumnControlPopup() {
|
||||||
|
return new DefaultColumnControlPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------- updates from table propertyChangelistnere
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts internal state after table's column model property has changed.
|
||||||
|
* Handles cleanup of listeners to the old/new columnModel (Note, that
|
||||||
|
* it listens to the column model only if it can control column visibility).
|
||||||
|
* Updates content of popup.
|
||||||
|
*
|
||||||
|
* @param oldModel the old <code>TableColumnModel</code> we had been listening to.
|
||||||
|
*/
|
||||||
|
protected void updateFromColumnModelChange(TableColumnModel oldModel) {
|
||||||
|
if (oldModel != null && columnModelListener != null) {
|
||||||
|
oldModel.removeColumnModelListener(columnModelListener);
|
||||||
|
}
|
||||||
|
populatePopup();
|
||||||
|
table.getColumnModel().addColumnModelListener(getColumnModelListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchs this button's enabled with table's enabled.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void updateFromTableEnabledChanged() {
|
||||||
|
getAction().setEnabled(table.isEnabled());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------ updating the popup
|
||||||
|
/**
|
||||||
|
* Populates the popup from scratch.
|
||||||
|
*
|
||||||
|
* If applicable, creates and adds column visibility actions. Always adds
|
||||||
|
* additional actions.
|
||||||
|
*/
|
||||||
|
protected void populatePopup() {
|
||||||
|
clearAll();
|
||||||
|
createVisibilityActions();
|
||||||
|
addVisibilityActionItems();
|
||||||
|
addAdditionalActionItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* removes all components from the popup, making sure to release all
|
||||||
|
* columnVisibility actions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void clearAll() {
|
||||||
|
clearColumnVisibilityActions();
|
||||||
|
getColumnControlPopup().removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases actions and clears list of actions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void clearColumnVisibilityActions() {
|
||||||
|
if (columnVisibilityActions == null)
|
||||||
|
return;
|
||||||
|
for (ColumnVisibilityAction action : columnVisibilityActions) {
|
||||||
|
action.releaseColumn();
|
||||||
|
}
|
||||||
|
columnVisibilityActions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds visibility actions into the popup view.
|
||||||
|
*
|
||||||
|
* Here: delegates the list of actions to the DefaultColumnControlPopup.
|
||||||
|
* <p>
|
||||||
|
* PRE: columnVisibilityActions populated before calling this.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void addVisibilityActionItems() {
|
||||||
|
getColumnControlPopup().addVisibilityActionItems(
|
||||||
|
Collections.unmodifiableList(getColumnVisibilityActions()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds additional actions to the popup.
|
||||||
|
* Here: delegates the list of actions as returned by #getAdditionalActions()
|
||||||
|
* to the DefaultColumnControlPopup.
|
||||||
|
* Does nothing if #getColumnActions() is empty.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected void addAdditionalActionItems() {
|
||||||
|
getColumnControlPopup().addAdditionalActionItems(
|
||||||
|
Collections.unmodifiableList(getAdditionalActions()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and adds a ColumnVisiblityAction for every column that should be
|
||||||
|
* togglable via the column control. <p>
|
||||||
|
*
|
||||||
|
* Here: all table columns contained in the <code>TableColumnModel</code> -
|
||||||
|
* visible and invisible columns - to <code>createColumnVisibilityAction</code> and
|
||||||
|
* adds all not <code>null</code> return values.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* PRE: canControl()
|
||||||
|
*
|
||||||
|
* @see #createColumnVisibilityAction
|
||||||
|
*/
|
||||||
|
protected void createVisibilityActions() {
|
||||||
|
Enumeration<TableColumn> columns = table.getColumnModel().getColumns();
|
||||||
|
while(columns.hasMoreElements()) {
|
||||||
|
TableColumn column = columns.nextElement();
|
||||||
|
if (column.getHeaderValue() != null) {
|
||||||
|
ColumnVisibilityAction action = createColumnVisibilityAction(column);
|
||||||
|
if (action != null) {
|
||||||
|
getColumnVisibilityActions().add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a <code>ColumnVisibilityAction</code> for the given
|
||||||
|
* <code>TableColumn</code>. The return value might be null, f.i. if the
|
||||||
|
* column should not be allowed to be toggled.
|
||||||
|
*
|
||||||
|
* @param column the <code>TableColumn</code> to use for the action
|
||||||
|
* @return a ColumnVisibilityAction to use for the given column,
|
||||||
|
* may be <code>null</code>.
|
||||||
|
*/
|
||||||
|
protected ColumnVisibilityAction createColumnVisibilityAction(TableColumn column) {
|
||||||
|
return new ColumnVisibilityAction(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazyly creates and returns the List of visibility actions.
|
||||||
|
*
|
||||||
|
* @return the list of visibility actions, guaranteed to be != null.
|
||||||
|
*/
|
||||||
|
protected List<ColumnVisibilityAction> getColumnVisibilityActions() {
|
||||||
|
if (columnVisibilityActions == null) {
|
||||||
|
columnVisibilityActions = new ArrayList<ColumnVisibilityAction>();
|
||||||
|
}
|
||||||
|
return columnVisibilityActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates and returns a list of additional Actions to add to the popup.
|
||||||
|
* Here: the actions are looked up in the table's actionMap according
|
||||||
|
* to the keys as returned from #getColumnControlActionKeys();
|
||||||
|
*
|
||||||
|
* @return a list containing all additional actions to include into the popup.
|
||||||
|
*/
|
||||||
|
protected List<Action> getAdditionalActions() {
|
||||||
|
List actionKeys = getColumnControlActionKeys();
|
||||||
|
List<Action> actions = new ArrayList<Action>();
|
||||||
|
for (Object key : actionKeys) {
|
||||||
|
actions.add(table.getActionMap().get(key));
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up and returns action keys to access actions in the
|
||||||
|
* table's actionMap which should be included into the popup.
|
||||||
|
*
|
||||||
|
* Here: all keys with isColumnControlActionKey(key). The list
|
||||||
|
* is sorted by those keys.
|
||||||
|
*
|
||||||
|
* @return the action keys of table's actionMap entries whose
|
||||||
|
* action should be included into the popup.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected List getColumnControlActionKeys() {
|
||||||
|
Object[] allKeys = table.getActionMap().allKeys();
|
||||||
|
List columnKeys = new ArrayList();
|
||||||
|
for (int i = 0; i < allKeys.length; i++) {
|
||||||
|
if (isColumnControlActionKey(allKeys[i])) {
|
||||||
|
columnKeys.add(allKeys[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JW: this will blow for non-String keys!
|
||||||
|
// so this method is less decoupled from the
|
||||||
|
// decision method isControl than expected.
|
||||||
|
Collections.sort(columnKeys);
|
||||||
|
return columnKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here: true if a String key starts with #COLUMN_CONTROL_MARKER.
|
||||||
|
*
|
||||||
|
* @param actionKey a key in the table's actionMap.
|
||||||
|
* @return a boolean to indicate whether the given actionKey maps to
|
||||||
|
* an action which should be included into the popup.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected boolean isColumnControlActionKey(Object actionKey) {
|
||||||
|
return (actionKey instanceof String) &&
|
||||||
|
((String) actionKey).startsWith(COLUMN_CONTROL_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------- init
|
||||||
|
|
||||||
|
private void installTable(CTable table) {
|
||||||
|
this.table = table;
|
||||||
|
table.addPropertyChangeListener(getTablePropertyChangeListener());
|
||||||
|
updateFromColumnModelChange(null);
|
||||||
|
updateFromTableEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the column control button's gui
|
||||||
|
*/
|
||||||
|
private void init() {
|
||||||
|
setFocusPainted(false);
|
||||||
|
setFocusable(false);
|
||||||
|
// this is a trick to get hold of the client prop which
|
||||||
|
// prevents closing of the popup
|
||||||
|
JComboBox box = new JComboBox();
|
||||||
|
Object preventHide = box.getClientProperty("doNotCancelPopup");
|
||||||
|
putClientProperty("doNotCancelPopup", preventHide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns the default action for this button.
|
||||||
|
*
|
||||||
|
* @param icon the Icon to use in the action.
|
||||||
|
* @return the default action.
|
||||||
|
*/
|
||||||
|
private Action createControlAction(Icon icon) {
|
||||||
|
Action control = new AbstractAction() {
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
togglePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
control.putValue(Action.SMALL_ICON, icon);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------- listeners
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the listener to table's property changes. The listener is
|
||||||
|
* lazily created if necessary.
|
||||||
|
* @return the <code>PropertyChangeListener</code> for use with the
|
||||||
|
* table, guaranteed to be not <code>null</code>.
|
||||||
|
*/
|
||||||
|
protected PropertyChangeListener getTablePropertyChangeListener() {
|
||||||
|
if (tablePropertyChangeListener == null) {
|
||||||
|
tablePropertyChangeListener = createTablePropertyChangeListener();
|
||||||
|
}
|
||||||
|
return tablePropertyChangeListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the listener to table's property changes. Subclasses are free
|
||||||
|
* to roll their own. <p>
|
||||||
|
* Implementation note: this listener reacts to table's <code>enabled</code> and
|
||||||
|
* <code>columnModel</code> properties and calls the respective
|
||||||
|
* <code>updateFromXX</code> methodes.
|
||||||
|
*
|
||||||
|
* @return the <code>PropertyChangeListener</code> for use with the table.
|
||||||
|
*/
|
||||||
|
protected PropertyChangeListener createTablePropertyChangeListener() {
|
||||||
|
return new PropertyChangeListener() {
|
||||||
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
if ("columnModel".equals(evt.getPropertyName())) {
|
||||||
|
updateFromColumnModelChange((TableColumnModel) evt
|
||||||
|
.getOldValue());
|
||||||
|
} else if ("enabled".equals(evt.getPropertyName())) {
|
||||||
|
updateFromTableEnabledChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the listener to table's column model. The listener is
|
||||||
|
* lazily created if necessary.
|
||||||
|
* @return the <code>TableColumnModelListener</code> for use with the
|
||||||
|
* table's column model, guaranteed to be not <code>null</code>.
|
||||||
|
*/
|
||||||
|
protected TableColumnModelListener getColumnModelListener() {
|
||||||
|
if (columnModelListener == null) {
|
||||||
|
columnModelListener = createColumnModelListener();
|
||||||
|
}
|
||||||
|
return columnModelListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the listener to columnModel. Subclasses are free to roll their
|
||||||
|
* own.
|
||||||
|
* <p>
|
||||||
|
* Implementation note: this listener reacts to "real" columnRemoved/-Added by
|
||||||
|
* populating the popups content from scratch.
|
||||||
|
*
|
||||||
|
* @return the <code>TableColumnModelListener</code> for use with the
|
||||||
|
* table's columnModel.
|
||||||
|
*/
|
||||||
|
protected TableColumnModelListener createColumnModelListener() {
|
||||||
|
return new TableColumnModelListener() {
|
||||||
|
/** Tells listeners that a column was added to the model. */
|
||||||
|
public void columnAdded(TableColumnModelEvent e) {
|
||||||
|
populatePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tells listeners that a column was removed from the model. */
|
||||||
|
public void columnRemoved(TableColumnModelEvent e) {
|
||||||
|
populatePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Tells listeners that a column was repositioned. */
|
||||||
|
public void columnMoved(TableColumnModelEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tells listeners that a column was moved due to a margin change. */
|
||||||
|
public void columnMarginChanged(ChangeEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells listeners that the selection model of the TableColumnModel
|
||||||
|
* changed.
|
||||||
|
*/
|
||||||
|
public void columnSelectionChanged(ListSelectionEvent e) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end class ColumnControlButton
|
|
@ -19,11 +19,14 @@ package org.compiere.swing;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.*;
|
import javax.swing.event.*;
|
||||||
import javax.swing.table.*;
|
import javax.swing.table.*;
|
||||||
|
|
||||||
import org.compiere.util.*;
|
import org.compiere.util.*;
|
||||||
|
import org.jdesktop.swingx.icon.ColumnControlIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model Independent enhanced JTable.
|
* Model Independent enhanced JTable.
|
||||||
|
@ -49,8 +52,22 @@ public class CTable extends JTable
|
||||||
setSurrendersFocusOnKeystroke(true);
|
setSurrendersFocusOnKeystroke(true);
|
||||||
//Default row height too narrow
|
//Default row height too narrow
|
||||||
setRowHeight(getFont().getSize() + 8);
|
setRowHeight(getFont().getSize() + 8);
|
||||||
|
|
||||||
|
setColumnControlVisible(true);
|
||||||
|
addHierarchyListener(createHierarchyListener());
|
||||||
} // CTable
|
} // CTable
|
||||||
|
|
||||||
|
private HierarchyListener createHierarchyListener() {
|
||||||
|
return new HierarchyListener() {
|
||||||
|
|
||||||
|
public void hierarchyChanged(HierarchyEvent e) {
|
||||||
|
if (e.getChangeFlags() == HierarchyEvent.PARENT_CHANGED)
|
||||||
|
configureColumnControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** Last model index sorted */
|
/** Last model index sorted */
|
||||||
protected int p_lastSortIndex = -1;
|
protected int p_lastSortIndex = -1;
|
||||||
/** Sort direction */
|
/** Sort direction */
|
||||||
|
@ -68,7 +85,28 @@ public class CTable extends JTable
|
||||||
/** Logger */
|
/** Logger */
|
||||||
private static Logger log = Logger.getLogger(CTable.class.getName());
|
private static Logger log = Logger.getLogger(CTable.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ScrollPane's original vertical scroll policy. If the column control is
|
||||||
|
* visible the policy is set to ALWAYS.
|
||||||
|
*/
|
||||||
|
private int verticalScrollPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to indicate if the column control is visible.
|
||||||
|
*/
|
||||||
|
private boolean columnControlVisible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component used a column control in the upper trailing corner of
|
||||||
|
* an enclosing <code>JScrollPane</code>.
|
||||||
|
*/
|
||||||
|
private JComponent columnControlButton;
|
||||||
|
|
||||||
|
private List<TableColumn> hiddenColumns = new ArrayList<TableColumn>();
|
||||||
|
|
||||||
|
private Map<TableColumn, ColumnAttributes> columnAttributesMap
|
||||||
|
= new HashMap<TableColumn, ColumnAttributes>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set Model index of Key Column.
|
* Set Model index of Key Column.
|
||||||
* Used for identifying previous selected row after fort complete to set as selected row.
|
* Used for identifying previous selected row after fort complete to set as selected row.
|
||||||
|
@ -153,7 +191,7 @@ public class CTable extends JTable
|
||||||
|| column.getMaxWidth() == 0
|
|| column.getMaxWidth() == 0
|
||||||
|| column.getIdentifier().toString().length() == 0))
|
|| column.getIdentifier().toString().length() == 0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
// Header
|
// Header
|
||||||
TableCellRenderer renderer = column.getHeaderRenderer();
|
TableCellRenderer renderer = column.getHeaderRenderer();
|
||||||
|
@ -179,8 +217,11 @@ public class CTable extends JTable
|
||||||
renderer = getCellRenderer(row, col);
|
renderer = getCellRenderer(row, col);
|
||||||
comp = renderer.getTableCellRendererComponent
|
comp = renderer.getTableCellRendererComponent
|
||||||
(this, getValueAt(row, col), false, false, row, col);
|
(this, getValueAt(row, col), false, false, row, col);
|
||||||
int rowWidth = comp.getPreferredSize().width;
|
if (comp != null)
|
||||||
width = Math.max(width, rowWidth);
|
{
|
||||||
|
int rowWidth = comp.getPreferredSize().width;
|
||||||
|
width = Math.max(width, rowWidth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -194,6 +235,51 @@ public class CTable extends JTable
|
||||||
column.setPreferredWidth(width);
|
column.setPreferredWidth(width);
|
||||||
} // for all columns
|
} // for all columns
|
||||||
} // autoSize
|
} // autoSize
|
||||||
|
|
||||||
|
public void packColumn(TableColumn column)
|
||||||
|
{
|
||||||
|
int width = 0;
|
||||||
|
// Header
|
||||||
|
TableCellRenderer renderer = column.getHeaderRenderer();
|
||||||
|
if (renderer == null)
|
||||||
|
renderer = new DefaultTableCellRenderer();
|
||||||
|
Component comp = null;
|
||||||
|
if (renderer != null)
|
||||||
|
comp = renderer.getTableCellRendererComponent
|
||||||
|
(this, column.getHeaderValue(), false, false, 0, 0);
|
||||||
|
//
|
||||||
|
if (comp != null)
|
||||||
|
{
|
||||||
|
width = comp.getPreferredSize().width;
|
||||||
|
width = Math.max(width, comp.getWidth());
|
||||||
|
|
||||||
|
// Cells
|
||||||
|
int col = column.getModelIndex();
|
||||||
|
int maxRow = Math.min(20, getRowCount());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int row = 0; row < maxRow; row++)
|
||||||
|
{
|
||||||
|
renderer = getCellRenderer(row, col);
|
||||||
|
comp = renderer.getTableCellRendererComponent
|
||||||
|
(this, getValueAt(row, col), false, false, row, col);
|
||||||
|
if (comp != null)
|
||||||
|
{
|
||||||
|
int rowWidth = comp.getPreferredSize().width;
|
||||||
|
width = Math.max(width, rowWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.log(Level.SEVERE, column.getIdentifier().toString(), e);
|
||||||
|
}
|
||||||
|
// Width not greater than 250
|
||||||
|
width = Math.min(MAXSIZE, width + SLACK);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
column.setPreferredWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +294,7 @@ public class CTable extends JTable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
sorting = true;
|
sorting = true;
|
||||||
|
|
||||||
// other column
|
// other column
|
||||||
if (modelColumnIndex != p_lastSortIndex)
|
if (modelColumnIndex != p_lastSortIndex)
|
||||||
p_asc = true;
|
p_asc = true;
|
||||||
|
@ -328,6 +414,8 @@ public class CTable extends JTable
|
||||||
*/
|
*/
|
||||||
class CTableMouseListener extends MouseAdapter
|
class CTableMouseListener extends MouseAdapter
|
||||||
{
|
{
|
||||||
|
private TableColumn cachedResizingColumn = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -342,14 +430,60 @@ public class CTable extends JTable
|
||||||
*/
|
*/
|
||||||
public void mouseClicked (MouseEvent e)
|
public void mouseClicked (MouseEvent e)
|
||||||
{
|
{
|
||||||
int vc = getColumnModel().getColumnIndexAtX(e.getX());
|
if (isInResizeRegion(e))
|
||||||
// log.info( "Sort " + vc + "=" + getColumnModel().getColumn(vc).getHeaderValue());
|
{
|
||||||
int mc = convertColumnIndexToModel(vc);
|
if (e.getClickCount() == 2)
|
||||||
sort(mc);
|
packColumn(cachedResizingColumn);
|
||||||
|
uncacheResizingColumn();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int vc = getColumnModel().getColumnIndexAtX(e.getX());
|
||||||
|
// log.info( "Sort " + vc + "=" + getColumnModel().getColumn(vc).getHeaderValue());
|
||||||
|
int mc = convertColumnIndexToModel(vc);
|
||||||
|
TableColumn column = getTableHeader().getResizingColumn();
|
||||||
|
if (column != null) return;
|
||||||
|
sort(mc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent e) {
|
||||||
|
cacheResizingColumn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
cacheResizingColumn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cacheResizingColumn(MouseEvent e) {
|
||||||
|
TableColumn column = getTableHeader().getResizingColumn();
|
||||||
|
if (column != null) {
|
||||||
|
cachedResizingColumn = column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uncacheResizingColumn() {
|
||||||
|
cachedResizingColumn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInResizeRegion(MouseEvent e) {
|
||||||
|
return cachedResizingColumn != null; // inResize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEntered(MouseEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseExited(MouseEvent e) {
|
||||||
|
uncacheResizingColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseDragged(MouseEvent e) {
|
||||||
|
uncacheResizingColumn();
|
||||||
|
}
|
||||||
|
|
||||||
} // CTableMouseListener
|
} // CTableMouseListener
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFont(Font font) {
|
public void setFont(Font font) {
|
||||||
super.setFont(font);
|
super.setFont(font);
|
||||||
|
@ -370,5 +504,193 @@ public class CTable extends JTable
|
||||||
public boolean isSortAscending() {
|
public boolean isSortAscending() {
|
||||||
return p_asc;
|
return p_asc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the column control visible property.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @return boolean to indicate whether the column control is visible.
|
||||||
|
* @see #setColumnControlVisible(boolean)
|
||||||
|
* @see #setColumnControl(JComponent)
|
||||||
|
*/
|
||||||
|
public boolean isColumnControlVisible() {
|
||||||
|
return columnControlVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the column control visible property. If true and
|
||||||
|
* <code>JXTable</code> is contained in a <code>JScrollPane</code>, the
|
||||||
|
* table adds the column control to the trailing corner of the scroll pane.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* Note: if the table is not inside a <code>JScrollPane</code> the column
|
||||||
|
* control is not shown even if this returns true. In this case it's the
|
||||||
|
* responsibility of the client code to actually show it.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* The default value is <code>false</code>.
|
||||||
|
*
|
||||||
|
* @param visible boolean to indicate if the column control should be shown
|
||||||
|
* @see #isColumnControlVisible()
|
||||||
|
* @see #setColumnControl(JComponent)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void setColumnControlVisible(boolean visible) {
|
||||||
|
boolean old = isColumnControlVisible();
|
||||||
|
this.columnControlVisible = visible;
|
||||||
|
if (old != isColumnControlVisible()) {
|
||||||
|
configureColumnControl();
|
||||||
|
firePropertyChange("columnControlVisible", old, !old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the component used as column control. Lazily creates the
|
||||||
|
* control to the default if it is <code>null</code>.
|
||||||
|
*
|
||||||
|
* @return component for column control, guaranteed to be != null.
|
||||||
|
* @see #setColumnControl(JComponent)
|
||||||
|
* @see #createDefaultColumnControl()
|
||||||
|
*/
|
||||||
|
public JComponent getColumnControl() {
|
||||||
|
if (columnControlButton == null) {
|
||||||
|
columnControlButton = createDefaultColumnControl();
|
||||||
|
}
|
||||||
|
return columnControlButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the default column control used by this table.
|
||||||
|
* This implementation returns a <code>ColumnControlButton</code> configured
|
||||||
|
* with default <code>ColumnControlIcon</code>.
|
||||||
|
*
|
||||||
|
* @return the default component used as column control.
|
||||||
|
* @see #setColumnControl(JComponent)
|
||||||
|
* @see org.jdesktop.swingx.table.ColumnControlButton
|
||||||
|
* @see org.jdesktop.swingx.icon.ColumnControlIcon
|
||||||
|
*/
|
||||||
|
protected JComponent createDefaultColumnControl() {
|
||||||
|
return new CColumnControlButton(this, new ColumnControlIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the upper trailing corner of an enclosing
|
||||||
|
* <code>JScrollPane</code>.
|
||||||
|
*
|
||||||
|
* Adds/removes the <code>ColumnControl</code> depending on the
|
||||||
|
* <code>columnControlVisible</code> property.<p>
|
||||||
|
*
|
||||||
|
* @see #setColumnControlVisible(boolean)
|
||||||
|
* @see #setColumnControl(JComponent)
|
||||||
|
*/
|
||||||
|
protected void configureColumnControl() {
|
||||||
|
Container p = getParent();
|
||||||
|
if (p instanceof JViewport) {
|
||||||
|
Container gp = p.getParent();
|
||||||
|
if (gp instanceof JScrollPane) {
|
||||||
|
JScrollPane scrollPane = (JScrollPane) gp;
|
||||||
|
// Make certain we are the viewPort's view and not, for
|
||||||
|
// example, the rowHeaderView of the scrollPane -
|
||||||
|
// an implementor of fixed columns might do this.
|
||||||
|
JViewport viewport = scrollPane.getViewport();
|
||||||
|
if (viewport == null || viewport.getView() != this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isColumnControlVisible()) {
|
||||||
|
verticalScrollPolicy = scrollPane
|
||||||
|
.getVerticalScrollBarPolicy();
|
||||||
|
scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
|
||||||
|
getColumnControl());
|
||||||
|
|
||||||
|
scrollPane
|
||||||
|
.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
|
} else {
|
||||||
|
if (verticalScrollPolicy != 0) {
|
||||||
|
// Fix #155-swingx: reset only if we had force always before
|
||||||
|
// PENDING: JW - doesn't cope with dynamically changing the policy
|
||||||
|
// shouldn't be much of a problem because doesn't happen too often??
|
||||||
|
scrollPane.setVerticalScrollBarPolicy(verticalScrollPolicy);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
|
||||||
|
null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Ignore spurious exception thrown by JScrollPane. This
|
||||||
|
// is a Swing bug!
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param column
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean isColumnVisible(TableColumn column)
|
||||||
|
{
|
||||||
|
return !hiddenColumns.contains(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide or show column
|
||||||
|
* @param column
|
||||||
|
* @param visible
|
||||||
|
*/
|
||||||
|
public void setColumnVisibility(TableColumn column, boolean visible)
|
||||||
|
{
|
||||||
|
if (visible)
|
||||||
|
{
|
||||||
|
if (isColumnVisible(column)) return;
|
||||||
|
ColumnAttributes attributes = columnAttributesMap.get(column);
|
||||||
|
if (attributes == null) return;
|
||||||
|
|
||||||
|
column.setCellEditor(attributes.cellEditor);
|
||||||
|
column.setCellRenderer(attributes.cellRenderer);
|
||||||
|
column.setMinWidth(attributes.minWidth);
|
||||||
|
column.setMaxWidth(attributes.maxWidth);
|
||||||
|
column.setPreferredWidth(attributes.preferredWidth);
|
||||||
|
columnAttributesMap.remove(column);
|
||||||
|
hiddenColumns.remove(column);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!isColumnVisible(column)) return;
|
||||||
|
|
||||||
|
ColumnAttributes attributes = new ColumnAttributes();
|
||||||
|
attributes.cellEditor = column.getCellEditor();
|
||||||
|
attributes.cellRenderer = column.getCellRenderer();
|
||||||
|
attributes.minWidth = column.getMinWidth();
|
||||||
|
attributes.maxWidth = column.getMaxWidth();
|
||||||
|
attributes.preferredWidth = column.getPreferredWidth();
|
||||||
|
columnAttributesMap.put(column, attributes);
|
||||||
|
|
||||||
|
TableCellNone h = new TableCellNone(column.getIdentifier() != null ?
|
||||||
|
column.getIdentifier().toString() : column.getHeaderValue().toString());
|
||||||
|
column.setCellEditor(h);
|
||||||
|
column.setCellRenderer(h);
|
||||||
|
column.setMinWidth(0);
|
||||||
|
column.setMaxWidth(0);
|
||||||
|
column.setPreferredWidth(0);
|
||||||
|
|
||||||
|
hiddenColumns.add(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ColumnAttributes {
|
||||||
|
protected TableCellEditor cellEditor;
|
||||||
|
|
||||||
|
protected TableCellRenderer cellRenderer;
|
||||||
|
|
||||||
|
protected Object headerValue;
|
||||||
|
|
||||||
|
protected int minWidth;
|
||||||
|
|
||||||
|
protected int maxWidth;
|
||||||
|
|
||||||
|
protected int preferredWidth;
|
||||||
|
}
|
||||||
|
|
||||||
} // CTable
|
} // CTable
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Product: Adempiere ERP & CRM Smart Business Solution *
|
||||||
|
* Copyright (C) 2007 Adempiere, Inc. All Rights Reserved. *
|
||||||
|
* This program is free software; you can redistribute it and/or modify it *
|
||||||
|
* under the terms version 2 of the GNU General Public License as published *
|
||||||
|
* by the Free Software Foundation. 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 General Public License for more details. *
|
||||||
|
* You should have received a copy of the GNU General Public License along *
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., *
|
||||||
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
|
||||||
|
*****************************************************************************/
|
||||||
|
package org.compiere.swing;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.event.CellEditorListener;
|
||||||
|
import javax.swing.table.TableCellEditor;
|
||||||
|
import javax.swing.table.TableCellRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy editor and renderer use for invisible column
|
||||||
|
* @author Low Heng Sin
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TableCellNone implements TableCellEditor, TableCellRenderer {
|
||||||
|
|
||||||
|
private Object m_value;
|
||||||
|
private String m_columnName;
|
||||||
|
|
||||||
|
public TableCellNone(String columnName) {
|
||||||
|
m_columnName = columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getTableCellEditorComponent(JTable table, Object value,
|
||||||
|
boolean isSelected, int row, int column) {
|
||||||
|
m_value = value;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCellEditorListener(CellEditorListener l) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelCellEditing() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getCellEditorValue() {
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCellEditable(EventObject anEvent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCellEditorListener(CellEditorListener l) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldSelectCell(EventObject anEvent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean stopCellEditing() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||||
|
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
m_value = value;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue