/* * (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; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.util.Hashtable; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JTable; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionListener; import javax.swing.table.TableCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import org.openflexo.ColorCst; import org.openflexo.application.FlexoApplication; import org.openflexo.components.browser.BrowserElement; import org.openflexo.components.browser.view.BrowserViewCellEditor; import org.openflexo.components.browser.view.BrowserViewCellRenderer; import org.openflexo.foundation.FlexoModelObject; /** * Cell renderer for a JTreeTable * * @author sguerin */ public class TreeTableCellRenderer extends JTree implements TableCellRenderer { private static final Logger logger = Logger.getLogger(TreeTableCellRenderer.class.getPackage().getName()); private final JTreeTable _table; protected int visibleRow; protected Hashtable<Integer, BrowserElement> _elementsForRow; protected Hashtable<FlexoModelObject, Integer> _rowForObjects; protected Hashtable<FlexoModelObject, Integer> _heightForObjects; private JTreeMouseEventPreprocessor _mouseEventPreprocessor; protected boolean pointerOnTree = false; public TreeTableCellRenderer(JTreeTable table, TreeModel model) { super(model); _elementsForRow = new Hashtable<Integer, BrowserElement>(); _rowForObjects = new Hashtable<FlexoModelObject, Integer>(); _heightForObjects = new Hashtable<FlexoModelObject, Integer>(); _table = table; JTreeCellRenderer renderer = new JTreeCellRenderer(); setCellRenderer(renderer); setCellEditor(new BrowserViewCellEditor(this, renderer)); addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { treeStructureChanged(); } }); } @Override public void treeCollapsed(TreeExpansionEvent event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { treeStructureChanged(); } }); } }); JTreeMouseAdapter _mouseAdapter = new JTreeMouseAdapter(); _table.addMouseListener(_mouseAdapter); _table.addMouseMotionListener(_mouseAdapter); _mouseEventPreprocessor = new JTreeMouseEventPreprocessor(); } private class JTreeMouseAdapter extends MouseAdapter implements MouseMotionListener { JTreeMouseAdapter() { super(); } @Override public void mouseEntered(MouseEvent e) { notifyPointerOn(e.getPoint()); } @Override public void mouseExited(MouseEvent e) { notifyPointerOn(e.getPoint()); if (pointerOnTree) { pointerExitedTree(); } } @Override public void mouseMoved(MouseEvent e) { notifyPointerOn(e.getPoint()); } @Override public void mouseDragged(MouseEvent e) { notifyPointerOn(e.getPoint()); } } @Override public void addTreeSelectionListener(TreeSelectionListener tsl) { /** * GPO: The code hereunder is here to prevent a same tree selection listener from being added twice to the same view making all * further add/remove (treeSelectionListener()) call completely useless. This was called "Bug critique difficilement reproductible" * reported by FVA. */ TreeSelectionListener[] t = listenerList.getListeners(TreeSelectionListener.class); for (int i = 0; i < t.length; i++) { TreeSelectionListener treeSelectionListener = t[i]; if (treeSelectionListener == tsl) { if (logger.isLoggable(Level.SEVERE)) { logger.severe("Adding twice the same tsl: " + tsl + ". Preventing this by returning"); } return; } } super.addTreeSelectionListener(tsl); } @Override public void removeTreeSelectionListener(TreeSelectionListener tsl) { /** * GPO: The code hereunder is here to track when we are not really removing <code>BrowserView.this</code> from the tree selection * listeners because another action has already performed this. This trace is very interesting to find which event is starting the * other one. You should see next a log "Adding twice the same tsl: " following this log. This was called * "Bug critique difficilement reproductible" reported by FVA. */ boolean isInList = false; TreeSelectionListener[] t = listenerList.getListeners(TreeSelectionListener.class); for (int i = 0; i < t.length; i++) { TreeSelectionListener treeSelectionListener = t[i]; if (treeSelectionListener == tsl) { isInList = true; } } if (!isInList) { if (logger.isLoggable(Level.SEVERE)) { logger.severe("Nothing to remove!"); } } super.removeTreeSelectionListener(tsl); } protected void notifyPointerOn(Point p) { // logger.info("notifyPointerOn "+p); if (_table.columnAtPoint(p) == 0 && !pointerOnTree) { pointerEnteredTree(); } else if (_table.columnAtPoint(p) != 0 && pointerOnTree) { pointerExitedTree(); } } protected void pointerEnteredTree() { // logger.info("Mouse entering, applying event translator "); pointerOnTree = true; FlexoApplication.eventProcessor.setPreprocessor(_mouseEventPreprocessor); } protected void pointerExitedTree() { // logger.info("Mouse exiting, removing event translator "); pointerOnTree = false; FlexoApplication.eventProcessor.setPreprocessor(null); } protected class JTreeMouseEventPreprocessor implements FlexoApplication.EventPreprocessor { @Override public void preprocessEvent(AWTEvent e) { if (e instanceof MouseEvent) { MouseEvent event = (MouseEvent) e; // logger.info("called preprocessEvent() "+event.getPoint()+" from "+event.getComponent()); Point p = SwingUtilities.convertPoint(event.getComponent(), event.getX(), event.getY(), _table); // logger.info("Related to table, point values "+p); int currentRowBeginning = 0; int deltaY = 0; int currentRowHeight; for (int i = 0; i < getRowCount(); i++) { currentRowHeight = getRowHeight(i); if (p.y < currentRowBeginning + currentRowHeight) { p.y = p.y - deltaY; // logger.info("Related to table, point FINALLY set to "+p); event.translatePoint(0, -deltaY); // logger.info("finally preprocessEvent() "+event.getPoint()); return; } deltaY = deltaY + currentRowHeight - getRowHeight(); currentRowBeginning = currentRowBeginning + currentRowHeight; } } } } public void treeStructureChanged() { _elementsForRow.clear(); /*_rowForObjects.clear(); _heightForObjects.clear();*/ _table.treeStructureChanged(); /*logger.info("On redessine toute la JTreeTable"); _table.revalidate(); _table.repaint();*/ } public void setRowHeightForObject(FlexoModelObject obj, int height) { if (obj != null) { _heightForObjects.put(obj, new Integer(height)); } } public void setRowHeight(int row, int height) { setRowHeightForObject(getObjectAt(row), height); } public int getRowHeight(int row) { return getRowHeightForObject(getObjectAt(row)); } public int getRowHeightForObject(FlexoModelObject object) { if (object == null) { return getRowHeight(); } Integer returned = _heightForObjects.get(object); if (returned == null) { return getRowHeight(); } return returned.intValue(); } @Override public void setBounds(int x, int y, int w, int h) { super.setBounds(x, 0, w, _table.getHeight()); } @Override public TreePath getClosestPathForLocation(int x, int y) { // logger.info("called getClosestPathForLocation() "+x+","+y); int currentRowBeginning = 0; int deltaY = 0; int currentRowHeight; for (int i = 0; i < getRowCount(); i++) { currentRowHeight = getRowHeight(i); if (y < currentRowBeginning + currentRowHeight) { // logger.info("finally call getClosestPathForLocation() "+x+","+(y-deltaY)); return super.getClosestPathForLocation(x, y - deltaY); } deltaY = deltaY + currentRowHeight - getRowHeight(); currentRowBeginning = currentRowBeginning + currentRowHeight; } return super.getClosestPathForLocation(x, y); } @Override public Rectangle getPathBounds(TreePath path) { // logger.info("getPathBounds() called"); Rectangle returned = super.getPathBounds(path); FlexoModelObject obj = ((BrowserElement) path.getLastPathComponent()).getObject(); if (getRowHeightForObject(obj) != getRowHeight()) { returned.height = getRowHeightForObject(obj); } return returned; } @Override public void paint(Graphics g) { // logger.info("paint() in TreeTableCellRenderer for visible row !"+visibleRow); int translation = 0; for (int i = 0; i < visibleRow; i++) { int rowHeight = getRowHeight(i); translation = translation + rowHeight; // logger.info("Row height for row "+i+" values "+rowHeight); } // g.translate(0, -translation); g.translate(0, -visibleRow * getRowHeight()); // logger.info("was: "+(-visibleRow*getRowHeight())+" is "+(-translation)); super.paint(g); g.setColor(getComponentBackground(false, false, visibleRow, 0)); g.fillRect(0, getRowHeight() + visibleRow * getRowHeight(), getWidth(), getRowHeight(visibleRow) - getRowHeight()); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { setComponentBackground(this, hasFocus, isSelected, row, column); visibleRow = row; return this; } protected void setComponentBackground(Component component, boolean hasFocus, boolean isSelected, int row, int column) { if (hasFocus && isSelected) { component.setForeground(ColorCst.SELECTED_CELL_TABULAR_VIEW_FOREGROUND_COLOR); } else { component.setForeground(ColorCst.UNSELECTED_CELL_TABULAR_VIEW_FOREGROUND_COLOR); } component.setBackground(getComponentBackground(hasFocus, isSelected, row, column)); } protected Color getComponentBackground(boolean hasFocus, boolean isSelected, int row, int column) { if (isSelected) { return ColorCst.SELECTED_LINES_TABULAR_VIEW_COLOR; } else { if (row % 2 == 0) { return ColorCst.ODD_LINES_TABULAR_VIEW_COLOR; } else { return ColorCst.NON_ODD_LINES_TABULAR_VIEW_COLOR; } } } protected BrowserElement getElementAt(int row) { return _elementsForRow.get(new Integer(row)); } protected FlexoModelObject getObjectAt(int row) { if (getElementAt(row) != null) { return getElementAt(row).getObject(); } return null; } /** * Cell renderer for a browser view * * @author sguerin */ public class JTreeCellRenderer extends BrowserViewCellRenderer { /*public int getHeight() { return 50; }*/ @Override public Component getTreeCellRendererComponent(JTree arg0, Object object, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(arg0, object, selected, expanded, leaf, row, hasFocus); _row = row; if (object instanceof BrowserElement && ((BrowserElement) object).getObject() != null) { Integer rowInt = new Integer(_row); _elementsForRow.put(rowInt, (BrowserElement) object); _rowForObjects.put(((BrowserElement) object).getObject(), rowInt); } setSize(getWidth(), 50); return this; } private int _row = 0; @Override public Color getBackgroundNonSelectionColor() { if (_row % 2 == 0) { return ColorCst.ODD_LINES_TABULAR_VIEW_COLOR; } else { return ColorCst.NON_ODD_LINES_TABULAR_VIEW_COLOR; } } } }