diff --git a/looks/.classpath b/looks/.classpath index 4b19d49ccc..069fcb28f2 100644 --- a/looks/.classpath +++ b/looks/.classpath @@ -3,5 +3,6 @@ + diff --git a/looks/src/org/compiere/swing/CColumnControlButton.java b/looks/src/org/compiere/swing/CColumnControlButton.java new file mode 100644 index 0000000000..d02949bfcf --- /dev/null +++ b/looks/src/org/compiere/swing/CColumnControlButton.java @@ -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 CTable's + * columns. + * It's main purpose is to allow toggling of table columns' visibility. + * Additionally, arbitrary configuration actions can be exposed. + *

+ * + * This component is installed in the CTable's + * trailing corner, if enabled: + * + *


+ * table.setColumnControlVisible(true);
+ * 
+ * + * From the perspective of a CTable, the component's behaviour is + * opaque. Typically, the button's action is to popup a component for user + * interaction.

+ * + * 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 + * ColumnControlPopup.

+ * + * @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 columnVisibilityActions; + + /** + * Creates a column control button for the table. The button + * uses the given icon and has no text. + * @param table the JTable controlled by this component + * @param icon the Icon 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.

+ * + * 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 Action 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 TableColumn to keep synched to. + */ + public ColumnVisibilityAction(TableColumn column) { + super((String) null); + setStateAction(); + installColumn(column); + } + + /** + * Releases all references to the synched TableColumn. + * 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 visible 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 headerValue 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 true. 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 PropertyChangeListener listening to + * TableColumn's property changes, guaranteed to be + * not null. + */ + 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. + *

+ * Implementation note: this listener reacts to column's + * visible and headerValue properties and + * calls the respective updateFromXX methodes. + * + * @return the PropertyChangeListener 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 actions) { + addItems(new ArrayList(actions)); + + } + + + /** + * @inheritDoc + * + */ + public void addAdditionalActionItems(List 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 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 null. + * @see #createColumnControlPopup() + */ + protected ColumnControlPopup getColumnControlPopup() { + if (popup == null) { + popup = createColumnControlPopup(); + } + return popup; + } + + /** + * Factory method to return a ColumnControlPopup. + * Subclasses can override to hook custom implementations. + * + * @return the ColumnControlPopup 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 TableColumnModel 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. + *

+ * 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.

+ * + * Here: all table columns contained in the TableColumnModel - + * visible and invisible columns - to createColumnVisibilityAction and + * adds all not null return values. + * + *

+ * PRE: canControl() + * + * @see #createColumnVisibilityAction + */ + protected void createVisibilityActions() { + Enumeration 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 ColumnVisibilityAction for the given + * TableColumn. The return value might be null, f.i. if the + * column should not be allowed to be toggled. + * + * @param column the TableColumn to use for the action + * @return a ColumnVisibilityAction to use for the given column, + * may be null. + */ + 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 getColumnVisibilityActions() { + if (columnVisibilityActions == null) { + columnVisibilityActions = new ArrayList(); + } + 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 getAdditionalActions() { + List actionKeys = getColumnControlActionKeys(); + List actions = new ArrayList(); + 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 PropertyChangeListener for use with the + * table, guaranteed to be not null. + */ + 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.

+ * Implementation note: this listener reacts to table's enabled and + * columnModel properties and calls the respective + * updateFromXX methodes. + * + * @return the PropertyChangeListener 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 TableColumnModelListener for use with the + * table's column model, guaranteed to be not null. + */ + protected TableColumnModelListener getColumnModelListener() { + if (columnModelListener == null) { + columnModelListener = createColumnModelListener(); + } + return columnModelListener; + } + + /** + * Creates the listener to columnModel. Subclasses are free to roll their + * own. + *

+ * Implementation note: this listener reacts to "real" columnRemoved/-Added by + * populating the popups content from scratch. + * + * @return the TableColumnModelListener 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 diff --git a/looks/src/org/compiere/swing/CTable.java b/looks/src/org/compiere/swing/CTable.java index a95cd98e79..a2d2f94b35 100644 --- a/looks/src/org/compiere/swing/CTable.java +++ b/looks/src/org/compiere/swing/CTable.java @@ -19,11 +19,14 @@ package org.compiere.swing; import java.awt.*; import java.awt.event.*; import java.util.*; +import java.util.List; import java.util.logging.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; + import org.compiere.util.*; +import org.jdesktop.swingx.icon.ColumnControlIcon; /** * Model Independent enhanced JTable. @@ -49,8 +52,22 @@ public class CTable extends JTable setSurrendersFocusOnKeystroke(true); //Default row height too narrow setRowHeight(getFont().getSize() + 8); + + setColumnControlVisible(true); + addHierarchyListener(createHierarchyListener()); } // CTable + private HierarchyListener createHierarchyListener() { + return new HierarchyListener() { + + public void hierarchyChanged(HierarchyEvent e) { + if (e.getChangeFlags() == HierarchyEvent.PARENT_CHANGED) + configureColumnControl(); + } + + }; + } + /** Last model index sorted */ protected int p_lastSortIndex = -1; /** Sort direction */ @@ -68,7 +85,28 @@ public class CTable extends JTable /** Logger */ 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 JScrollPane. + */ + private JComponent columnControlButton; + + private List hiddenColumns = new ArrayList(); + + private Map columnAttributesMap + = new HashMap(); + /** * Set Model index of Key Column. * 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.getIdentifier().toString().length() == 0)) continue; - + int width = 0; // Header TableCellRenderer renderer = column.getHeaderRenderer(); @@ -179,8 +217,11 @@ public class CTable extends JTable renderer = getCellRenderer(row, col); comp = renderer.getTableCellRendererComponent (this, getValueAt(row, col), false, false, row, col); - int rowWidth = comp.getPreferredSize().width; - width = Math.max(width, rowWidth); + if (comp != null) + { + int rowWidth = comp.getPreferredSize().width; + width = Math.max(width, rowWidth); + } } } catch (Exception e) @@ -194,6 +235,51 @@ public class CTable extends JTable column.setPreferredWidth(width); } // for all columns } // 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; sorting = true; - + // other column if (modelColumnIndex != p_lastSortIndex) p_asc = true; @@ -328,6 +414,8 @@ public class CTable extends JTable */ class CTableMouseListener extends MouseAdapter { + private TableColumn cachedResizingColumn = null; + /** * Constructor */ @@ -342,14 +430,60 @@ public class CTable extends JTable */ public void mouseClicked (MouseEvent e) { - int vc = getColumnModel().getColumnIndexAtX(e.getX()); - // log.info( "Sort " + vc + "=" + getColumnModel().getColumn(vc).getHeaderValue()); - int mc = convertColumnIndexToModel(vc); - sort(mc); + if (isInResizeRegion(e)) + { + if (e.getClickCount() == 2) + 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 - + @Override public void setFont(Font font) { super.setFont(font); @@ -370,5 +504,193 @@ public class CTable extends JTable public boolean isSortAscending() { return p_asc; } + + /** + * Returns the column control visible property. + *

+ * + * @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 + * JXTable is contained in a JScrollPane, the + * table adds the column control to the trailing corner of the scroll pane. + *

+ * + * Note: if the table is not inside a JScrollPane 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. + *

+ * + * The default value is false. + * + * @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 null. + * + * @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 ColumnControlButton configured + * with default ColumnControlIcon. + * + * @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 + * JScrollPane. + * + * Adds/removes the ColumnControl depending on the + * columnControlVisible property.

+ * + * @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 diff --git a/looks/src/org/compiere/swing/TableCellNone.java b/looks/src/org/compiere/swing/TableCellNone.java new file mode 100644 index 0000000000..feb723ddaa --- /dev/null +++ b/looks/src/org/compiere/swing/TableCellNone.java @@ -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; + } + +}