All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.jbizmo.commons.richclient.javafx.tree.AbstractTreeView 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.tree;

import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_REFRESH;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_SEARCH;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.IMG_STOP;
import static net.sourceforge.jbizmo.commons.richclient.javafx.image.ImageLoader.getImage;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tab;
import javafx.scene.control.ToolBar;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import net.sourceforge.jbizmo.commons.richclient.format.FormatDTO;
import net.sourceforge.jbizmo.commons.richclient.format.FormatPreferencesManager;
import net.sourceforge.jbizmo.commons.richclient.javafx.control.Action;
import net.sourceforge.jbizmo.commons.richclient.javafx.control.StatusBar;
import net.sourceforge.jbizmo.commons.richclient.javafx.control.View;
import net.sourceforge.jbizmo.commons.richclient.javafx.dialog.DialogButtonType;
import net.sourceforge.jbizmo.commons.richclient.javafx.dialog.DialogUtil;
import net.sourceforge.jbizmo.commons.richclient.javafx.i18n.I18NJavaFX;
import net.sourceforge.jbizmo.commons.richclient.javafx.search.Countable;
import net.sourceforge.jbizmo.commons.richclient.javafx.search.SearchInputDialog;
import net.sourceforge.jbizmo.commons.richclient.search.util.SearchManager;
import net.sourceforge.jbizmo.commons.search.dto.SearchDTO;
import net.sourceforge.jbizmo.commons.search.exception.GeneralSearchException;

/**
 * 

* Abstract base class for tree views *

*

* Copyright 2015 (C) by Martin Ganserer *

* @author Martin Ganserer * @version 1.0.0 * @param the type of the data objects for the root tree item */ public abstract class AbstractTreeView extends Tab implements View, Countable { protected FormatDTO userFormat = FormatPreferencesManager.getFormatDTO(); protected SimpleDateFormat dateTimeFormat = new SimpleDateFormat(userFormat.getDateTimeFormat()); protected SimpleDateFormat dateFormat = new SimpleDateFormat(userFormat.getDateFormat()); protected DecimalFormat decimalFormat = new DecimalFormat(userFormat.getDecimalFormat()); protected SearchDTO searchObj = new SearchDTO(); protected ToolBar toolBar; protected TreeView treeView; protected Action refreshAction; protected Action suspendAction; protected Action searchInputAction; protected StatusBar statusBar; protected TreeDataItem dragItem; private DataFetchTask refreshViewTask; /** * @return a list of all items that should be added to the tree * @throws Exception if data fetch operation has failed */ protected abstract List fetchRootItems() throws Exception; /** * @param rootTreeItems */ public abstract void addRootTreeItems(List rootTreeItems); /** * @param item * @return the context menu for a given tree item */ @SuppressWarnings("unused") public ContextMenu getContextMenuForTreeItem(TreeDataItem item) { return null; } /** * @return a search object for advanced search operations or null if this option shouldn't be added */ public SearchDTO initAdvancedSearch() { return null; } /** * @return the quick search bar or null if this functionality shouldn't be added */ protected Node getQuickSearchBar() { return null; } /** * @param item * @return the transfer mode or null if the item shouldn't be dragged */ @SuppressWarnings("unused") public TransferMode startDrag(TreeDataItem item) { return null; } /** * Callback method that is called when a drop operation occurred * @param e * @param item */ @SuppressWarnings("unused") public void onDragDropped(DragEvent e, TreeDataItem item) { } /** * @return the ID of the view */ public String getViewID() { return getClass().getName(); } /** * Callback method that is called when a count operation can be invoked * @return the count result * @throws Exception if the operation has failed */ protected long onPerformCountOperation() throws Exception { if(initAdvancedSearch() == null || !searchObj.isCount()) return 0; try { return countData(); } catch (final GeneralSearchException e) { e.printStackTrace(); } return 0; } /** * Callback method that is called when a data fetch operation starts */ protected void onStartSearch() { // Remove all existing items from tree view treeView.setRoot(new TreeDataItem(null, null)); statusBar.showProgress(); statusBar.setText(I18NJavaFX.getInstance().getString("data_fetch_action.status_fetch_data")); suspendAction.setEnabled(true); refreshAction.setEnabled(false); if(searchInputAction != null) searchInputAction.setEnabled(false); } /** * Callback method that is called as soon as a data fetch operation is finished * @param itemsFetched * @param countResult * @param timeElapsed */ protected void onFinishSearch(int itemsFetched, long countResult, long timeElapsed) { String statusMessage = ""; suspendAction.setEnabled(false); refreshAction.setEnabled(true); if(searchInputAction != null) searchInputAction.setEnabled(true); if(searchObj.isCount() && countResult > 0) { final ArrayList params = new ArrayList<>(); params.add(itemsFetched); params.add(countResult); params.add(String.format("%.2f", timeElapsed / 1000.0)); // The translation expects page information that we cannot provide at this point. Thus we just add "reasonable" default values! params.add(1); params.add(1); statusMessage = I18NJavaFX.getInstance().getString("data_fetch_action.result_with_count", params.toArray()); } else { final ArrayList params = new ArrayList<>(); params.add(itemsFetched); params.add(String.format("%.2f", timeElapsed / 1000.0)); statusMessage = I18NJavaFX.getInstance().getString("data_fetch_action.result_no_count", params.toArray()); } statusBar.setText(statusMessage); statusBar.stopProgress(); } /** * Callback method that is called when a data fetch operation has failed * @param cause the exception that caused the data fetch operation to fail. If null the operation has been cancelled by the user! */ protected void onSearchFailed(Throwable cause) { statusBar.stopProgress(); suspendAction.setEnabled(false); refreshAction.setEnabled(true); if(searchInputAction != null) searchInputAction.setEnabled(true); if(cause == null) statusBar.setText(I18NJavaFX.getInstance().getString("data_fetch_action.status_op_canceled")); else DialogUtil.openErrorDialog(null, I18NJavaFX.getInstance().getString("data_fetch_action.msg_query_failed"), cause); } /** * Refresh view */ public void refreshView() { new Thread(refreshViewTask = new DataFetchTask()).start(); } /** * Action to refresh view */ private class RefreshAction extends Action { /** * Constructor */ public RefreshAction() { this.title = I18NJavaFX.getInstance().getString("action_refresh.title"); this.image = getImage(IMG_REFRESH); } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.control.Action#handle() */ @Override public void handle() { searchObj = new SearchDTO(); searchObj.setMaxResult(1000); searchObj.setExactFilterMatch(true); searchObj.setCaseSensitive(false); searchObj.setCount(true); refreshView(); } } /** * Action to suspend the search */ protected class SuspendSearchAction extends Action { /** * Constructor */ public SuspendSearchAction() { this.title = I18NJavaFX.getInstance().getString("action_suspend.title"); this.image = getImage(IMG_STOP); } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.control.Action#handle() */ @Override public void handle() { if(refreshViewTask != null && refreshViewTask.isRunning()) refreshViewTask.cancel(); } } /** * Action for opening search input dialog */ private class SearchInputAction extends Action { /** * Constructor */ public SearchInputAction() { this.title = I18NJavaFX.getInstance().getString("action_search.title"); this.image = getImage(IMG_SEARCH); } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.control.Action#handle() */ @Override public void handle() { refreshFormatSettings(); searchObj = initAdvancedSearch(); final SearchInputDialog dlg = new SearchInputDialog(null, searchObj, AbstractTreeView.this); dlg.setSize(700, 600); if(DialogButtonType.OK != dlg.open()) return; SearchManager.saveLastSearch(getViewID(), searchObj); refreshView(); } } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.control.View#initialize() */ @Override public void initialize() { final VBox panRoot = new VBox(); toolBar = new ToolBar(); statusBar = new StatusBar(); treeView = new TreeView<>(); treeView.setShowRoot(false); treeView.setCellFactory(new Callback, TreeCell>() { /* * (non-Javadoc) * @see javafx.util.Callback#call(java.lang.Object) */ @Override public TreeCell call(TreeView param) { return new DragAndDropCell(); } }); panRoot.getChildren().add(toolBar); final Node quickSearchBar = getQuickSearchBar(); if(quickSearchBar != null) panRoot.getChildren().add(quickSearchBar); panRoot.getChildren().add(treeView); panRoot.getChildren().add(statusBar); panRoot.setPadding(new Insets(5, 5, 5, 5)); VBox.setVgrow(treeView, Priority.ALWAYS); initActions(); setContent(panRoot); } /** * Initialize actions */ protected void initActions() { refreshAction = new RefreshAction(); suspendAction = new SuspendSearchAction(); toolBar.getItems().add(refreshAction.createToolbarButton()); toolBar.getItems().add(suspendAction.createToolbarButton()); suspendAction.setEnabled(false); if(initAdvancedSearch() != null) { searchInputAction = new SearchInputAction(); toolBar.getItems().add(searchInputAction.createToolbarButton()); } } /** * Refresh format settings */ protected void refreshFormatSettings() { userFormat = FormatPreferencesManager.getFormatDTO(); decimalFormat = new DecimalFormat(userFormat.getDecimalFormat()); dateTimeFormat = new SimpleDateFormat(userFormat.getDateTimeFormat()); dateFormat = new SimpleDateFormat(userFormat.getDateFormat()); searchObj.setDecimalSeparator(DecimalFormatSymbols.getInstance().getDecimalSeparator()); searchObj.setGroupingSeparator(DecimalFormatSymbols.getInstance().getGroupingSeparator()); searchObj.setDateFormat(userFormat.getDateFormat()); searchObj.setDateTimeFormat(userFormat.getDateTimeFormat()); searchObj.setNumberFormat(userFormat.getDecimalFormat()); } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.control.View#getTab() */ @Override public Tab getTab() { return this; } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.control.View#getSavedQueryId() */ @Override public Integer getSavedQueryId() { return null; } /* * (non-Javadoc) * @see net.sourceforge.jbizmo.commons.richclient.javafx.search.Countable#countData() */ @Override public long countData() throws GeneralSearchException { return 0; } /** * Implementation of tree cell that supports drag and drop operations */ private class DragAndDropCell extends TreeCell { /** * Constructor */ public DragAndDropCell() { setOnDragDetected((e) -> { if(!(getTreeItem() instanceof TreeDataItem)) return; final TransferMode mode = startDrag((TreeDataItem) getTreeItem()); if(mode == null) return; dragItem = (TreeDataItem) getTreeItem(); final Map content = new HashMap<>(); content.put(DataFormat.PLAIN_TEXT, dragItem.getValue() == null ? "" : dragItem.getValue()); final Dragboard dragBoard = startDragAndDrop(mode); dragBoard.setContent(content); e.consume(); }); setOnDragDone((e) -> e.consume()); setOnDragOver((e) -> { e.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE); e.consume(); }); setOnDragDropped((e) -> { // We don't have to inform a listener about a drop operation if either the drag or the drop item is missing! if(getTreeItem() == null || dragItem == null) return; if(!(getTreeItem() instanceof TreeDataItem)) return; onDragDropped(e, (TreeDataItem) getTreeItem()); dragItem = null; }); } /* * (non-Javadoc) * @see javafx.scene.control.Cell#updateItem(java.lang.Object, boolean) */ @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); if(empty) { setText(null); setGraphic(null); setContextMenu(null); return; } if(getTreeItem() != null) { setText(item); setGraphic(getTreeItem().getGraphic()); setContextMenu(getContextMenuForTreeItem((TreeDataItem) getTreeItem())); } } } /** * Task for fetching data in a separate thread */ private class DataFetchTask extends Task { /* * (non-Javadoc) * @see javafx.concurrent.Task#cancelled() */ @Override protected void cancelled() { onSearchFailed(null); } /* * (non-Javadoc) * @see javafx.concurrent.Task#call() */ @Override public Void call() throws Exception { final List data; final long start = System.currentTimeMillis(); final long countResult; Platform.runLater(() -> onStartSearch()); data = fetchRootItems(); if(isDone()) return null; countResult = onPerformCountOperation(); if(isDone()) return null; Platform.runLater(() -> { if(data != null) { addRootTreeItems(data); onFinishSearch(data.size(), countResult, System.currentTimeMillis() - start); } else onFinishSearch(0, countResult, System.currentTimeMillis() - start); }); return null; } /* * (non-Javadoc) * @see javafx.concurrent.Task#failed() */ @Override protected void failed() { onSearchFailed(getException()); } } }