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,47 +459,139 @@ 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 zoomToSet ;
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 > modelRootScrollbars = scrollbars .stream ().filter (scrollbar -> scrollbar .getParent () == model_root ).collect (Collectors .toUnmodifiableList ());
479+ for (ScrollBar scrollBar : modelRootScrollbars ) {
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 zoomXWithScrollbarsRounded ;
502+ final double zoomXWithoutScrollbarsRounded ;
503+ final double zoomYWithScrollbarsRounded ;
504+ final double zoomYWithoutScrollbarsRounded ;
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 zoomXWithoutScrollbars , zoomYWithoutScrollbars ;
518+ final double zoomXWithScrollbars , zoomYWithScrollbars ;
519+ if (outline .getWidth () > layoutBoundsWithScrollbars .getWidth ()) {
520+ zoomXWithoutScrollbars = layoutBoundsWithoutScrollbars .getWidth () / outline .getWidth ();
521+ zoomXWithScrollbars = layoutBoundsWithScrollbars .getWidth () / outline .getWidth ();
522+ }
523+ else if (model .propWidth ().getValue () > 0 ) {
524+ zoomXWithoutScrollbars = layoutBoundsWithoutScrollbars .getWidth () / model .propWidth ().getValue ();
525+ zoomXWithScrollbars = layoutBoundsWithScrollbars .getWidth () / model .propWidth ().getValue ();
526+ }
527+ else {
528+ zoomXWithoutScrollbars = 1.0 ;
529+ zoomXWithScrollbars = 1.0 ;
530+ }
531+
532+ if (outline .getHeight () > layoutBoundsWithScrollbars .getHeight ()) {
533+ zoomYWithoutScrollbars = layoutBoundsWithoutScrollbars .getHeight () / outline .getHeight ();
534+ zoomYWithScrollbars = layoutBoundsWithScrollbars .getHeight () / outline .getHeight ();
535+ }
536+ else if (model .propHeight ().getValue () > 0 ) {
537+ zoomYWithoutScrollbars =layoutBoundsWithoutScrollbars .getHeight () / model .propHeight ().getValue ();
538+ zoomYWithScrollbars = layoutBoundsWithScrollbars .getHeight () / model .propHeight ().getValue ();
539+ }
540+ else {
541+ zoomYWithoutScrollbars = 1.0 ;
542+ zoomYWithScrollbars = 1.0 ;
543+ }
544+
545+ // Round down the zoom-level in order to avoid situations where the
546+ // zoom-level is instead being rounded up, causing a scrollbar to
547+ // be displayed when a scrollbar is not needed.
548+ zoomXWithScrollbarsRounded = Math .floor (zoomXWithScrollbars * 1000.0 ) / 1000.0 ;
549+ zoomXWithoutScrollbarsRounded = Math .floor (zoomXWithoutScrollbars * 1000.0 ) / 1000.0 ;
550+ zoomYWithScrollbarsRounded = Math .floor (zoomYWithScrollbars * 1000.0 ) / 1000.0 ;
551+ zoomYWithoutScrollbarsRounded = Math .floor (zoomYWithoutScrollbars * 1000.0 ) / 1000.0 ;
552+ }
553+
554+ if (zoom == ZOOM_WIDTH ) {
555+ if (zoomXWithoutScrollbarsRounded * model .propHeight ().getValue () > layoutBoundsWithoutScrollbars .getHeight ()) {
556+ // Setting zoomToSet to 'zoomXWithoutScrollbarsRounded'
557+ // would result in the horizontal scrollbar being shown.
558+ // Therefore, set zoomToSet = zoomXWithScrollbarsRounded
559+ zoomToSet = zoomXWithScrollbarsRounded ;
560+ }
561+ else {
562+ zoomToSet = zoomXWithoutScrollbarsRounded ;
563+ }
564+ }
565+ else if (zoom == ZOOM_HEIGHT ) {
566+ if (zoomYWithoutScrollbarsRounded * model .propWidth ().getValue () > layoutBoundsWithoutScrollbars .getWidth ()) {
567+ // Setting zoomToSet to 'zoomYWithoutScrollbarsRounded'
568+ // would result in the vertical scrollbar being shown.
569+ // Therefore, set zoomToSet = zoomYWithScrollbarsRounded:
570+ zoomToSet = zoomYWithScrollbarsRounded ;
571+ }
572+ else {
573+ zoomToSet = zoomYWithoutScrollbarsRounded ;
574+ }
575+ }
576+ else {
577+ // Assume ZOOM_ALL
578+ if (zoomYWithoutScrollbarsRounded * model .propWidth ().getValue () > layoutBoundsWithoutScrollbars .getWidth () ||
579+ zoomXWithoutScrollbarsRounded * model .propHeight ().getValue () > layoutBoundsWithoutScrollbars .getHeight ()) {
580+ // Setting zoomToSet to 'Math.min(zoomXWithoutScrollbarsRounded, zoomYWithScrollbarsRounded)'
581+ // would result in at least either the vertical or the horizontal scrollbar being shown.
582+ // Therefore, set zoomToSet = Math.min(zoomXWithoutScrollbarsRounded, zoomYWithoutScrollbarsRounded):
583+ zoomToSet = Math .min (zoomXWithScrollbarsRounded , zoomYWithScrollbarsRounded ); // Assume ZOOM_ALL
584+ }
585+ else {
586+ zoomToSet = Math .min (zoomXWithoutScrollbarsRounded , zoomYWithoutScrollbarsRounded );
587+ }
588+ }
589+ }
590+ else {
591+ zoomToSet = zoom ;
492592 }
493593
494- widget_parent .getTransforms ().setAll (new Scale (zoom , zoom ));
495- widget_pane .getTransforms ().setAll (new Scale (zoom , zoom ));
594+ widget_pane .getTransforms ().setAll (new Scale (zoomToSet , zoomToSet ));
496595 // Appears similar to using this API:
497596 // widget_parent.setScaleX(zoom);
498597 // widget_parent.setScaleY(zoom);
@@ -506,7 +605,7 @@ else if (zoom == ZOOM_HEIGHT)
506605 if (isEditMode ())
507606 updateModelSizeIndicators ();
508607
509- return zoom ;
608+ return zoomToSet ;
510609 }
511610
512611 /** @return Zoom factor, 1.0 for 1:1 */
0 commit comments