/*******************************************************************************
 * 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;
		}
	}
}
