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

com.jfoenix.skins.JFXGenericPickerSkin Maven / Gradle / Ivy

There is a newer version: 9.0.10
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.jfoenix.skins;

import com.jfoenix.adapters.ReflectionHelper;
import com.jfoenix.controls.behavior.JFXGenericPickerBehavior;
import com.sun.javafx.binding.ExpressionHelper;
import com.sun.javafx.event.EventHandlerManager;
import com.sun.javafx.stage.WindowEventDispatcher;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.control.ComboBoxBase;
import javafx.scene.control.PopupControl;
import javafx.scene.control.TextField;
import javafx.scene.control.skin.ComboBoxBaseSkin;
import javafx.scene.control.skin.ComboBoxPopupControl;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Window;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public abstract class JFXGenericPickerSkin extends ComboBoxPopupControl{

    private final EventHandler mouseEnteredEventHandler;
    private final EventHandler mousePressedEventHandler;
    private final EventHandler mouseReleasedEventHandler;
    private final EventHandler mouseExitedEventHandler;

    protected JFXGenericPickerBehavior behavior;

    // reference of the arrow button node in getChildren (not the actual field)
    protected Pane arrowButton;
    protected PopupControl popup;

    public JFXGenericPickerSkin(ComboBoxBase comboBoxBase) {
        super(comboBoxBase);
        behavior = new JFXGenericPickerBehavior(comboBoxBase);

        removeParentFakeFocusListener(comboBoxBase);

        this.mouseEnteredEventHandler = event -> behavior.mouseEntered(event);
        this.mousePressedEventHandler = event -> {
            behavior.mousePressed(event);
            event.consume();
        };
        this.mouseReleasedEventHandler = event -> {
            behavior.mouseReleased(event);
            event.consume();
        };
        this.mouseExitedEventHandler = event -> behavior.mouseExited(event);

        arrowButton = (Pane) getChildren().get(0);

        parentArrowEventHandlerTerminator.accept("mouseEnteredEventHandler", MouseEvent.MOUSE_ENTERED);
        parentArrowEventHandlerTerminator.accept("mousePressedEventHandler", MouseEvent.MOUSE_PRESSED);
        parentArrowEventHandlerTerminator.accept("mouseReleasedEventHandler", MouseEvent.MOUSE_RELEASED);
        parentArrowEventHandlerTerminator.accept("mouseExitedEventHandler", MouseEvent.MOUSE_EXITED);
        this.unregisterChangeListeners(comboBoxBase.editableProperty());

        updateArrowButtonListeners();
        registerChangeListener(comboBoxBase.editableProperty(), obs->{
            updateArrowButtonListeners();
            reflectUpdateDisplayArea();
        });

        removeParentPopupHandlers();

        popup = ReflectionHelper.getFieldContent(ComboBoxPopupControl.class, this, "popup");
    }

    @Override
    public void dispose() {
        super.dispose();
        if (this.behavior != null) {
            this.behavior.dispose();
        }
    }


    /***************************************************************************
     *                                                                         *
     * Reflections internal API                                                *
     *                                                                         *
     **************************************************************************/

    private BiConsumer> parentArrowEventHandlerTerminator = (handlerName, eventType) ->{
        try {
            EventHandler handler = ReflectionHelper.getFieldContent(ComboBoxBaseSkin.class, this, handlerName);
            arrowButton.removeEventHandler(eventType, handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    };

    private void removeParentFakeFocusListener(ComboBoxBase comboBoxBase) {
        // handle FakeFocusField cast exception
        try {
            final ReadOnlyBooleanProperty focusedProperty = comboBoxBase.focusedProperty();
            ExpressionHelper value = ReflectionHelper.getFieldContent(focusedProperty.getClass().getSuperclass(), focusedProperty, "helper");
            ChangeListener[] changeListeners = ReflectionHelper.getFieldContent(value.getClass(), value, "changeListeners");
            // remove parent focus listener to prevent editor class cast exception
            for(int i = changeListeners.length - 1; i > 0; i--) {
                if (changeListeners[i] != null && changeListeners[i].getClass().getName().contains("ComboBoxPopupControl")) {
                    focusedProperty.removeListener(changeListeners[i]);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void removeParentPopupHandlers() {
        try {
            PopupControl popup = ReflectionHelper.invoke(ComboBoxPopupControl.class, this, "getPopup");
            popup.setOnAutoHide(event -> behavior.onAutoHide(popup));
            WindowEventDispatcher dispatcher = ReflectionHelper.invoke(Window.class, popup, "getInternalEventDispatcher");
            Map compositeEventHandlersMap = ReflectionHelper.getFieldContent(EventHandlerManager.class, dispatcher.getEventHandlerManager(), "eventHandlerMap");
            compositeEventHandlersMap.remove(MouseEvent.MOUSE_CLICKED);
//            CompositeEventHandler compositeEventHandler = (CompositeEventHandler) compositeEventHandlersMap.get(MouseEvent.MOUSE_CLICKED);
//            Object obj = fieldConsumer.apply(()->CompositeEventHandler.class.getDeclaredField("firstRecord"),compositeEventHandler);
//            EventHandler handler = (EventHandler) fieldConsumer.apply(() -> obj.getClass().getDeclaredField("eventHandler"), obj);
//            popup.removeEventHandler(MouseEvent.MOUSE_CLICKED, handler);
            popup.addEventHandler(MouseEvent.MOUSE_CLICKED, click-> behavior.onAutoHide(popup));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void updateArrowButtonListeners() {
        if (getSkinnable().isEditable()) {
            arrowButton.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);
            arrowButton.addEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);
            arrowButton.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);
            arrowButton.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);
        } else {
            arrowButton.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredEventHandler);
            arrowButton.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressedEventHandler);
            arrowButton.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleasedEventHandler);
            arrowButton.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedEventHandler);
        }
    }


    /***************************************************************************
     *                                                                         *
     * Reflections internal API for ComboBoxPopupControl                       *
     *                                                                         *
     **************************************************************************/

    private HashMap parentCachedMethods = new HashMap<>();

    Function methodSupplier = name ->{
        if(!parentCachedMethods.containsKey(name)){
            try {
                Method method = ReflectionHelper.getMethod(ComboBoxPopupControl.class, name);
                parentCachedMethods.put(name, method);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return parentCachedMethods.get(name);
    };

    Consumer methodInvoker = method -> {
        try {
            method.invoke(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    };

    Function methodReturnInvoker = method -> {
        try {
            return method.invoke(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    };

    protected void reflectUpdateDisplayArea() {
        methodInvoker.accept(methodSupplier.apply("updateDisplayArea"));
    }

    protected void reflectSetTextFromTextFieldIntoComboBoxValue() {
        methodInvoker.accept(methodSupplier.apply("setTextFromTextFieldIntoComboBoxValue"));
    }

    protected TextField reflectGetEditableInputNode(){
        return (TextField) methodReturnInvoker.apply(methodSupplier.apply("getEditableInputNode"));
    }

    protected void reflectUpdateDisplayNode() {
        methodInvoker.accept(methodSupplier.apply("updateDisplayNode"));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy