001/*
002 * ============================================================================
003 *  Copyright © 2002-2024 by Thomas Thrien.
004 *  All Rights Reserved.
005 * ============================================================================
006 *  Licensed to the public under the agreements of the GNU Lesser General Public
007 *  License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *       http://www.gnu.org/licenses/lgpl.html
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 *  License for the specific language governing permissions and limitations
015 *  under the License.
016 */
017
018package org.tquadrat.foundation.fx;
019
020import static org.apiguardian.api.API.Status.STABLE;
021import static org.tquadrat.foundation.lang.Objects.isNull;
022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
023
024import org.apiguardian.api.API;
025import org.tquadrat.foundation.annotation.ClassVersion;
026import org.tquadrat.foundation.annotation.UtilityClass;
027import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError;
028import javafx.event.ActionEvent;
029import javafx.scene.control.Button;
030import javafx.scene.control.MenuItem;
031import javafx.stage.Window;
032
033/**
034 *  Some useful utility function for the work with JavaFX.
035 *
036 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
037 *  @version $Id: FXUtils.java 1134 2024-05-20 16:53:16Z tquadrat $
038 *  @since 0.4.2
039 *
040 *  @UMLGraph.link
041 */
042@ClassVersion( sourceVersion = "$Id: FXUtils.java 1134 2024-05-20 16:53:16Z tquadrat $" )
043@API( status = STABLE, since = "0.4.2" )
044@UtilityClass
045public final class FXUtils
046{
047        /*---------------*\
048    ====** Inner Classes **====================================================
049        \*---------------*/
050
051        /*-----------*\
052    ====** Constants **========================================================
053        \*-----------*/
054
055        /*------------*\
056    ====** Attributes **=======================================================
057        \*------------*/
058
059        /*------------------------*\
060    ====** Static Initialisations **===========================================
061        \*------------------------*/
062
063        /*--------------*\
064    ====** Constructors **=====================================================
065        \*--------------*/
066    /**
067     *  No instance allowed for this class!
068     */
069    private FXUtils() { throw new PrivateConstructorForStaticClassCalledError( FXUtils.class ); }
070
071        /*---------*\
072    ====** Methods **==========================================================
073        \*---------*/
074    /**
075     *  <p>{@summary Clamps the given value to be strictly between the
076     *  {@code min} and {@code max} values.}</p>
077     *  <p>Basically, this method does the same as
078     *  {@link Math#clamp(double, double, double)},
079     *  only the sequence of the arguments is different.</p>
080     *
081     *  @param  min The lower border.
082     *  @param  value   The value.
083     *  @param  max The upper border.
084     *  @return The value if it is greater than {@code min} and less than
085     *      {@code max}, {@code min}, when it is less than {@code min}, or
086     *      {@code max} when it is greater than that.
087     *  @throws IllegalArgumentException {@code min} is greater than {@code max}.
088     *
089     *  @since 0.4.6
090     */
091    @API( status = STABLE, since = "0.4.6" )
092    public static final double clamp( final double min, final double value, final double max ) throws IllegalArgumentException
093    {
094        final var retValue = Math.clamp( value, min, max );
095
096        //---* Done *----------------------------------------------------------
097        return retValue;
098    }   //  clamp()
099
100    /**
101     *  <p>{@summary Returns either {@code less} or {@code more} depending on
102     *  which one is closer to {@code value}.} If {@code value} is perfectly
103     *  between them, then either may be returned.</p>
104     *
105     *  @param  less    The lower value.
106     *  @param  value   The reference value.
107     *  @param  more    The upper value.
108     *  @return The value that is closer to the reference.
109     *
110     *  @since 0.4.6
111     */
112    @API( status = STABLE, since = "0.4.6" )
113    public static final double nearest( final double less, final double value, final double more )
114    {
115        final var lessDiff = value - less;
116        final var moreDiff = more - value;
117        final var retValue = lessDiff < moreDiff ? less : more;
118
119        //---* Done *----------------------------------------------------------
120        return retValue;
121    }   //  nearest()
122
123    /**
124     *  Retrieves the window owning the
125     *  {@link Button}
126     *  that fired the given
127     *  {@link ActionEvent}.
128     *
129     *  @param  event   The action event.
130     *  @return The owning window.
131     *  @throws IllegalArgumentException    The event was not fired by a
132     *      {@code Button}.
133     */
134    public static final Window retrieveOwner( final ActionEvent event ) throws IllegalArgumentException
135    {
136        final var retValue = switch( requireNonNullArgument( event, "event" ).getSource() )
137        {
138            case null -> throw new IllegalArgumentException( "Event source is null" );
139
140            /*
141             * When the triggering event originates from a button, that button
142             * is part of a scene (perhaps defined by an FXML file or created
143             * from scratch). So we just walk up the scenes to the root, and
144             * from there we take the Window that owns that button. That is
145             * quite often a Stage.
146             */
147            case final Button button -> button.getScene()
148                .getRoot()
149                .getScene()
150                .getWindow();
151
152            /*
153             * When the event is triggered from a menu item, this is either
154             * part of a menu or a context menu. In both cases it has the same
155             * path to the window that owns it.
156             */
157            case final MenuItem menuItem ->
158            {
159                var resultItem = menuItem.getParentPopup();
160                var currentItem = menuItem;
161                while( isNull( resultItem ) )
162                {
163                    currentItem = currentItem.getParentMenu();
164                    resultItem = currentItem.getParentPopup();
165                }
166                yield resultItem.getOwnerWindow();
167            }
168
169            default -> throw new IllegalArgumentException( "Inappropriate event source" );
170        };
171
172        //---* Done *----------------------------------------------------------
173        return retValue;
174    }   //  retrieveOwner()
175}
176//  class FXUtils
177
178/*
179 *  End of File
180 */