/** * Copyright (c) 2014 - 2017 Frank Appel * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Frank Appel - initial API and implementation */ package com.codeaffine.eclipse.swt.widget.scrollable.context; import static com.codeaffine.eclipse.swt.test.util.ShellHelper.createShell; import static com.codeaffine.eclipse.swt.widget.scrollable.TreeHelper.createTree; import static com.codeaffine.eclipse.swt.widget.scrollable.TreeHelper.expandRootLevelItems; import static com.codeaffine.eclipse.swt.widget.scrollable.TreeHelper.expandTopBranch; import static com.codeaffine.eclipse.swt.widget.scrollable.context.AdaptionContext.OVERLAY_OFFSET; import static com.codeaffine.eclipse.swt.widget.scrollable.context.AdaptionContextAssert.assertThat; import static org.assertj.core.api.Assertions.assertThat; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Scrollable; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Tree; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import com.codeaffine.eclipse.swt.test.util.DisplayHelper; import com.codeaffine.eclipse.swt.test.util.SWTIgnoreConditions.CocoaPlatform; import com.codeaffine.eclipse.swt.test.util.SWTIgnoreConditions.GtkPlatform; import com.codeaffine.eclipse.swt.util.ControlReflectionUtil; import com.codeaffine.test.util.junit.ConditionalIgnoreRule; import com.codeaffine.test.util.junit.ConditionalIgnoreRule.ConditionalIgnore; public class AdaptionContextTest { private static final int SELECTION = 5; @Rule public final ConditionalIgnoreRule conditionalIgnoreRule = new ConditionalIgnoreRule(); @Rule public final DisplayHelper displayHelper = new DisplayHelper(); private AdaptionContext<Tree> adaptionContext; private Shell shell; private Tree tree; @Before public void setUp() { shell = createShell( displayHelper, SWT.RESIZE ); tree = createTree( shell, 2, 4 ); adaptionContext = new AdaptionContext<Tree>( tree.getParent(), new ScrollableControl<>( tree ) ); shell.open(); } @Test public void initial() { assertThat( adaptionContext ) .hasAdapter( shell ) .hasScrollable( tree ) .verticalBarIsInvisible() .horizontalBarIsInvisible() .hasPreferredSize( computePreferredTreeSize() ) .hasOffset( new OffsetComputer( new ScrollableControl<>( tree ) ).compute() ) .hasVisibleArea( expectedVisibleArea( tree ) ) .hasHorizontalAdapterSelection( 0 ) .hasBorderWidth( expectedBorderWidth( tree ) ) .isNotScrollableReplacement(); } @Test public void newContext() { AdaptionContext<Tree> context = adaptionContext.newContext(); assertThat( context ) .hasAdapter( shell ) .hasScrollable( tree ) .verticalBarIsInvisible() .horizontalBarIsInvisible() .hasPreferredSize( computePreferredTreeSize() ) .hasOffset( new OffsetComputer( new ScrollableControl<>( tree ) ).compute() ) .hasVisibleArea( expectedVisibleArea( tree ) ) .hasHorizontalAdapterSelection( 0 ) .hasBorderWidth( expectedBorderWidth( tree ) ) .isNotScrollableReplacement(); } @Test public void newContextWithItemHeight() { AdaptionContext<Tree> context = adaptionContext.newContext( tree.getItemHeight() ); assertThat( context ) .hasAdapter( shell ) .hasScrollable( tree ) .verticalBarIsInvisible() .horizontalBarIsInvisible() .hasPreferredSize( computePreferredTreeSize() ) .hasOffset( new OffsetComputer( new ScrollableControl<>( tree ) ).compute() ) .hasVisibleArea( expectedVisibleArea( tree ) ) .hasHorizontalAdapterSelection( 0 ) .hasBorderWidth( expectedBorderWidth( tree ) ) .isNotScrollableReplacement(); } @Test @ConditionalIgnore( condition = CocoaPlatform.class ) public void preferredWidthExceedsVisibleAreaWidth() { shell.setSize( 200, 400 ); expandTopBranch( tree ); waitForGtkRendering(); adaptionContext.updatePreferredSize(); AdaptionContext<Tree> context = adaptionContext.newContext( tree.getItemHeight() ); assertThat( context ) .verticalBarIsInvisible() .horizontalBarIsVisible(); } @Test public void preferredHeightExceedsVisibleAreaHeight() { shell.setSize( 200, 100 ); expandRootLevelItems( tree ); waitForGtkRendering(); adaptionContext.updatePreferredSize(); AdaptionContext<Tree> context = adaptionContext.newContext( tree.getItemHeight() ); assertThat( context ) .verticalBarIsVisible() .hasVerticalBarOffset( expectedVerticalBarOffset() ) .horizontalBarIsInvisible(); } @Test @ConditionalIgnore( condition = CocoaPlatform.class ) public void preferredSizeExceedsVisibleArea() { expandRootLevelItems( tree ); expandTopBranch( tree ); adaptionContext.updatePreferredSize(); AdaptionContext<Tree> context = adaptionContext.newContext( tree.getItemHeight() ); assertThat( context ) .verticalBarIsVisible() .hasVerticalBarOffset( expectedVerticalBarOffset() ) .horizontalBarIsVisible(); } @Test public void preferredSizeIsBuffered() { Point expected = adaptionContext.getPreferredSize(); expandRootLevelItems( tree ); expandTopBranch( tree ); assertThat( adaptionContext ).hasPreferredSize( expected ); } @Test @ConditionalIgnore( condition = CocoaPlatform.class ) public void updatePreferredSize() { Point initialSize = adaptionContext.getPreferredSize(); expandRootLevelItems( tree ); expandTopBranch( tree ); adaptionContext.updatePreferredSize(); assertThat( adaptionContext ).hasNotPreferredSize( initialSize ); assertThat( tree.getHorizontalBar().isVisible() ).isTrue(); } @Test @ConditionalIgnore( condition = CocoaPlatform.class ) public void updatePreferredSizeIfIsOwnerDrawnAndVirtual() { tree.dispose(); reinitWithOwnerDrawnAndVirtualScrollable(); Point initialSize = adaptionContext.getPreferredSize(); expandRootLevelItems( tree ); expandTopBranch( tree ); adaptionContext.updatePreferredSize(); assertThat( adaptionContext ).hasNotPreferredSize( initialSize ); assertThat( tree.getHorizontalBar().isVisible() ).isFalse(); } @Test public void adjustPreferredWidthIfHorizontalBarIsVisible() { shell.setSize( 500, 400 ); expandRootLevelItems( tree ); expandTopBranch( tree ); adaptionContext.updatePreferredSize(); AdaptionContext<Tree> context = createContextWithReparentedScrollable(); context.adjustPreferredWidthIfHorizontalBarIsVisible(); tree.setSize( context.getPreferredSize() ); AdaptionContext<Tree> actual = context.newContext( tree.getItemHeight() ); assertThat( actual ) .verticalBarIsInvisible() .hasVerticalBarOffset( expectedVerticalBarOffset() ) .horizontalBarIsInvisible(); } @Test @ConditionalIgnore( condition = GtkPlatform.class ) // Only for build server, works fine on Ubuntu public void verticalBarVisibilityOnThresholdHeightDependsOnHorizontalBarVisibility() { int thresholdHight = computeThresholdHeight(); shell.setSize( 1000, thresholdHight ); AdaptionContext<Tree> first = adaptionContext.newContext( tree.getItemHeight() ); shell.setSize( 90, thresholdHight ); AdaptionContext<Tree> second = adaptionContext.newContext( tree.getItemHeight() ); assertThat( first ) .verticalBarIsInvisible() .horizontalBarIsInvisible(); assertThat( second ) .verticalBarIsVisible() .horizontalBarIsVisible(); } @Test public void getOriginOfScrollableOrdinates() { AdaptionContext<Tree> actual = adaptionContext.newContext( tree.getItemHeight() ); assertThat( actual ).hasOriginOfScrollableOrdinates( expectedOriginOfScrollableOrdinates( tree ) ); } @Test public void getReconciliation() { AdaptionContext<Tree> context = adaptionContext.newContext( tree.getItemHeight() ); Reconciliation first = adaptionContext.getReconciliation(); Reconciliation second = context.getReconciliation(); assertThat( first ).isNotNull(); assertThat( first ).isSameAs( second ); } @Test public void isScrollableAdaptedWithReplacementFake() { Composite composite = new Composite( tree.getParent(), SWT.NONE ); AdaptionContext<Tree> actual = new AdaptionContext<Tree>( composite, new ScrollableControl<>( tree ) ); assertThat( actual ).isScrollableReplacement(); } @Test public void getHorizontalAdapterWithReplacementFake() { Composite composite = new Composite( tree.getParent(), SWT.H_SCROLL | SWT.V_SCROLL ); composite.getHorizontalBar().setSelection( SELECTION ); AdaptionContext<Tree> actual = new AdaptionContext<Tree>( composite, new ScrollableControl<>( tree ) ); assertThat( actual ).hasHorizontalAdapterSelection( SELECTION ); } @Test public void createContextOnScrollableWithBorder() { Tree scrollable = new Tree( shell, SWT.BORDER ); AdaptionContext<Tree> actual = new AdaptionContext<Tree>( shell, new ScrollableControl<>( scrollable ) ); assertThat( actual ) .hasOriginOfScrollableOrdinates( expectedOriginOfScrollableOrdinates( scrollable ) ) .hasBorderWidth( scrollable.getBorderWidth() ) .hasVisibleArea( expectedVisibleArea( scrollable ) ); } private int computeThresholdHeight() { int trim = shell.getSize().x - shell.getClientArea().height; return tree.getItemHeight() * 2 + trim + 3; } private Point computePreferredTreeSize() { return new SizeComputer( new ScrollableControl<>( tree ), shell ).getPreferredSize(); } private Point expectedOriginOfScrollableOrdinates( Tree scrollable ) { int borderWidth = expectedBorderWidth( scrollable ); int x = expectedVisibleArea( scrollable ).x - borderWidth; int y = expectedVisibleArea( scrollable ).y - borderWidth; return new Point( x, y ); } private Rectangle expectedVisibleArea( Tree scrollable ) { Rectangle area = shell.getClientArea(); int borderAdjustment = expectedBorderWidth( scrollable ) * 2; return new Rectangle( area.x, area.y, area.width + borderAdjustment, area.height + borderAdjustment ); } private int expectedVerticalBarOffset() { int result = tree.getVerticalBar().getSize().x; if( result <= 0 ) { result = OVERLAY_OFFSET; } return result; } private static int expectedBorderWidth( Tree scrollable ) { if( ( scrollable.getStyle() & SWT.BORDER ) > 0 ) { return scrollable.getBorderWidth(); } return 0; } private AdaptionContext<Tree> createContextWithReparentedScrollable() { Composite parent = new Composite( shell, SWT.NONE ); tree.setParent( parent ); shell.setBounds( 100, 100, 1000, 700 ); reparentScrollable( shell, tree ); return new AdaptionContext<Tree>( parent, new ScrollableControl<>( tree ) ); } private static void reparentScrollable( Composite parent, Scrollable scrollable ) { new ControlReflectionUtil().setField( scrollable, "parent", parent ); } private void reinitWithOwnerDrawnAndVirtualScrollable() { tree = createTree( shell, 2, 4, SWT.VIRTUAL ); tree.addListener( SWT.MeasureItem, evt -> {} ); adaptionContext = new AdaptionContext<Tree>( tree.getParent(), new ScrollableControl<>( tree ) ); } public static void waitForGtkRendering() { if( "gtk".equals( SWT.getPlatform() ) ) { long start = System.currentTimeMillis(); while( ( System.currentTimeMillis() - start ) < 500 ) { if( !Display.getCurrent().readAndDispatch() ) {} } } } }