/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.components.tabularbrowser;
/*
* %W% %E%
*
* Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
* DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
* RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
* ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
* FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
* SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
* THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or
* intended for use in the design, construction, operation or
* maintenance of any nuclear facility.
*/
import java.awt.Component;
import java.awt.Dimension;
import java.util.Enumeration;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreePath;
import org.openflexo.components.browser.BrowserElement;
import org.openflexo.components.browser.ExpansionSynchronizedElement;
import org.openflexo.components.browser.ProjectBrowser;
import org.openflexo.components.browser.ProjectBrowser.DisableExpandingSynchronizationEvent;
import org.openflexo.components.browser.ProjectBrowser.EnableExpandingSynchronizationEvent;
import org.openflexo.components.browser.ProjectBrowser.ExpansionNotificationEvent;
import org.openflexo.components.browser.ProjectBrowser.ObjectAddedToSelectionEvent;
import org.openflexo.components.browser.ProjectBrowser.ObjectRemovedFromSelectionEvent;
import org.openflexo.components.browser.ProjectBrowser.SelectionClearedEvent;
import org.openflexo.components.browser.ProjectBrowserListener;
import org.openflexo.components.tabular.model.AbstractColumn;
import org.openflexo.components.tabular.model.HeightAdjustableColumn;
import org.openflexo.components.tabular.model.RowHeightListener;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.selection.SelectionListener;
/**
* Basic representation of a JTreeTable given a TabularBrowserModel
*
* @see http://java.sun.com/products/jfc/tsc/articles/treetable1/ Updated to fit Flexo architecture
*
* @author Philip Milne
* @author Scott Violet
* @author Sylvain Guerin
*/
public class JTreeTable extends JTable implements SelectionListener, RowHeightListener, ProjectBrowserListener, TreeSelectionListener,
TreeExpansionListener {
private static final Logger logger = Logger.getLogger(JTreeTable.class.getPackage().getName());
protected TreeTableCellRenderer tree;
private TabularBrowserModel _treeTableModel;
/** This adaptor makes the link between the treemodel (ProjectBrowser) and the tablemodel */
private TreeTableModelAdapter _modelAdapter;
private JTreeTableMouseAdapter _mouseAdapter;
protected ListSelectionModel _listSelectionModel;
protected Vector<FlexoModelObject> _selectedObjects;
protected boolean _selectedObjectsNeedsRecomputing = true;
public JTreeTable(TabularBrowserModel treeTableModel) {
super();
_treeTableModel = treeTableModel;
_selectedObjects = new Vector<FlexoModelObject>();
// Create the tree. It will be used as a renderer and editor.
tree = new TreeTableCellRenderer(this, treeTableModel);
// Install a tableModel representing the visible rows in the tree.
super.setModel(_modelAdapter = new TreeTableModelAdapter(treeTableModel.getDefaultRootObject(), treeTableModel.getProject(),
treeTableModel, tree));
// Force the JTable and JTree to share their row selection models.
tree.setSelectionModel(new DefaultTreeSelectionModel() {
// Extend the implementation of the constructor, as if:
/* public this() */{
setSelectionModel(listSelectionModel);
_listSelectionModel = listSelectionModel;
}
});
// Make the tree and table row heights the same.
tree.setRowHeight(getRowHeight());
for (int i = 0; i < treeTableModel.getColumnCount(); i++) {
TableColumn col = getColumnModel().getColumn(i);
col.setPreferredWidth(treeTableModel.getDefaultColumnSize(i));
if (treeTableModel.getColumnResizable(i)) {
col.setResizable(true);
} else {
col.setWidth(treeTableModel.getDefaultColumnSize(i));
col.setMinWidth(treeTableModel.getDefaultColumnSize(i));
col.setMaxWidth(treeTableModel.getDefaultColumnSize(i));
col.setResizable(false);
}
((AbstractColumn<FlexoModelObject, FlexoModelObject>) treeTableModel.columnAt(i)).setModel(_modelAdapter);
if (treeTableModel.columnAt(i) instanceof TabularBrowserModel.TreeColumn) {
col.setCellRenderer(tree);
col.setCellEditor(new TreeTableCellEditor());
} else {
if (treeTableModel.columnAt(i).requireCellRenderer()) {
col.setCellRenderer(treeTableModel.columnAt(i).getCellRenderer());
}
if (treeTableModel.columnAt(i).requireCellEditor()) {
col.setCellEditor(treeTableModel.columnAt(i).getCellEditor());
}
}
if (treeTableModel.columnAt(i) instanceof HeightAdjustableColumn) {
((HeightAdjustableColumn) treeTableModel.columnAt(i)).addRowHeightListener(this);
}
}
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));
_mouseAdapter = new JTreeTableMouseAdapter(this);
addMouseListener(_mouseAdapter);
addMouseMotionListener(_mouseAdapter);
treeTableModel.addBrowserListener(this);
}
protected void treeStructureChanged() {
((TreeTableModelAdapter) getModel()).fireTableDataChanged();
for (int i = 0; i < _treeTableModel.getColumnCount(); i++) {
TableColumn col = getColumnModel().getColumn(i);
if (_treeTableModel.columnAt(i) instanceof HeightAdjustableColumn) {
HeightAdjustableColumn column = (HeightAdjustableColumn) _treeTableModel.columnAt(i);
for (int row = 0; row < getRowCount(); row++) {
notifyRowHeightChanged(row, column.getRowHeight(row));
}
}
}
}
@Override
public void setRowHeight(int rowHeight) {
super.setRowHeight(rowHeight);
if (tree != null) {
tree.setRowHeight(rowHeight);
}
}
/* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
* paint the editor. The UI currently uses different techniques to
* paint the renderers and editors and overriding setBounds() below
* is not the right thing to do for an editor. Returning -1 for the
* editing row in this case, ensures the editor is never painted.
*/
@Override
public int getEditingRow() {
return getColumnClass(editingColumn) == TreeTableModel.class ? -1 : editingRow;
}
//
// The renderer used to display the tree nodes, a JTree.
//
public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int r, int c) {
return tree;
}
}
public TabularBrowserModel getTreeTableModel() {
return _treeTableModel;
}
public BrowserElement getElementAt(int row) {
return tree.getElementAt(row);
}
public FlexoModelObject getObjectAt(int row) {
BrowserElement element = getElementAt(row);
if (element != null) {
return element.getObject();
}
return null;
}
public Vector<FlexoModelObject> getSelectedObjects() {
if (_selectedObjectsNeedsRecomputing) {
_selectedObjects.clear();
for (int i = 0; i < getRowCount(); i++) {
if (_listSelectionModel.isSelectedIndex(i)) {
_selectedObjects.add(getObjectAt(i));
}
}
_selectedObjectsNeedsRecomputing = false;
}
return _selectedObjects;
}
public boolean isSelected(FlexoModelObject object) {
return getSelectedObjects().contains(object);
}
public ListSelectionModel getTreeTableSelectionModel() {
return _listSelectionModel;
}
public ProjectBrowser getProjectBrowser() {
return _treeTableModel;
}
@Override
public void fireObjectSelected(FlexoModelObject object) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("JTreeTable fireObjectSelected() with " + object);
}
TreePath[] paths = getProjectBrowser().treePathForObject(object);
if (paths == null) {
return;
}
for (int i = 0; i < paths.length; i++) {
TreePath path = paths[i];
tree.makeVisible(path);
int row = tree.getRowForPath(path);
_listSelectionModel.addSelectionInterval(row, row);
}
_selectedObjectsNeedsRecomputing = true;
}
@Override
public void fireObjectDeselected(FlexoModelObject object) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("JTreeTable fireObjectDeselected() with " + object);
}
TreePath[] paths = getProjectBrowser().treePathForObject(object);
if (paths == null) {
return;
}
for (int i = 0; i < paths.length; i++) {
TreePath path = paths[i];
if (tree.isVisible(path)) {
int row = tree.getRowForPath(path);
_listSelectionModel.removeSelectionInterval(row, row);
}
}
tree.removeSelectionPaths(paths);
_selectedObjectsNeedsRecomputing = true;
}
@Override
public void fireResetSelection() {
_selectedObjectsNeedsRecomputing = true;
_listSelectionModel.clearSelection();
}
public boolean mayRepresents(FlexoModelObject anObject) {
// logger.warning("Implements me later !");
return true;
}
@Override
public void fireBeginMultipleSelection() {
}
@Override
public void fireEndMultipleSelection() {
}
@Override
public void valueChanged(ListSelectionEvent e) {
super.valueChanged(e);
// Ignore extra messages.
if (e.getValueIsAdjusting()) {
return;
}
_selectedObjectsNeedsRecomputing = true;
}
@Override
public void notifyRowHeightChanged(int row, int newRowHeight) {
if (newRowHeight > 0) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("notifyRowHeightChanged row=" + row + " new height=" + newRowHeight);
}
setRowHeight(row, newRowHeight);
tree.setRowHeight(row, newRowHeight);
}
}
public TreeTableCellRenderer getTree() {
return tree;
}
private boolean superviseExpansion = false;
private Vector<BrowserElement> expansionSupervisedElements;
@Override
public void objectAddedToSelection(ObjectAddedToSelectionEvent event) {
// logger.info("**** objectAddedToSelection() with "+event);
TreePath[] paths = _treeTableModel.treePathForObject(event.getAddedObject());
if (paths == null) {
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("BrowserView.objectAddedToSelection() " + event.getAddedObject());
}
superviseExpansion = true;
expansionSupervisedElements = new Vector();
for (int i = 0; i < paths.length; i++) {
BrowserElement element = (BrowserElement) paths[i].getLastPathComponent();
expansionSupervisedElements.add(element);
}
tree.removeTreeSelectionListener(this);
tree.addSelectionPaths(paths);
tree.addTreeSelectionListener(this);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Added " + event.getAddedObject());
}
superviseExpansion = false;
// selectedElementsNeedRecomputing = true;
/*if (_treeTableModel.handlesControlPanel()) {
controlPanel.handleSelectionChanged();
}*/
}
@Override
public void objectRemovedFromSelection(ObjectRemovedFromSelectionEvent event) {
// logger.info("**** objectRemovedFromSelection() with "+event);
if (event.getRemovedObject() != null) {
TreePath[] paths = _treeTableModel.treePathForObject(event.getRemovedObject());
if (paths == null) {
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("BrowserView.objectRemovedFromSelection() " + event.getRemovedObject());
}
tree.removeTreeSelectionListener(this);
tree.removeSelectionPaths(paths);
tree.addTreeSelectionListener(this);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Removed " + event.getRemovedObject());
}
}
}
@Override
public void selectionCleared(SelectionClearedEvent event) {
// logger.info("**** selectionCleared() with "+event);
/* if (_treeTableModel.handlesControlPanel()) {
controlPanel.handleSelectionCleared();
}*/
tree.removeTreeSelectionListener(this);
tree.clearSelection();
tree.addTreeSelectionListener(this);
}
@Override
public void optionalFilterAdded(ProjectBrowser.OptionalFilterAddedEvent event) {
/* if (_treeTableModel.handlesControlPanel()) {
controlPanel.handleOptionalFilterAdded();
} */
}
@Override
public void notifyExpansions(ExpansionNotificationEvent event) {
// logger.info("**** notifyExpansions() with "+event);
_treeTableModel.deleteBrowserListener(this);
tree.removeTreeExpansionListener(this);
for (TreePath path : event.pathsToExpand()) {
if (tree.isCollapsed(path)) {
tree.expandPath(path);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Expand " + path);
}
}
}
for (TreePath path : event.pathsToCollapse()) {
if (tree.isExpanded(path)) {
tree.collapsePath(path);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Collabse " + path);
}
}
}
tree.addTreeExpansionListener(this);
_treeTableModel.addBrowserListener(this);
}
@Override
public void enableExpandingSynchronization(EnableExpandingSynchronizationEvent event) {
// logger.info("**** enableExpandingSynchronization() with "+event);
if (logger.isLoggable(Level.FINE)) {
logger.fine("enableExpandingSynchronization()");
}
tree.addTreeExpansionListener(this);
}
@Override
public void disableExpandingSynchronization(DisableExpandingSynchronizationEvent event) {
// logger.info("**** disableExpandingSynchronization() with "+event);
if (logger.isLoggable(Level.FINE)) {
logger.fine("disableExpandingSynchronization()");
}
tree.removeTreeExpansionListener(this);
}
/**
* Implements
*
* @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent)
* @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent)
*/
@Override
public void valueChanged(TreeSelectionEvent e) {
// logger.info("**** valueChanged() with "+e);
if (logger.isLoggable(Level.FINE)) {
logger.fine("valueChanged() " + e);
}
// selectedElementsNeedRecomputing = true;
if (_treeTableModel.getSelectionManager() != null) {
_treeTableModel.deleteBrowserListener(this);
TreePath[] selectionChanges = e.getPaths();
for (int i = 0; i < selectionChanges.length; i++) {
BrowserElement element = (BrowserElement) selectionChanges[i].getLastPathComponent();
if (e.isAddedPath(selectionChanges[i])) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("valueChanged() for ADDITION " + element.getSelectableObject());
}
_treeTableModel.getSelectionManager().addToSelected(element.getSelectableObject());
// _browser.addToSelected(element.getSelectableObject());
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("valueChanged() for REMOVING " + element.getSelectableObject());
}
_treeTableModel.getSelectionManager().removeFromSelected(element.getSelectableObject());
// _browser.removeFromSelected(element.getSelectableObject());
}
}
_treeTableModel.addBrowserListener(this);
_treeTableModel.getSelectionManager().updateSelectionForMaster(_treeTableModel);
if (logger.isLoggable(Level.FINE)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(_treeTableModel.getSelectionManager().toString());
}
}
if (_treeTableModel.getSelectionManager().getContextualMenuManager() != null) {
if (_treeTableModel.getSelectionManager().getContextualMenuManager().isPopupMenuDisplayed()) {
_treeTableModel.getSelectionManager().getContextualMenuManager().hidePopupMenu();
}
}
}
/*if (_treeTableModel.handlesControlPanel()) {
controlPanel.handleSelectionChanged();
}*/
}
/**
* Implements
*
* @see javax.swing.event.TreeExpansionListener#treeExpanded(javax.swing.event.TreeExpansionEvent)
* @see javax.swing.event.TreeExpansionListener#treeExpanded(javax.swing.event.TreeExpansionEvent)
*/
@Override
public void treeExpanded(TreeExpansionEvent event) {
// logger.info("**** treeExpanded() with "+event);
BrowserElement element = (BrowserElement) event.getPath().getLastPathComponent();
if (logger.isLoggable(Level.FINE)) {
logger.fine("Tree may expand for " + element);
}
// Lets look at the expansion supervising
if (_treeTableModel.isExpansionSynchronizedElement(element) && element.isSynchronizeExpansionEnabled()) {
// At this level, element is considered as expansion supervised
boolean doExpand = true;
ExpansionSynchronizedElement elementToExpand = (ExpansionSynchronizedElement) element;
if (superviseExpansion) {
// BUT.... here, the flag superviseExpansion indicates that this tree expansion
// has its origin in the fact that one or more objects were selected.
// In this case, this is not sure that the expansion whould be synchronized
// We have to look that at least one of the selected element requires expansion
doExpand = false;
for (Enumeration e = expansionSupervisedElements.elements(); e.hasMoreElements();) {
BrowserElement next = (BrowserElement) e.nextElement();
if (elementToExpand.requiresExpansionFor(next)) {
// Selecting next requires expansion for elementToExpand
doExpand = true;
}
}
}
if (doExpand) {
// Finally i decide to expand
elementToExpand.expand();
}
}
}
/**
* Implements
*
* @see javax.swing.event.TreeExpansionListener#treeCollapsed(javax.swing.event.TreeExpansionEvent)
* @see javax.swing.event.TreeExpansionListener#treeCollapsed(javax.swing.event.TreeExpansionEvent)
*/
@Override
public void treeCollapsed(TreeExpansionEvent event) {
// logger.info("**** treeCollapsed() with "+event);
BrowserElement element = (BrowserElement) event.getPath().getLastPathComponent();
if (logger.isLoggable(Level.FINE)) {
logger.fine("Tree collabsed for " + element);
}
if (_treeTableModel.isExpansionSynchronizedElement(element)) {
((ExpansionSynchronizedElement) element).collapse();
}
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
TableCellRenderer returned = super.getCellRenderer(row, column);
if (returned == null) {
logger.warning("null TableCellRenderer for row=" + row + " column=" + column);
// SGU: I don't understand this bug: big HACK to avoid problem !!!
return new DefaultTableCellRenderer();
}
return returned;
}
}