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

ensemble.controls.SearchBox Maven / Gradle / Ivy

/*
 * Copyright (c) 2008, 2012 Oracle and/or its affiliates.
 * All rights reserved. Use is subject to license terms.
 *
 * This file is available and licensed under the following license:
 *
 * 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 Oracle Corporation 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 THE COPYRIGHT
 * OWNER OR CONTRIBUTORS 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 ensemble.controls;

import ensemble.Ensemble2;
import ensemble.search.DocumentType;
import ensemble.search.IndexSearcher;
import ensemble.search.SearchResult;
import java.util.List;
import java.util.Map;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Popup;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import org.apache.lucene.queryParser.ParseException;

/**
 * A mac style search box with drop down with results
 */
public class SearchBox extends Region {
    private TextField textBox;
    private Button clearButton;
    private IndexSearcher indexSearcher;
    private ContextMenu contextMenu = new ContextMenu();
    private Popup extraInfoPopup = new Popup();
    private Label infoName;
    private Label infoDescription;
    private VBox infoBox;
    private Tooltip searchErrorTooltip = new Tooltip();
    private Timeline searchErrorTooltipHidder = null;

    public SearchBox() {
        setId("SearchBox");
        setMinHeight(24);
        setPrefSize(150, 24);
        setMaxHeight(24);
        textBox = new TextField();
        textBox.setPromptText("Search");
        clearButton = new Button();
        clearButton.setVisible(false);
        getChildren().addAll(textBox, clearButton);
        clearButton.setOnAction(new EventHandler() {
            @Override public void handle(ActionEvent actionEvent) {
                textBox.setText("");
                textBox.requestFocus();
            }
        });
        textBox.setOnKeyReleased(new EventHandler() {
            @Override public void handle(KeyEvent keyEvent) {
                if (keyEvent.getCode() == KeyCode.DOWN) {
                    ///System.out.println("SearchBox.handle DOWN");
                    contextMenu.setFocused(true);
                }
            }
        });
        textBox.textProperty().addListener(new ChangeListener() {
            @Override public void changed(ObservableValue observable, String oldValue, String newValue) {
                clearButton.setVisible(textBox.getText().length() != 0);
                if (textBox.getText().length() == 0) {
                    if (contextMenu != null) contextMenu.hide();
                    showError(null);
                } else {
                    boolean haveResults = false;
                    Map> results = null;
                    try {
                        if (indexSearcher == null) indexSearcher = new IndexSearcher();
                        results = indexSearcher.search(
                                textBox.getText() + (textBox.getText().matches("\\w+") ? "*" : "")
                        );
                        // check if we have any results
                        for (List categoryResults: results.values()) {
                            if (categoryResults.size() > 0) {
                                haveResults = true;
                                break;
                            }
                        }
                    } catch (ParseException e) {
                        showError(e.getMessage().substring("Cannot parse ".length()));
                    }
                    if (haveResults) {
                        showError(null);
                        populateMenu(results);
                        if (!contextMenu.isShowing()) contextMenu.show(SearchBox.this, Side.BOTTOM, 10,-5);
                    } else {
                        if (searchErrorTooltip.getText() == null) showError("No matches");
                        contextMenu.hide();
                    }
                    contextMenu.setFocused(true);
                }
            }
        });
        // create info popup
        infoBox = new VBox();
        infoBox.setId("search-info-box");
        infoBox.setFillWidth(true);
        infoBox.setMinWidth(USE_PREF_SIZE);
        infoBox.setPrefWidth(350);
        infoName = new Label();
        infoName.setId("search-info-name");
        infoName.setMinHeight(USE_PREF_SIZE);
        infoName.setPrefHeight(28);
        infoDescription = new Label();
        infoDescription.setId("search-info-description");
        infoDescription.setWrapText(true);
        infoDescription.setPrefWidth(infoBox.getPrefWidth()-24);
        infoBox.getChildren().addAll(infoName,infoDescription);
        extraInfoPopup.getContent().add(infoBox);
        // hide info popup when context menu is hidden
        contextMenu.setOnHidden(new EventHandler() {
            @Override public void handle(WindowEvent windowEvent) {
                extraInfoPopup.hide();
            }
        });
    }
    
    private void showError(String message) {
        searchErrorTooltip.setText(message);
        if (searchErrorTooltipHidder != null) searchErrorTooltipHidder.stop();
        if (message != null) {
            Point2D toolTipPos = textBox.localToScene(0, textBox.getLayoutBounds().getHeight());
            double x = toolTipPos.getX() + textBox.getScene().getX() + textBox.getScene().getWindow().getX();
            double y = toolTipPos.getY() + textBox.getScene().getY() + textBox.getScene().getWindow().getY();
            searchErrorTooltip.show( textBox.getScene().getWindow(),x, y);
            searchErrorTooltipHidder = new Timeline();
            searchErrorTooltipHidder.getKeyFrames().add( 
                new KeyFrame(Duration.seconds(3), 
                    new EventHandler() {
                        public void handle(ActionEvent t) {
                            searchErrorTooltip.hide();
                            searchErrorTooltip.setText(null);
                        }
                    }
                )
            );
            searchErrorTooltipHidder.play();
        } else {
            searchErrorTooltip.hide();
        }
    }

    private void populateMenu(Map> results) {
        contextMenu.getItems().clear();
        for (Map.Entry> entry : results.entrySet()) {
            boolean first = true;
            for(SearchResult result: entry.getValue()) {
                final SearchResult sr = result;
                final HBox hBox = new HBox();
                hBox.setFillHeight(true);
                Label itemLabel = new Label(result.getName());
                itemLabel.getStyleClass().add("item-label");
                if (first) {
                    first = false;
                    Label groupLabel = new Label(result.getDocumentType().getPluralDisplayName());
                    groupLabel.getStyleClass().add("group-label");
                    groupLabel.setAlignment(Pos.CENTER_RIGHT);
                    groupLabel.setMinWidth(USE_PREF_SIZE);
                    groupLabel.setPrefWidth(70);
                    hBox.getChildren().addAll(groupLabel,itemLabel);
                } else {
                    Region spacer = new Region();
                    spacer.setMinWidth(USE_PREF_SIZE);
                    spacer.setPrefWidth(70);
                    hBox.getChildren().addAll(spacer,itemLabel);
                }
                // create a special node for hiding/showing popup content
                final Region popRegion = new Region();
                popRegion.getStyleClass().add("search-menu-item-popup-region");
                popRegion.setPrefSize(10, 10);
                hBox.getChildren().add(popRegion);
                final String name = (result.getDocumentType() == DocumentType.SAMPLE) ? result.getName() :
                        result.getPackageName()+
                                ((result.getClassName() != null) ? "."+result.getClassName() : "") +
                                ((result.getName() != null) ? "."+result.getName() : "");
                final String shortDescription = (result.getShortDescription().length() == 160) ? result.getShortDescription() +"..." : result.getShortDescription();
                popRegion.opacityProperty().addListener(new ChangeListener() {
                    @Override public void changed(ObservableValue observable, Number oldValue, Number newValue) {
                        Platform.runLater( new Runnable() { // TODO runLater used here as a workaround for RT-14396
                            @Override public void run() {
                                if (popRegion.getOpacity() == 1) {
                                    infoName.setText(name);
                                    infoDescription.setText(shortDescription);
                                    Point2D hBoxPos = hBox.localToScene(0, 0);
                                    extraInfoPopup.show(getScene().getWindow(),
                                        hBoxPos.getX() + contextMenu.getScene().getX() + contextMenu.getX() - infoBox.getPrefWidth() - 10,
                                        hBoxPos.getY() + contextMenu.getScene().getY() + contextMenu.getY() - 27
                                    );
                                }
                            }
                        });
                    }
                });
                // create menu item
                CustomMenuItem menuItem = new CustomMenuItem(hBox, true);
                menuItem.getStyleClass().add("search-menu-item");
                contextMenu.getItems().add(menuItem);
                // handle item selection
                menuItem.setOnAction(new EventHandler() {
                    @Override public void handle(ActionEvent actionEvent) {
                        ///System.out.println("SearchBox.handle menuItem.setOnAction");
                        Ensemble2.getEnsemble2().goToPage(sr.getEnsemblePath(),true);
                    }
                });
            }
        }
    }

    @Override protected void layoutChildren() {
        textBox.resize(getWidth(),getHeight());
        clearButton.resizeRelocate(getWidth()-18,6,12,13);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy