2525import java .util .concurrent .TimeoutException ;
2626import java .util .function .Consumer ;
2727import java .util .logging .Level ;
28+ import java .util .stream .Collectors ;
2829
29- import org .csstudio .display .builder .model .*;
30+ import javafx .geometry .BoundingBox ;
31+ import javafx .geometry .Bounds ;
32+ import javafx .geometry .Insets ;
33+ import javafx .geometry .Orientation ;
34+ import javafx .geometry .Point2D ;
35+ import javafx .scene .control .Alert ;
36+ import javafx .scene .control .ButtonType ;
37+ import javafx .scene .control .ChoiceDialog ;
38+ import javafx .scene .control .ScrollBar ;
39+ import javafx .scene .control .ScrollPane ;
40+ import org .csstudio .display .builder .model .DisplayModel ;
41+ import org .csstudio .display .builder .model .UntypedWidgetPropertyListener ;
42+ import org .csstudio .display .builder .model .Widget ;
43+ import org .csstudio .display .builder .model .WidgetPropertyListener ;
3044import org .csstudio .display .builder .model .properties .PredefinedColorMaps ;
3145import org .csstudio .display .builder .model .properties .WidgetColor ;
3246import org .csstudio .display .builder .representation .ToolkitRepresentation ;
4761import javafx .collections .ObservableList ;
4862import javafx .embed .swing .SwingFXUtils ;
4963import javafx .event .EventHandler ;
50- import javafx .geometry .Bounds ;
51- import javafx .geometry .Insets ;
52- import javafx .geometry .Point2D ;
5364import javafx .scene .Cursor ;
5465import javafx .scene .Group ;
5566import javafx .scene .Node ;
5667import javafx .scene .Parent ;
5768import javafx .scene .Scene ;
58- import javafx .scene .control .Alert ;
59- import javafx .scene .control .ButtonType ;
60- import javafx .scene .control .ChoiceDialog ;
61- import javafx .scene .control .ScrollPane ;
6269import javafx .scene .image .WritableImage ;
6370import javafx .scene .input .MouseEvent ;
6471import javafx .scene .input .ScrollEvent ;
@@ -452,46 +459,135 @@ else if (level_spec.equalsIgnoreCase(Messages.Zoom_Height))
452459 * @param zoom Zoom level: 1.0 for 100%, 0.5 for 50%, ZOOM_ALL, ZOOM_WIDTH, ZOOM_HEIGHT
453460 * @return Zoom level actually used
454461 */
455- private double setZoom (double zoom )
462+ private double setZoom (final double zoom )
456463 {
464+ final double zoom_to_set ;
457465 if (zoom <= 0.0 )
458- { // Determine zoom to fit outline of display into available space
459- final Bounds available = model_root .getLayoutBounds ();
460- final Bounds outline = widget_pane .getLayoutBounds ();
461-
462- // 'outline' will wrap the actual widgets when the display
463- // is larger than the available viewport.
464- // So it can be used to zoom 'out'.
465- // But when the viewport is much larger than the widget,
466- // the JavaFX outline grows to fill the viewport,
467- // so falling back to the self-declared model width and height
468- // to zoom 'in'.
469- // This requires displays to be created with
470- // correct width/height properties.
471- final double zoom_x , zoom_y ;
472- if (outline .getWidth () > available .getWidth ())
473- zoom_x = available .getWidth () / outline .getWidth ();
474- else if (model .propWidth ().getValue () > 0 )
475- zoom_x = available .getWidth () / model .propWidth ().getValue ();
476- else
477- zoom_x = 1.0 ;
478-
479- if (outline .getHeight () > available .getHeight ())
480- zoom_y = available .getHeight () / outline .getHeight ();
481- else if (model .propHeight ().getValue () > 0 )
482- zoom_y = available .getHeight () / model .propHeight ().getValue ();
483- else
484- zoom_y = 1.0 ;
485-
486- if (zoom == ZOOM_WIDTH )
487- zoom = zoom_x ;
488- else if (zoom == ZOOM_HEIGHT )
489- zoom = zoom_y ;
490- else // Assume ZOOM_ALL
491- zoom = Math .min (zoom_x , zoom_y );
466+ { // Determine zoom to fit outline of display into available space.
467+ // In order to determine the actual bounds within which an OPI
468+ // can be displayed, model_root.getViewportBounds() is called
469+ // and then the width/height of the scrollbars are added/subtracted
470+ // in order to determine the bounds both _with_ and _without_
471+ // scrollbars being displayed.
472+ boolean hScrollbarVisible = false ;
473+ boolean vScrollbarVisible = false ;
474+ double vScrollbarWidth = 0.0 ;
475+ double hHcrollbarHeight = 0.0 ;
476+ {
477+ List <ScrollBar > scrollbars = model_root .lookupAll (".scroll-bar" ).stream ().filter (node -> node instanceof ScrollBar ).map (node -> (ScrollBar ) node ).collect (Collectors .toUnmodifiableList ());
478+ List <ScrollBar > model_root_scrollbars = scrollbars .stream ().filter (scrollbar -> scrollbar .getParent () == model_root ).collect (Collectors .toUnmodifiableList ());
479+ for (ScrollBar scrollBar : model_root_scrollbars ) {
480+ if (scrollBar .getOrientation () == Orientation .HORIZONTAL ) {
481+ hScrollbarVisible = scrollBar .isVisible ();
482+ hHcrollbarHeight = scrollBar .getLayoutBounds ().getHeight ();
483+ }
484+ else if (scrollBar .isVisible () && scrollBar .getOrientation () == Orientation .VERTICAL ) {
485+ vScrollbarVisible = scrollBar .isVisible ();
486+ vScrollbarWidth = scrollBar .getLayoutBounds ().getWidth ();
487+ }
488+ }
489+ }
490+
491+ final Bounds viewportBounds = model_root .getViewportBounds ();
492+ final BoundingBox layoutBoundsWithoutScrollbars = new BoundingBox (viewportBounds .getMinX (),
493+ viewportBounds .getMinY (),
494+ Math .max (0.0 , viewportBounds .getWidth () + (vScrollbarVisible ? vScrollbarWidth : 0.0 )),
495+ Math .max (0.0 , viewportBounds .getHeight () + (hScrollbarVisible ? hHcrollbarHeight : 0.0 )));
496+ final BoundingBox layoutBoundsWithScrollbars = new BoundingBox (viewportBounds .getMinX (),
497+ viewportBounds .getMinY (),
498+ Math .max (0.0 , viewportBounds .getWidth () - (vScrollbarVisible ? 0.0 : vScrollbarWidth )),
499+ Math .max (0.0 , viewportBounds .getHeight () - (hScrollbarVisible ? 0.0 : hHcrollbarHeight )));
500+
501+ final double zoom_x_with_scrollbars_rounded ;
502+ final double zoom_x_without_scrollbars_rounded ;
503+ final double zoom_y_with_scrollbars_rounded ;
504+ final double zoom_y_without_scrollbars_rounded ;
505+
506+ {
507+ final Bounds outline = widget_pane .getLayoutBounds ();
508+ // 'outline' will wrap the actual widgets when the display
509+ // is larger than the available viewport.
510+ // So it can be used to zoom 'out'.
511+ // But when the viewport is much larger than the widget,
512+ // the JavaFX outline grows to fill the viewport,
513+ // so falling back to the self-declared model width and height
514+ // to zoom 'in'.
515+ // This requires displays to be created with
516+ // correct width/height properties.
517+ final double zoom_x_without_scrollbars , zoom_y_without_scrollbars ;
518+ final double zoom_x_with_scrollbars , zoom_y_with_scrollbars ;
519+ if (outline .getWidth () > layoutBoundsWithScrollbars .getWidth ()) {
520+ zoom_x_without_scrollbars = layoutBoundsWithoutScrollbars .getWidth () / outline .getWidth ();
521+ zoom_x_with_scrollbars = layoutBoundsWithScrollbars .getWidth () / outline .getWidth ();
522+ }
523+ else if (model .propWidth ().getValue () > 0 ) {
524+ zoom_x_without_scrollbars = layoutBoundsWithoutScrollbars .getWidth () / model .propWidth ().getValue ();
525+ zoom_x_with_scrollbars = layoutBoundsWithScrollbars .getWidth () / model .propWidth ().getValue ();
526+ }
527+ else {
528+ zoom_x_without_scrollbars = 1.0 ;
529+ zoom_x_with_scrollbars = 1.0 ;
530+ }
531+
532+ if (outline .getHeight () > layoutBoundsWithScrollbars .getHeight ()) {
533+ zoom_y_without_scrollbars = layoutBoundsWithoutScrollbars .getHeight () / outline .getHeight ();
534+ zoom_y_with_scrollbars = layoutBoundsWithScrollbars .getHeight () / outline .getHeight ();
535+ }
536+ else if (model .propHeight ().getValue () > 0 ) {
537+ zoom_y_without_scrollbars =layoutBoundsWithoutScrollbars .getHeight () / model .propHeight ().getValue ();
538+ zoom_y_with_scrollbars = layoutBoundsWithScrollbars .getHeight () / model .propHeight ().getValue ();
539+ }
540+ else {
541+ zoom_y_without_scrollbars = 1.0 ;
542+ zoom_y_with_scrollbars = 1.0 ;
543+ }
544+
545+ zoom_x_with_scrollbars_rounded = Math .floor (zoom_x_with_scrollbars * 1000.0 ) / 1000.0 ;
546+ zoom_x_without_scrollbars_rounded = Math .floor (zoom_x_without_scrollbars * 1000.0 ) / 1000.0 ;
547+ zoom_y_with_scrollbars_rounded = Math .floor (zoom_y_with_scrollbars * 1000.0 ) / 1000.0 ;
548+ zoom_y_without_scrollbars_rounded = Math .floor (zoom_y_without_scrollbars * 1000.0 ) / 1000.0 ;
549+ }
550+
551+ if (zoom == ZOOM_WIDTH ) {
552+ if (zoom_x_without_scrollbars_rounded * model .propHeight ().getValue () > layoutBoundsWithoutScrollbars .getHeight ()) {
553+ // Setting zoom_to_set to 'zoom_x_without_scrollbars_rounded'
554+ // would result in the horizontal scrollbar being shown.
555+ // Therefore, set zoom_to_set = zoom_x_with_scrollbars_rounded
556+ zoom_to_set = zoom_x_with_scrollbars_rounded ;
557+ }
558+ else {
559+ zoom_to_set = zoom_x_without_scrollbars_rounded ;
560+ }
561+ }
562+ else if (zoom == ZOOM_HEIGHT ) {
563+ if (zoom_y_without_scrollbars_rounded * model .propWidth ().getValue () > layoutBoundsWithoutScrollbars .getWidth ()) {
564+ // Setting zoom_to_set to 'zoom_y_without_scrollbars_rounded'
565+ // would result in the vertical scrollbar being shown.
566+ // Therefore, set zoom_to_set = zoom_y_with_scrollbars_rounded:
567+ zoom_to_set = zoom_y_with_scrollbars_rounded ;
568+ }
569+ else {
570+ zoom_to_set = zoom_y_without_scrollbars_rounded ;
571+ }
572+ }
573+ else {
574+ if (zoom_y_without_scrollbars_rounded * model .propWidth ().getValue () > layoutBoundsWithoutScrollbars .getWidth () ||
575+ zoom_x_without_scrollbars_rounded * model .propHeight ().getValue () > layoutBoundsWithoutScrollbars .getHeight ()) {
576+ // Setting zoom_to_set to 'Math.min(zoom_x_without_scrollbars_rounded, zoom_y_with_scrollbars_rounded)'
577+ // would result in at least either the vertical or the horizontal scrollbar being shown.
578+ // Therefore, set zoom_to_set = Math.min(zoom_x_without_scrollbars_rounded, zoom_y_without_scrollbars_rounded):
579+ zoom_to_set = Math .min (zoom_x_with_scrollbars_rounded , zoom_y_with_scrollbars_rounded ); // Assume ZOOM_ALL
580+ }
581+ else {
582+ zoom_to_set = Math .min (zoom_x_without_scrollbars_rounded , zoom_y_without_scrollbars_rounded ); // Assume ZOOM_ALL
583+ }
584+ }
585+ }
586+ else {
587+ zoom_to_set = zoom ;
492588 }
493589
494- widget_pane .getTransforms ().setAll (new Scale (zoom , zoom ));
590+ widget_pane .getTransforms ().setAll (new Scale (zoom_to_set , zoom_to_set ));
495591 // Appears similar to using this API:
496592 // widget_parent.setScaleX(zoom);
497593 // widget_parent.setScaleY(zoom);
@@ -505,7 +601,7 @@ else if (zoom == ZOOM_HEIGHT)
505601 if (isEditMode ())
506602 updateModelSizeIndicators ();
507603
508- return zoom ;
604+ return zoom_to_set ;
509605 }
510606
511607 /** @return Zoom factor, 1.0 for 1:1 */
0 commit comments