/*******************************************************************************
 * Copyright (c) 2004, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.layout;

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.Control;
import org.eclipse.swt.widgets.Layout;

/**
 * This class wraps a control with a complex computeSize method. It uses caching
 * to reduce the number of times the control's computeSize method is called. This
 * allows controls (such as Coolbars and wrapping text) with slow computeSize
 * operations to be used inside layouts and composites that use inefficient caching.
 * <p>
 * For example, you want to use Coolbar A inside composite B. Rather than making A
 * a direct child of B, place it inside a CacheWrapper and insert the CacheWrapper
 * into B. Any layout data that would normally be attached to the control itself
 * should be attached to the wrapper instead:
 * </p>
 * <code>
 *
 *   // Unoptimized code
 *   Toolbar myToolbar = new Toolbar(someParent, SWT.WRAP);
 *   myToolbar.setLayoutData(someLayoutData);
 * </code>
 * <code>
 *
 *   // Optimized code
 *   CacheWrapper myWrapper = new CacheWrapper(someParent);
 *   Toolbar myToolbar = new Toolbar(myWrapper.getControl(), SWT.WRAP);
 *   myWrapper.getControl().setLayoutData(someLayoutData);
 * </code>
 * <p>
 * CacheWrapper creates a Composite which should have exactly one child: the control
 * whose size should be cached. Note that CacheWrapper does NOT respect the flushCache
 * argument to layout() and computeSize(). This is intentional, since the whole point of
 * this class is to workaround layouts with poor caching, and such layouts will typically
 * be too eager about flushing the caches of their children. However, this means that you
 * MUST manually call flushCache() whenver the child's preferred size changes (and before
 * the parent is layed out).
 * </p>
 *
 * @since 3.0
 */
public class CacheWrapper {
    private Composite proxy;

    private SizeCache cache = new SizeCache();

    private Rectangle lastBounds = new Rectangle(0, 0, 0, 0);

    private class WrapperLayout extends Layout implements ICachingLayout {
        @Override
		protected Point computeSize(Composite composite, int wHint, int hHint,
                boolean flushCache) {
            Control[] children = composite.getChildren();
            if (children.length != 1) {
                return new Point(0, 0);
            }

            cache.setControl(children[0]);

            return cache.computeSize(wHint, hHint);
        }

        @Override
		protected void layout(Composite composite, boolean flushCache) {
            Control[] children = composite.getChildren();
            if (children.length != 1) {
                return;
            }

            Control child = children[0];
            Rectangle newBounds = composite.getClientArea();
            if (!newBounds.equals(lastBounds)) {
                child.setBounds(newBounds);
                lastBounds = newBounds;
            }

        }

        @Override
		public void flush(Control dirtyControl) {
            CacheWrapper.this.flushCache();
        }
    }

    /**
     * Creates a <code>CacheWrapper</code> with the given parent
     *
     * @param parent
     */
    public CacheWrapper(Composite parent) {
        proxy = new Composite(parent, SWT.NONE);

        proxy.setLayout(new WrapperLayout());
    }

    /**
     * Flush the cache. Call this when the child has changed in order to force
     * the size to be recomputed in the next resize event.
     */
    public void flushCache() {
        cache.flush();
    }

    /**
     * Use this as the parent of the real control.
     *
     * @return the proxy contol. It should be given exactly one child.
     */
    public Composite getControl() {
        return proxy;
    }

    /**
     * Dispose of any widgets created by this wrapper.
     */
    public void dispose() {
        if (proxy != null) {
            proxy.dispose();
            proxy = null;
        }
    }
}
