net.sourceforge.jbizmo.commons.richclient.javafx.control.AbstractSelectionListPanel Maven / Gradle / Ivy
/*
* This file is part of JBizMo, a set of tools, libraries and plug-ins
* for modeling and creating Java-based enterprise applications.
* For more information visit:
*
* http://sourceforge.net/projects/jbizmo/
*
* This software 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 2 of the License, or
* (at your option) any later version.
*
* This software 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 software; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sourceforge.jbizmo.commons.richclient.javafx.control;
import static javafx.scene.input.MouseEvent.MOUSE_CLICKED;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_DESELECT_ALL;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_DESELECT_ITEM;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_SELECT_ALL;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_SELECT_ITEM;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.getImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import net.sourceforge.jbizmo.commons.richclient.javafx.dialog.DialogUtil;
import net.sourceforge.jbizmo.commons.richclient.javafx.i18n.I18NJavaFX;
/**
*
* Abstract generic base class for dual data selection list components
*
*
* Copyright 2015 (C) by Martin Ganserer
*
* @author Martin Ganserer
* @version 1.0.0
* @param the type of the selection list
*/
public abstract class AbstractSelectionListPanel extends VBox
{
private static final long INPUT_DELAY = 250;
private ListView sourceListView;
private ListView targetListView;
private Button cmdSelectItem;
private Button cmdSelectAllItems;
private Button cmdDeselectItem;
private Button cmdDeselectAllItems;
private TextField txtFilter;
private DataFetchTask task;
private boolean addSearchField;
/**
* Constructor
*/
public AbstractSelectionListPanel()
{
super();
initialize(null, false);
}
/**
* Constructor
* @param title
*/
public AbstractSelectionListPanel(String title)
{
super();
initialize(title, false);
}
/**
* Constructor
* @param title
* @param addSearchField
*/
public AbstractSelectionListPanel(String title, boolean addSearchField)
{
super();
this.addSearchField = addSearchField;
initialize(title, addSearchField);
}
/**
* @param element
* @return the string representation of a given element
*/
public abstract String getItemText(T element);
/**
* @param filter
* @return a list containing all items to be displayed in source list
* @throws Exception if search operation has failed
*/
public abstract List searchItems(String filter) throws Exception;
/**
* @return a list containing all available items
*/
public List getAvailableItems()
{
return sourceListView.getItems().stream().collect(Collectors.toList());
}
/**
* @param availableItems
*/
public void setAvailableItems(Collection availableItems)
{
sourceListView.getItems().clear();
// Remove all items that are already selected!
sourceListView.getItems().addAll(availableItems.stream().filter((f) -> !getSelectedItems().contains(f)).collect(Collectors.toList()));
}
/**
* @return a list containing all selected items
*/
public List getSelectedItems()
{
return targetListView.getItems().stream().collect(Collectors.toList());
}
/**
* @param selectedItems
*/
public void setSelectedItems(Collection selectedItems)
{
targetListView.getItems().clear();
targetListView.getItems().addAll(selectedItems);
}
/**
* Move element form source to target list
*/
private void moveToTarget()
{
move(sourceListView, targetListView);
sourceListView.getSelectionModel().clearSelection();
}
/**
* Move all elements to target list
*/
private void moveAllToTarget()
{
move(sourceListView, targetListView, new ArrayList<>(sourceListView.getItems()));
sourceListView.getSelectionModel().clearSelection();
}
/**
* Move element from target to source list
*/
private void moveToSource()
{
move(targetListView, sourceListView);
targetListView.getSelectionModel().clearSelection();
}
/**
* Move all elements to source list
*/
private void moveAllToSource()
{
move(targetListView, sourceListView, new ArrayList<>(targetListView.getItems()));
targetListView.getSelectionModel().clearSelection();
}
/**
* Move all items from one list view to another
* @param fromList
* @param toList
*/
private void move(ListView fromList, ListView toList)
{
final List selectedItems = new ArrayList<>(fromList.getSelectionModel().getSelectedItems());
move(fromList, toList, selectedItems);
}
/**
* Move a list of items from one list view to another
* @param fromList
* @param toList
* @param items
*/
private void move(ListView fromList, ListView toList, List items)
{
for(final T item : items)
{
fromList.getItems().remove(item);
toList.getItems().add(item);
}
}
/**
* Task for fetching selection elements
*/
private class DataFetchTask extends Task
{
private final String filter;
/**
* Constructor
* @param filter
*/
public DataFetchTask(String filter)
{
super();
this.filter = filter;
}
/*
* (non-Javadoc)
* @see javafx.concurrent.Task#scheduled()
*/
@Override
protected void scheduled()
{
getScene().setCursor(Cursor.WAIT);
}
/*
* (non-Javadoc)
* @see javafx.concurrent.Task#call()
*/
@Override
public Void call() throws Exception
{
final long startTime = System.currentTimeMillis();
final List data = searchItems(filter);
if(addSearchField)
{
final long sleepTime = startTime + INPUT_DELAY - System.currentTimeMillis();
// Don't sleep if the operation took longer than the input delay!
if(sleepTime > 0 && !isDone())
Thread.sleep(sleepTime);
}
if(isDone())
return null;
if(data != null)
Platform.runLater(() -> setAvailableItems(data));
return null;
}
/*
* (non-Javadoc)
* @see java.util.concurrent.FutureTask#done()
*/
@Override
protected void done()
{
Platform.runLater(() -> getScene().setCursor(Cursor.DEFAULT));
}
/*
* (non-Javadoc)
* @see javafx.concurrent.Task#failed()
*/
@Override
protected void failed()
{
DialogUtil.openErrorDialog(null, I18NJavaFX.getInstance().getString("data_fetch_action.msg_query_failed"), getException());
}
}
/**
* Overwrite textual representation of a {@link ListCell}
*/
private class InternalCallback implements Callback, ListCell>
{
/*
* (non-Javadoc)
* @see javafx.util.Callback#call(java.lang.Object)
*/
@Override
public ListCell call(ListView arg0)
{
return new ListCell()
{
/*
* (non-Javadoc)
* @see javafx.scene.control.Cell#updateItem(java.lang.Object, boolean)
*/
@Override
protected void updateItem(T t, boolean bln)
{
super.updateItem(t, bln);
if(t != null)
setText(getItemText(t));
else
setText("");
}
};
}
}
/**
* Refresh source list
*/
private void refresh()
{
if(task != null && task.isRunning())
task.cancel();
new Thread(task = new DataFetchTask(txtFilter.getText())).start();
}
/**
* Initialize component
* @param title
* @param addSearchField
*/
private void initialize(String title, boolean addSearchField)
{
GridPane panFilter = null;
if(addSearchField)
{
panFilter = new GridPane();
panFilter.setHgap(5.0);
panFilter.getColumnConstraints().add(new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.SOMETIMES, HPos.LEFT, false));
panFilter.getColumnConstraints().add(new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.LEFT, true));
panFilter.add(new Label(I18NJavaFX.getInstance().getString("selection_list_panel.lbl_search_elements")), 0, 0);
panFilter.add(txtFilter = new TextField(), 1, 0);
panFilter.setPadding(new Insets(5, 0, 10, 0));
txtFilter.textProperty().addListener((observable, oldValue, newValue) -> refresh());
}
final GridPane panLists = new GridPane();
panLists.getColumnConstraints().add(new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.LEFT, true));
panLists.getColumnConstraints().add(new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, HPos.CENTER, false));
panLists.getColumnConstraints().add(new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.LEFT, true));
panLists.setHgap(5);
panLists.setVgap(5);
panLists.setPrefHeight(150);
panLists.setMaxHeight(Double.MAX_VALUE);
panLists.setMaxWidth(Double.MAX_VALUE);
final Label lblSource = new Label(I18NJavaFX.getInstance().getString("selection_list_panel.lbl_source_list"));
final Label lblTarget = new Label(I18NJavaFX.getInstance().getString("selection_list_panel.lbl_target_list"));
sourceListView = new ListView<>();
sourceListView.setCellFactory(new InternalCallback());
targetListView = new ListView<>();
targetListView.setCellFactory(new InternalCallback());
sourceListView.addEventHandler(MOUSE_CLICKED, event ->
{
if(event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2)
moveToTarget();
});
targetListView.addEventHandler(MOUSE_CLICKED, event ->
{
if(event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2)
moveToSource();
});
final VBox panButtons = new VBox(5);
panButtons.setFillWidth(true);
cmdSelectItem = new Button("", new ImageView(getImage(IMG_SELECT_ITEM)));
cmdSelectItem.setOnAction(e -> moveToTarget());
cmdSelectAllItems = new Button("", new ImageView(getImage(IMG_SELECT_ALL)));
cmdSelectAllItems.setOnAction(e -> moveAllToTarget());
cmdDeselectItem = new Button("", new ImageView(getImage(IMG_DESELECT_ITEM)));
cmdDeselectItem.setOnAction(e -> moveToSource());
cmdDeselectAllItems = new Button("", new ImageView(getImage(IMG_DESELECT_ALL)));
cmdDeselectAllItems.setOnAction(e -> moveAllToSource());
panButtons.getChildren().addAll(cmdSelectItem, cmdSelectAllItems, cmdDeselectItem, cmdDeselectAllItems);
panLists.add(lblSource, 0, 0);
panLists.add(lblTarget, 2, 0);
panLists.add(sourceListView, 0, 1);
panLists.add(panButtons, 1, 1);
panLists.add(targetListView, 2, 1);
GridPane.setValignment(panButtons, VPos.CENTER);
GridPane.setVgrow(panButtons, Priority.NEVER);
GridPane.setVgrow(sourceListView, Priority.ALWAYS);
GridPane.setVgrow(targetListView, Priority.ALWAYS);
if(title != null)
{
final VBox panTitleContent = new VBox();
if(panFilter != null)
panTitleContent.getChildren().add(panFilter);
panTitleContent.getChildren().add(panLists);
final TitledPane panTitle = new TitledPane(title, panTitleContent);
panTitle.setCollapsible(false);
panTitle.setMaxHeight(Double.MAX_VALUE);
panTitle.setMaxWidth(Double.MAX_VALUE);
getChildren().add(panTitle);
VBox.setVgrow(panTitleContent, Priority.ALWAYS);
VBox.setVgrow(panLists, Priority.ALWAYS);
VBox.setVgrow(panTitle, Priority.ALWAYS);
}
else
{
if(panFilter != null)
getChildren().add(panFilter);
getChildren().add(panLists);
VBox.setVgrow(panLists, Priority.ALWAYS);
}
}
/**
* @param tooltip
*/
public void setTooltip(Tooltip tooltip)
{
targetListView.setTooltip(tooltip);
}
}