001/*
002 * $Id$
003 *
004 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 *
021 */
022package org.jdesktop.swingx.table;
023
024import static org.jdesktop.swingx.table.TableUtilities.isDataChanged;
025import static org.jdesktop.swingx.table.TableUtilities.isInsert;
026import static org.jdesktop.swingx.table.TableUtilities.isStructureChanged;
027import static org.jdesktop.swingx.table.TableUtilities.isUpdate;
028import static org.jdesktop.swingx.table.TableUtilities.setPreferredRowHeight;
029
030import java.beans.PropertyChangeEvent;
031import java.beans.PropertyChangeListener;
032import java.util.logging.Logger;
033
034import javax.swing.JTable;
035import javax.swing.SwingUtilities;
036import javax.swing.event.TableModelEvent;
037import javax.swing.event.TableModelListener;
038import javax.swing.table.TableModel;
039
040/**
041 * A controller to adjust JTable rowHeight based on sizing requirements of its renderers.
042 * 
043 * @author Jeanette Winzenburg, Berlin
044 */
045public class TableRowHeightController {
046
047    private JTable table;
048    private TableModelListener tableModelListener;
049    private PropertyChangeListener tablePropertyListener;
050
051    /**
052     * Instantiates an unbound TableRowHeightController.
053     */
054    public TableRowHeightController() {
055        this(null);
056    }
057
058    /**
059     * Instantiates a TableRowHeightController and installs itself to the given table.
060     * The row heights of all visible rows are automatically adjusted on model changes.
061     * 
062     * @param table the table to control.
063     */
064    public TableRowHeightController(JTable table) {
065        install(table);
066    }
067
068    /**
069     * Installs this controller on the given table. Releases control from previously
070     * installed table, if any. 
071     * @param table the table to install upon.
072     */
073    public void install(JTable table) {
074        release();
075        if (table != null) {
076            this.table = table;
077            installListeners();
078            updatePreferredRowHeights();
079        }
080    }
081
082    /**
083     * Release this controller from its table. Does nothing if no table installed.
084     * 
085     */
086    public void release() {
087        if (table == null)
088            return;
089        uninstallListeners();
090        table = null;
091    }
092
093    /**
094     * Sets the row heights of the rows in the range of first- to lastRow, inclusive.
095     * The coordinates are model indices.
096     * 
097     * @param firstRow the first row in model coordinates
098     * @param lastRow the last row in model coordinates
099     */
100    protected void updatePreferredRowHeights(int firstRow, int lastRow) {
101        for (int row = firstRow; row <= lastRow; row++) {
102            int viewRow = table.convertRowIndexToView(row);
103            if (viewRow >= 0) {
104//                int oldHeight = table.getRowHeight(viewRow);
105//                LOG.info("in viewRow/old/new: " + viewRow + " / " + oldHeight + " / " + table.getRowHeight(viewRow));
106                setPreferredRowHeight(table, viewRow);
107            }
108        }
109    }
110
111    /**
112     * Sets the row heights of all rows.
113     */
114    protected void updatePreferredRowHeights() {
115        if (table.getRowCount() == 0) return;
116        updatePreferredRowHeights(0, table.getModel().getRowCount() - 1);
117    }
118    
119    /**
120     * @param oldValue
121     */
122    protected void updateModel(TableModel oldValue) {
123        if (oldValue != null) {
124            oldValue.removeTableModelListener(getTableModelListener());
125        }
126        table.getModel().addTableModelListener(getTableModelListener());
127        updatePreferredRowHeights();
128    }
129
130
131    /**
132     * @return
133     */
134    protected PropertyChangeListener createTablePropertyListener() {
135        PropertyChangeListener l = new PropertyChangeListener() {
136            
137            @Override
138            public void propertyChange(PropertyChangeEvent evt) {
139                invokedPropertyChanged(evt);
140            }
141
142            /**
143             * @param evt
144             */
145            private void invokedPropertyChanged(final PropertyChangeEvent evt) {
146                SwingUtilities.invokeLater(new Runnable() {
147                    @Override
148                    public void run() {
149                        if ("model".equals(evt.getPropertyName())) {
150                            updateModel((TableModel) evt.getOldValue());
151                        }
152                        
153                    }
154                });
155            }
156        };
157        return l;
158    }
159
160    protected TableModelListener createTableModelListener() {
161        TableModelListener l = new TableModelListener() {
162            @Override
163            public void tableChanged(final TableModelEvent e) {
164                SwingUtilities.invokeLater(new Runnable() {
165                    @Override
166                    public void run() {
167                        invokedTableChanged(e);
168                    }
169                });
170            }
171
172            private void invokedTableChanged(TableModelEvent e) {
173                if (isStructureChanged(e) || isDataChanged(e)) {
174                    updatePreferredRowHeights();
175                } else  if (isUpdate(e) || isInsert(e)) {
176                    updatePreferredRowHeights(e.getFirstRow(), e.getLastRow());
177                } 
178                // do nothing on delete
179            }
180        };
181        return l;
182    }
183    /**
184     * 
185     */
186    private void uninstallListeners() {
187        table.removePropertyChangeListener(getPropertyChangeListener());
188        table.getModel().removeTableModelListener(getTableModelListener());
189        // whatever else turns out to be needed
190    }
191
192    private void installListeners() {
193        table.addPropertyChangeListener(getPropertyChangeListener());
194        table.getModel().addTableModelListener(getTableModelListener());
195        // whatever else turns out to be needed
196    }
197
198    /**
199     * @return
200     */
201    protected TableModelListener getTableModelListener() {
202        if (tableModelListener == null) {
203            tableModelListener = createTableModelListener();
204        }
205        return tableModelListener;
206    }
207
208    /**
209     * @return
210     */
211    protected PropertyChangeListener getPropertyChangeListener() {
212        if (tablePropertyListener == null) {
213            tablePropertyListener = createTablePropertyListener();
214        }
215        return tablePropertyListener;
216    }
217
218    @SuppressWarnings("unused")
219    private static final Logger LOG = Logger
220        .getLogger(TableRowHeightController.class.getName());
221}