org.datafx.control.cell.ExpandOnMouseEventTableRow Maven / Gradle / Ivy
/**
* Copyright (c) 2011, 2013, Jonathan Giles, Johan Vos, Hendrik Ebbers
* 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.
* * Redistributions 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 DataFX, the website javafxdata.org, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.datafx.control.cell;
import com.sun.javafx.scene.control.skin.TableRowSkin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.WeakHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.input.MouseEvent;
import javafx.util.Duration;
import static org.datafx.control.cell.ExpandOnMouseEventCell.ANIMATION_DURATION;
import static org.datafx.control.cell.ExpandOnMouseEventCell.expandedCellsMap;
import static org.datafx.control.cell.ExpandOnMouseEventCell.expandedIndicesMap;
/**
* A class containing a {@link TableRow} implementation that expands when a
* certain mouse event occurs. When the row expands, it allows for additional
* information to be shown to the user in all columns.
*
*
*
* @param The type of the elements contained within the TableView.
* @author Jonathan Giles
*/
public class ExpandOnMouseEventTableRow extends TableRow {
public static final double DEFAULT_PREF_HEIGHT = 24;
public static final double DEFAULT_EXPAND_HEIGHT = DEFAULT_PREF_HEIGHT * 3;
@SuppressWarnings("unchecked")
public static EventType> cellResizeEvent() {
return (EventType>) CELL_RESIZE_EVENT;
}
private static final EventType> CELL_RESIZE_EVENT =
new EventType(Event.ANY, "CELL_RESIZE");
private int maxExpandedCells = 1;
private final double expandedHeight;
public ExpandOnMouseEventTableRow() {
this(1);
}
public ExpandOnMouseEventTableRow(int maxExpandedCells) {
this(maxExpandedCells, DEFAULT_EXPAND_HEIGHT, MouseEvent.MOUSE_CLICKED);
}
public ExpandOnMouseEventTableRow(int maxExpandedCells, double expandHeight) {
this(maxExpandedCells, expandHeight, MouseEvent.MOUSE_CLICKED);
}
public ExpandOnMouseEventTableRow(int maxExpandedCells, double expandHeight, EventType mouseEventType) {
this.maxExpandedCells = maxExpandedCells >= 0 ? maxExpandedCells : 1;
this.expandedHeight = expandHeight < 0.0 ? DEFAULT_EXPAND_HEIGHT : expandHeight;
this.setEditable(false);
prefHeightProperty().addListener(new InvalidationListener() {
@Override public void invalidated(Observable arg0) {
requestLayout();
}
});
addEventFilter(mouseEventType == null ? MouseEvent.MOUSE_CLICKED : mouseEventType,
new EventHandler() {
@Override public void handle(MouseEvent t) {
if (isEmpty()) {
return;
}
// flip whether this cell index is expanded or not
flip(ExpandOnMouseEventTableRow.this, getMaxExpandedCells());
}
});
}
@Override public void updateIndex(int i) {
super.updateIndex(i);
if (isExpanded()) {
// immediately expand this cell
setPrefHeight(DEFAULT_EXPAND_HEIGHT);
} else {
// immediately collapse this cell
setPrefHeight(DEFAULT_PREF_HEIGHT);
}
}
public void setOnCellResize(EventHandler> evt) {
if (evt == null) {
return;
}
addEventHandler(ExpandOnMouseEventTableRow.cellResizeEvent(), evt);
}
private static void flip(ExpandOnMouseEventTableRow> cell, int maxExpandedCells) {
Queue expandedCells = expandedCellsMap.get(cell.getTableView());
if (expandedCells == null) {
expandedCells = new LinkedBlockingQueue();
expandedCellsMap.put(cell.getTableView(), expandedCells);
}
List expandedIndices = expandedIndicesMap.get(cell.getTableView());
if (expandedIndices == null) {
expandedIndices = new ArrayList();
expandedIndicesMap.put(cell.getTableView(), expandedIndices);
}
boolean cellIsExpanded = expandedCells.contains(cell);
if (maxExpandedCells <= expandedCells.size() && !cellIsExpanded) {
ExpandOnMouseEventTableRow collapsed = (ExpandOnMouseEventTableRow) expandedCells.remove();
animate(collapsed, false);
expandedIndices.remove((Object) collapsed.getIndex());
}
if (cellIsExpanded) {
expandedCells.remove(cell);
expandedIndices.remove((Object) cell.getIndex());
} else {
expandedCells.add(cell);
expandedIndices.add(cell.getIndex());
}
animate(cell, !cellIsExpanded);
}
public boolean isExpanded() {
return isExpanded(this);
}
public static boolean isExpanded(ExpandOnMouseEventTableRow> cell) {
if (expandedIndicesMap.containsKey(cell.getTableView())) {
boolean isExpanded = expandedIndicesMap.get(cell.getTableView()).contains(cell.getIndex());
// if (isExpanded) {
// System.out.println("row " + cell.getIndex() + " is expanded: " + isExpanded);
// }
return isExpanded;
}
return false;
}
public int getMaxExpandedCells() {
return maxExpandedCells;
}
public void setMaxExpandedCells(int maxExpandedCells) {
this.maxExpandedCells = maxExpandedCells;
}
@Override protected double computePrefHeight(double width) {
return isExpanded() ? getPrefHeight() : super.computePrefHeight(width);
}
@Override protected double computeMinHeight(double width) {
return computePrefHeight(width);
}
private static void animate(final ExpandOnMouseEventTableRow cell, final boolean toExpanded) {
double startHeight = cell.getHeight();
// the end height is the opposite of the current state -
// we are animating out of this state after all
double endHeight = toExpanded ? cell.expandedHeight : DEFAULT_PREF_HEIGHT;
// create a timeline to expand/collapse the cell. All this
// really does is modify the height of the content
Timeline timeline = new Timeline();
timeline.setCycleCount(1);
timeline.setAutoReverse(false);
// first key frame
EventHandler fireEvent = new EventHandler() {
@Override public void handle(ActionEvent arg0) {
// if we are collapsing, we inform listeners now
if (!toExpanded && !isExpanded(cell)) {
fireEvents(cell, toExpanded);
// cell.fireEvent(new CellResizeEvent(cellResizeEvent(), cell, toExpanded));
}
}
};
KeyFrame firstFrame = new KeyFrame(Duration.ZERO,
fireEvent,
new KeyValue(cell.prefHeightProperty(), startHeight, Interpolator.EASE_BOTH));
// second key frame
EventHandler fireEvent2 = new EventHandler() {
@Override public void handle(ActionEvent arg0) {
// if we are collapsing, we inform listeners now
if (toExpanded && isExpanded(cell)) {
fireEvents(cell, toExpanded);
// cell.fireEvent(new CellResizeEvent(cellResizeEvent(), cell, toExpanded));
}
}
};
KeyFrame secondFrame = new KeyFrame(Duration.millis(ANIMATION_DURATION),
fireEvent2,
new KeyValue(cell.prefHeightProperty(), endHeight, Interpolator.EASE_BOTH));
timeline.getKeyFrames().addAll(
firstFrame,
secondFrame);
timeline.playFromStart();
}
private static void fireEvents(ExpandOnMouseEventTableRow tableRow, boolean toExpanded) {
CellResizeEvent event = new CellResizeEvent(cellResizeEvent(), tableRow, toExpanded);
tableRow.fireEvent(event);
Map cells = event.getCells();
for (Map.Entry e : cells.entrySet()) {
if (e.getValue() instanceof ExpandingTableCell) {
((ExpandingTableCell) e.getValue()).updateExpanded(toExpanded);
}
}
}
public static class CellResizeEvent extends Event {
private final ExpandOnMouseEventTableRow tableRow;
private final boolean expanded;
private Map cells;
public CellResizeEvent(EventType extends Event> eventType, ExpandOnMouseEventTableRow tableRow, boolean isExpanded) {
super(eventType);
this.tableRow = tableRow;
this.expanded = isExpanded;
}
public ExpandOnMouseEventTableRow getTableRow() {
return tableRow;
}
public boolean isExpanded() {
return expanded;
}
public Map getCells() {
if (cells != null) {
return cells;
}
if (tableRow.getSkin() instanceof TableRowSkin) {
TableRowSkin trs = (TableRowSkin) tableRow.getSkin();
List children = trs.getChildren();
if (children == null || children.isEmpty()) {
return Collections.emptyMap();
}
cells = new WeakHashMap();
for (int i = 0; i < children.size(); i++) {
Node n = children.get(i);
if (n instanceof TableCell) {
TableCell tableCell = (TableCell) n;
if (tableCell.getTableColumn() == null) {
continue;
}
cells.put(tableCell.getTableColumn(), tableCell);
}
}
return cells;
}
return Collections.emptyMap();
}
}
} | |