001/*
002 * ============================================================================
003 *  Copyright © 2002-2025 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.util;
019
020import static javafx.scene.control.ButtonType.OK;
021import static org.apiguardian.api.API.Status.STABLE;
022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
023
024import org.apiguardian.api.API;
025import org.tquadrat.foundation.annotation.ClassVersion;
026import javafx.event.Event;
027import javafx.event.EventHandler;
028import javafx.event.EventType;
029import javafx.scene.Node;
030import javafx.scene.control.Alert;
031import javafx.scene.control.Alert.AlertType;
032import javafx.scene.control.ButtonType;
033import javafx.scene.control.DialogEvent;
034import javafx.scene.control.DialogPane;
035import javafx.stage.Modality;
036import javafx.stage.StageStyle;
037import javafx.stage.Window;
038import javafx.util.Callback;
039
040/**
041 *  This class provides a fluent API to build an
042 *  {@link Alert}.
043 *
044 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
045 *  @version $Id: AlertBuilder.java 1151 2025-10-01 21:32:15Z tquadrat $
046 *  @since 0.4.1
047 *
048 *  @UMLGraph.link
049 */
050@SuppressWarnings( "ClassWithTooManyMethods" )
051@ClassVersion( sourceVersion = "$Id: AlertBuilder.java 1151 2025-10-01 21:32:15Z tquadrat $" )
052@API( status = STABLE, since = "0.4.1" )
053public class AlertBuilder
054{
055        /*------------*\
056    ====** Attributes **=======================================================
057        \*------------*/
058    /**
059     *  The alert instance to build.
060     */
061    private final Alert m_Alert;
062
063    /**
064     *  Flag that prevents the re-use of the builder.
065     */
066    private boolean m_IsBuilt;
067
068        /*--------------*\
069    ====** Constructors **=====================================================
070        \*--------------*/
071    /**
072     *  Creates a new instance of {@code AlertBuilder}.
073     *
074     *  @param  type    The type of the new alert.
075     */
076    public AlertBuilder( final AlertType type )
077    {
078        m_Alert = new Alert( requireNonNullArgument( type, "type" ) );
079        m_IsBuilt = false;
080    }   //  AlertBuilder()
081
082    /**
083     *  Creates a new instance of {@code AlertBuilder}.
084     *
085     *  @param  type    The type of the new alert.
086     *  @param  owner   The parent window for the alert.
087     */
088    public AlertBuilder( final AlertType type, final Window owner )
089    {
090        this( type );
091        setOwner( owner );
092    }   //  AlertBuilder()
093
094        /*---------*\
095    ====** Methods **==========================================================
096        \*---------*/
097    /**
098     *  <p>{@summary Registers an event filter for the new alert.}</p>
099     *
100     *  @param  <E> The event class of the filer.
101     *  @param  eventType   The type of the events received by the filter.
102     *  @param  eventFilter The event filter.
103     *  @return The builder reference.
104     *  @throws IllegalStateException   The method
105     *      {@link #build()}
106     *      was already called on this builder instance.
107     *
108     *  @see Alert#addEventFilter(EventType,EventHandler)
109     */
110    public final <E extends Event> AlertBuilder addEventFilter( final EventType<E> eventType, final EventHandler<? super E> eventFilter ) throws IllegalStateException
111    {
112        checkIsBuilt();
113        m_Alert.addEventFilter( eventType, eventFilter );
114
115        //---* Done *----------------------------------------------------------
116        return this;
117    }   //  addEventFilter()
118
119    /**
120     *  <p>{@summary Registers an even filter for the new alert.}</p>
121     *
122     *  @param  <E> The event class of the filter.
123     *  @param  eventType   The type of the events received by the filter.
124     *  @param  eventHandler    The event handler.
125     *  @return The builder reference.
126     *  @throws IllegalStateException   The method
127     *      {@link #build()}
128     *      was already called on this builder instance.
129     *
130     *  @see Alert#addEventHandler(EventType,EventHandler)
131     */
132    public final <E extends Event> AlertBuilder addEventHandler( final EventType<E> eventType, final EventHandler<? super E> eventHandler ) throws IllegalStateException
133    {
134        checkIsBuilt();
135        m_Alert.addEventHandler( eventType, eventHandler );
136
137        //---* Done *----------------------------------------------------------
138        return this;
139    }   //  addEventFilter()
140
141    /**
142     *  Returns the alert instance.
143     *
144     *  @return The alert.
145     *  @throws IllegalStateException   The method {@code build()} was already
146     *      called previously on this builder instance.
147     */
148    public final Alert build() throws IllegalStateException
149    {
150        checkIsBuilt();
151        m_IsBuilt = true;
152
153        //---* Done *----------------------------------------------------------
154        return m_Alert;
155    }   //  build()
156
157    /**
158     *  Throws an
159     *  {@link IllegalStateException}
160     *  when
161     *  {@link #m_IsBuilt}
162     *  is {@code true}.
163     *
164     *  @throws IllegalStateException
165     *      {@link #m_IsBuilt}
166     *      is {@code true}.
167     */
168    private final void checkIsBuilt() throws IllegalStateException
169    {
170        if( m_IsBuilt ) throw new IllegalStateException( "Illegal Builder state" );
171    }   //  checkIsBuilt()
172
173    /**
174     *  Builds the alert, calls
175     *  {@link Alert#showAndWait() showAndWait()}
176     *  on it and returns the result.
177     *
178     *  @return {@code true} if the alert was terminated with the
179     *      {@linkplain ButtonType#OK Ok button}, {@code false} in any other
180     *      case.
181     *  @throws IllegalStateException   The method
182     *      {@link #build()}
183     *      was already called on this builder instance.
184     *
185     *  @since 0.4.2
186     */
187    @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" )
188    @API( status = STABLE, since = "0.4.1" )
189    public final boolean execute() throws IllegalStateException
190    {
191        final var retValue = build().showAndWait()
192            .filter( result -> result == OK )
193            .isPresent();
194
195        //---* Done *----------------------------------------------------------
196        return retValue;
197    }   //  execute()
198
199    /**
200     *  <p>{@summary Sets the content text for the new alert.}</p>
201     *  <p>This operation can be called repeatedly; each consecutive call will
202     *  overwrite the value set by the previous one.</p>
203     *
204     *  @param  s   The content text; can be {@code null}.
205     *  @return The builder reference.
206     *  @throws IllegalStateException   The method
207     *      {@link #build()}
208     *      was already called on this builder instance.
209     *
210     *  @see Alert#setContentText(String)
211     */
212    public final AlertBuilder setContentText( final String s ) throws IllegalStateException
213    {
214        checkIsBuilt();
215        m_Alert.setContentText( s );
216
217        //---* Done *----------------------------------------------------------
218        return this;
219    }   //  setContentText()
220
221    /**
222     *  <p>{@summary Sets the
223     *  {@link DialogPane }
224     *  for the new alert.}</p>
225     *  <p>This operation can be called repeatedly; each consecutive call will
226     *  overwrite the value set by the previous one.</p>
227     *
228     *  @param  dialogPane   The dialog pane; can be {@code null}.
229     *  @return The builder reference.
230     *  @throws IllegalStateException   The method
231     *      {@link #build()}
232     *      was already called on this builder instance.
233     *
234     *  @see Alert#setDialogPane(DialogPane)
235     */
236    public final AlertBuilder setDialogPane( final DialogPane dialogPane ) throws IllegalStateException
237    {
238        checkIsBuilt();
239        m_Alert.setDialogPane( dialogPane );
240
241        //---* Done *----------------------------------------------------------
242        return this;
243    }   //  setDialogPane()
244
245    /**
246     *  <p>{@summary Sets the window dimensions for the new alert.}</p>
247     *  <p>This operation can be called repeatedly; each consecutive call will
248     *  overwrite the values set by the previous one.</p>
249     *
250     *  @param  width   The window width.
251     *  @param  height  The window height.
252     *  @return The builder reference.
253     *  @throws IllegalStateException   The method
254     *      {@link #build()}
255     *      was already called on this builder instance.
256     *
257     *  @see Alert#setHeight(double)
258     *  @see Alert#setWidth(double)
259     */
260    public final AlertBuilder setDimensions( final double width, final double height ) throws IllegalStateException
261    {
262        setHeight( height );
263        setWidth( width );
264
265        //---* Done *----------------------------------------------------------
266        return this;
267    }   //  setDimensions()
268
269    /**
270     *  <p>{@summary Sets the graphics for the new alert.}</p>
271     *  <p>This operation can be called repeatedly; each consecutive call will
272     *  overwrite the value set by the previous one.</p>
273     *
274     *  @param  node    The graphics; can be {@code null}.
275     *  @return The builder reference.
276     *  @throws IllegalStateException   The method
277     *      {@link #build()}
278     *      was already called on this builder instance.
279     *
280     *  @see Alert#setGraphic(Node)
281     */
282    public final AlertBuilder setGraphic( final Node node ) throws IllegalStateException
283    {
284        checkIsBuilt();
285        m_Alert.setGraphic( node );
286
287        //---* Done *----------------------------------------------------------
288        return this;
289    }   //  setGraphic()
290
291    /**
292     *  <p>{@summary Sets the header text for the new alert.}</p>
293     *  <p>This operation can be called repeatedly; each consecutive call will
294     *  overwrite the value set by the previous one.</p>
295     *
296     *  @param  s   The text for the header; can be {@code null}.
297     *  @return The builder reference.
298     *  @throws IllegalStateException   The method
299     *      {@link #build()}
300     *      was already called on this builder instance.
301     *
302     *  @see Alert#setHeaderText(String)
303     */
304    public final AlertBuilder setHeaderText( final String s ) throws IllegalStateException
305    {
306        checkIsBuilt();
307        m_Alert.setHeaderText( s );
308
309        //---* Done *----------------------------------------------------------
310        return this;
311    }   //  setHeaderText()
312
313    /**
314     *  <p>{@summary Sets the window height for the new alert.}</p>
315     *  <p>This operation can be called repeatedly; each consecutive call will
316     *  overwrite the value set by the previous one.</p>
317     *
318     *  @param  height  The window height.
319     *  @return The builder reference.
320     *  @throws IllegalStateException   The method
321     *      {@link #build()}
322     *      was already called on this builder instance.
323     *
324     *  @see Alert#setHeight(double)
325     */
326    @SuppressWarnings( "UnusedReturnValue" )
327    public final AlertBuilder setHeight( final double height ) throws IllegalStateException
328    {
329        checkIsBuilt();
330        m_Alert.setHeight( height );
331
332        //---* Done *----------------------------------------------------------
333        return this;
334    }   //  setHeight()
335
336    /**
337     *  <p>{@summary Sets the
338     *  {@link Modality }
339     *  for the new alert.}</p>
340     *
341     *  @param  modality    The modality.
342     *  @return The builder reference.
343     *  @throws IllegalStateException   The method
344     *      {@link #build()}
345     *      was already called on this builder instance.
346     *
347     *  @see Alert#initModality(Modality)
348     */
349    public final AlertBuilder setModality( final Modality modality ) throws IllegalStateException
350    {
351        checkIsBuilt();
352        m_Alert.initModality( requireNonNullArgument( modality, "modality" ) );
353
354        //---* Done *----------------------------------------------------------
355        return this;
356    }   //  setModality()
357
358    /**
359     *  <p>{@summary Sets the 'OnCloseRequest' event handler for the new
360     *  alert.}</p>
361     *  <p>This operation can be called repeatedly; each consecutive call will
362     *  overwrite the value set by the previous one.</p>
363     *
364     *  @param  eventHandler    The event handler
365     *  @return The builder reference.
366     *  @throws IllegalStateException   The method
367     *      {@link #build()}
368     *      was already called on this builder instance.
369     *
370     *  @see Alert#setOnCloseRequest(EventHandler)
371     */
372    public final AlertBuilder setOnCloseRequest( final EventHandler<DialogEvent> eventHandler ) throws IllegalStateException
373    {
374        checkIsBuilt();
375        m_Alert.setOnCloseRequest( eventHandler );
376
377        //---* Done *----------------------------------------------------------
378        return this;
379    }   //  setOnCloseRequest()
380
381    /**
382     *  <p>{@summary Sets the 'OnHidden' event handler for the new alert.}</p>
383     *  <p>This operation can be called repeatedly; each consecutive call will
384     *  overwrite the value set by the previous one.</p>
385     *
386     *  @param  eventHandler    The event handler
387     *  @return The builder reference.
388     *  @throws IllegalStateException   The method
389     *      {@link #build()}
390     *      was already called on this builder instance.
391     *
392     *  @see Alert#setOnHidden(EventHandler)
393     */
394    public final AlertBuilder setOnHidden( final EventHandler<DialogEvent> eventHandler ) throws IllegalStateException
395    {
396        checkIsBuilt();
397        m_Alert.setOnHidden( eventHandler );
398
399        //---* Done *----------------------------------------------------------
400        return this;
401    }   //  setHidden()}
402
403    /**
404     *  <p>{@summary Sets the 'OnHiding' event handler for the new alert.}</p>
405     *  <p>This operation can be called repeatedly; each consecutive call will
406     *  overwrite the value set by the previous one.</p>
407     *
408     *  @param  eventHandler    The event handler
409     *  @return The builder reference.
410     *  @throws IllegalStateException   The method
411     *      {@link #build()}
412     *      was already called on this builder instance.
413     *
414     *  @see Alert#setOnHiding(EventHandler)
415     */
416    public final AlertBuilder setOnHiding( final EventHandler<DialogEvent> eventHandler ) throws IllegalStateException
417    {
418        checkIsBuilt();
419        m_Alert.setOnHiding( eventHandler );
420
421        //---* Done *----------------------------------------------------------
422        return this;
423    }   //  setHiding()}
424
425    /**
426     *  <p>{@summary Sets the 'OnShowing' event handler for the new alert.}</p>
427     *  <p>This operation can be called repeatedly; each consecutive call will
428     *  overwrite the value set by the previous one.</p>
429     *
430     *  @param  eventHandler    The event handler
431     *  @return The builder reference.
432     *  @throws IllegalStateException   The method
433     *      {@link #build()}
434     *      was already called on this builder instance.
435     *
436     *  @see Alert#setOnShowing(EventHandler)
437     */
438    public final AlertBuilder setOnShowing( final EventHandler<DialogEvent> eventHandler ) throws IllegalStateException
439    {
440        checkIsBuilt();
441        m_Alert.setOnShowing( eventHandler );
442
443        //---* Done *----------------------------------------------------------
444        return this;
445    }   //  setOnShowing()
446
447    /**
448     *  <p>{@summary Sets the 'OnShown' event handler for the new alert.}</p>
449     *  <p>This operation can be called repeatedly; each consecutive call will
450     *  overwrite the value set by the previous one.</p>
451     *
452     *  @param  eventHandler    The event handler
453     *  @return The builder reference.
454     *  @throws IllegalStateException   The method
455     *      {@link #build()}
456     *      was already called on this builder instance.
457     *
458     *  @see Alert#setOnShown(EventHandler)
459     */
460    public final AlertBuilder setOnShown( final EventHandler<DialogEvent> eventHandler ) throws IllegalStateException
461    {
462        checkIsBuilt();
463        m_Alert.setOnShown( eventHandler );
464
465        //---* Done *----------------------------------------------------------
466        return this;
467    }   //  setOnShown()
468
469    /**
470     *  Sets the parent window for the new alert.
471     *
472     *  @param  owner   The parent window.
473     *  @return The builder reference.
474     *  @throws IllegalStateException   The method
475     *      {@link #build()}
476     *      was already called on this builder instance.
477     *
478     *  @see Alert#initOwner(Window)
479     *
480     *  @since 0.4.2
481     */
482    @API( status = STABLE, since = "0.4.1" )
483    @SuppressWarnings( "UnusedReturnValue" )
484    public final AlertBuilder setOwner( final Window owner ) throws IllegalStateException
485    {
486        checkIsBuilt();
487        m_Alert.initOwner( owner );
488
489        //---* Done *----------------------------------------------------------
490        return this;
491    }   //  setOwner()
492
493    /**
494     *  <p>{@summary Sets the position for the new alert.}</p>
495     *  <p>This operation can be called repeatedly; each consecutive call will
496     *  overwrite the values set by the previous one.</p>
497     *
498     *  @param  x   The x position for the alert window.
499     *  @param  y   The y position for the alert window.
500     *  @return The builder reference.
501     *  @throws IllegalStateException   The method
502     *      {@link #build()}
503     *      was already called on this builder instance.
504     *
505     *  @see Alert#setX(double)
506     *  @see Alert#setY(double)
507     */
508    public final AlertBuilder setPos( final double x, final double y ) throws IllegalStateException
509    {
510        setX( x );
511        setY( y );
512
513        //---* Done *----------------------------------------------------------
514        return this;
515    }   //  setPos()
516
517    /**
518     *  <p>{@summary Sets the flag that indicates whether the window for the
519     *  new alert can be resized.}</p>
520     *  <p>This operation can be called repeatedly; each consecutive call will
521     *  overwrite the value set by the previous one.</p>
522     *
523     *  @param  flag    {@code true} for a resizeable window, _false for a
524     *      window with fixed sizes.
525     *  @return The builder reference.
526     *  @throws IllegalStateException   The method
527     *      {@link #build()}
528     *      was already called on this builder instance.
529     *
530     *  @see Alert#setResizable(boolean)
531     */
532    public final AlertBuilder setResizable( final boolean flag ) throws IllegalStateException
533    {
534        checkIsBuilt();
535        m_Alert.setResizable( flag );
536
537        //---* Done *----------------------------------------------------------
538        return this;
539    }   //  setResizable()
540
541    /**
542     *  <p>{@summary Sets the result converter for the new alert.}</p>
543     *  <p>This operation can be called repeatedly; each consecutive call will
544     *  overwrite the value set by the previous one.</p>
545     *  <p>Setting a result converter may have an impact on the behaviour of
546     *  {@link #execute()}.</p>
547     *
548     *  @param  callback    The result converter; can be {@code null}.
549     *  @return The builder reference.
550     *  @throws IllegalStateException   The method
551     *      {@link #build()}
552     *      was already called on this builder instance.
553     *
554     *  @see Alert#setResultConverter(Callback)
555     */
556    public final AlertBuilder setResultConverter( final Callback<ButtonType,ButtonType> callback ) throws IllegalStateException
557    {
558        checkIsBuilt();
559        m_Alert.setResultConverter( callback );
560
561        //---* Done *----------------------------------------------------------
562        return this;
563    }   //  setResultConverter()
564
565    /**
566     *  <p>{@summary Sets the
567     *  {@linkplain StageStyle style}
568     *  for the new alert.}</p>
569     *
570     *  @param  style   The style.
571     *  @return The builder reference.
572     *  @throws IllegalStateException   The method
573     *      {@link #build()}
574     *      was already called on this builder instance.
575     *
576     *  @see Alert#initStyle(StageStyle)
577     */
578    public final AlertBuilder setStyle( final StageStyle style ) throws IllegalStateException
579    {
580        checkIsBuilt();
581        m_Alert.initStyle( requireNonNullArgument( style, "style" ) );
582
583        //---* Done *----------------------------------------------------------
584        return this;
585    }   //  setStyle()
586
587    /**
588     *  <p>{@summary Sets the window title for the new alert.}</p>
589     *  <p>This operation can be called repeatedly; each consecutive call will
590     *  overwrite the value set by the previous one.</p>
591     *
592     *  @param  s   The window title; can be {@code null}.
593     *  @return The builder reference.
594     *  @throws IllegalStateException   The method
595     *      {@link #build()}
596     *      was already called on this builder instance.
597     *
598     *  @see Alert#setTitle(String)
599     */
600    public final AlertBuilder setTitle( final String s ) throws IllegalStateException
601    {
602        checkIsBuilt();
603        m_Alert.setTitle( s );
604
605        //---* Done *----------------------------------------------------------
606        return this;
607    }   //  setTitle()
608
609    /**
610     *  <p>{@summary Sets the window width for the new alert.}</p>
611     *  <p>This operation can be called repeatedly; each consecutive call will
612     *  overwrite the value set by the previous one.</p>
613     *
614     *  @param  width   The window width.
615     *  @return The builder reference.
616     *  @throws IllegalStateException   The method
617     *      {@link #build()}
618     *      was already called on this builder instance.
619     *
620     *  @see Alert#setWidth(double)
621     */
622    @SuppressWarnings( "UnusedReturnValue" )
623    public final AlertBuilder setWidth( final double width ) throws IllegalStateException
624    {
625        checkIsBuilt();
626        m_Alert.setWidth( width );
627
628        //---* Done *----------------------------------------------------------
629        return this;
630    }   //  setWidth()
631
632    /**
633     *  <p>{@summary Sets the x position for the new alert.}</p>
634     *  <p>This operation can be called repeatedly; each consecutive call will
635     *  overwrite the value set by the previous one.</p>
636     *
637     *  @param  x   The x position.
638     *  @return The builder reference.
639     *  @throws IllegalStateException   The method
640     *      {@link #build()}
641     *      was already called on this builder instance.
642     *
643     *  @see Alert#setX(double)
644     */
645    @SuppressWarnings( "UnusedReturnValue" )
646    public final AlertBuilder setX( final double x ) throws IllegalStateException
647    {
648        checkIsBuilt();
649        m_Alert.setX( x );
650
651        //---* Done *----------------------------------------------------------
652        return this;
653    }   //  setX()
654
655    /**
656     *  <p>{@summary Sets the y position for the new alert.}</p>
657     *  <p>This operation can be called repeatedly; each consecutive call will
658     *  overwrite the value set by the previous one.</p>
659     *
660     *  @param  y   The y position.
661     *  @return The builder reference.
662     *  @throws IllegalStateException   The method
663     *      {@link #build()}
664     *      was already called on this builder instance.
665     *
666     *  @see Alert#setX(double)
667     */
668    @SuppressWarnings( "UnusedReturnValue" )
669    public final AlertBuilder setY( final double y ) throws IllegalStateException
670    {
671        checkIsBuilt();
672        m_Alert.setY( y );
673
674        //---* Done *----------------------------------------------------------
675        return this;
676    }   //  setY()
677}
678//  class AlertBuilder
679
680/*
681 *  End of File
682 */