/* WCEScrollPanePeer.java
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package gnu.java.awt.peer.wce;

import java.awt.Adjustable;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Scrollbar;
import java.awt.ScrollPane;
import java.awt.ScrollPaneAdjustable;
import java.awt.AWTEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.PaintEvent;
import java.awt.peer.ScrollPanePeer;
import java.lang.reflect.Field;

public class WCEScrollPanePeer extends WCEContainerPeer implements ScrollPanePeer {
	/**
	 * ScrollPaneAdjustble.maximum
	 */
	private static Field maximumField;
	
	/**
	 * ScrollPaneAdjustable.visibleAmount
	 */
	private static Field visibleAmountField;
	
	static {
		try {
			// ScrollPaneAdjustable.maximum フィールドはパッケージプライベートなので
			// 無理やりアクセスできるようにする
			Class clazz = ScrollPaneAdjustable.class;
			maximumField = clazz.getDeclaredField("maximum");
			maximumField.setAccessible(true);
			
			// "visibleAmount"フィールドについても同じ
			visibleAmountField = clazz.getDeclaredField("visibleAmount");
			visibleAmountField.setAccessible(true);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public WCEScrollPanePeer(ScrollPane scrollPane) {
		super(scrollPane);
	}
	
	protected int createNative(int hParentWnd, int x, int y, int width, int height) {
		ScrollPane scrollPane = (ScrollPane) getComponent();
		return createNativeScrollPane(hParentWnd,
									  x,
									  y,
									  width,
									  height,
									  scrollPane.getScrollbarDisplayPolicy());
	}
		
	private native int createNativeScrollPane(int hParentWnd, int x, int y, int width, int height, int policy);
	
	public int getHScrollbarHeight() {
		return getNativeHScrollbarHeight(getWindowHandle());
	}
	
	/**
	 * ネイティブ水平スクロールバーの高さを返す
	 */
	private native int getNativeHScrollbarHeight(int windowHandle);
	
	public int getVScrollbarWidth() {
		return getNativeVScrollbarWidth(getWindowHandle());
	}
	
	/**
	 * ネイティブ垂直スクロールバーの幅を返す
	 */
	private native int getNativeVScrollbarWidth(int windowHandle);
	
	/**
	 * ネイティブスクロールバーで発生したスクロールイベントを元に、新しい位置を計算し、
	 * Java側のコンポーネントに反映する。
	 * このメソッドは、OS側で WM_VSCROLL/WM_HSCROLL メッセージを受信した際に呼び出される
	 *
	 * @param	scrollbarType	スクロール種別。Scrollbar.HORIZONTAL, Scrollbar.VERTICALのいずれか。
	 * @param	eventType		発生したイベントタイプ。AdjustmentEvent の定数値を指定する。
	 * @param	value			ネイティブスクロールバーの現在値
	 * @param	isAdjusting		値を調整中（スクロールバーをドラッグ中）の場合true
	 * @return					Javaコンポーネントに反映した値。
	 */
	int updatePosition(int scrollbarType,
					   int eventType,
					   int value,
					   boolean isAdjusting) {
		ScrollPane scrollPane = (ScrollPane) getComponent();
		Adjustable adjustable;
		int maximum;
		switch (scrollbarType) {
			case Scrollbar.HORIZONTAL:
				adjustable = scrollPane.getHAdjustable();
				break;
			case Scrollbar.VERTICAL:
				adjustable = scrollPane.getVAdjustable();
				break;
			default:
				throw new IllegalArgumentException("scrollbarType");
		}
		
		// 新しい値を計算する
		int newvalue = value;
		switch (eventType) {
			case AdjustmentEvent.UNIT_INCREMENT:
				newvalue += adjustable.getUnitIncrement();
				break;
				
			case AdjustmentEvent.UNIT_DECREMENT:
				newvalue -= adjustable.getUnitIncrement();
				break;
				
			case AdjustmentEvent.BLOCK_INCREMENT:
				newvalue += adjustable.getBlockIncrement();
				break;
				
			case AdjustmentEvent.BLOCK_DECREMENT:
				newvalue -= adjustable.getBlockIncrement();
				break;
			
			case AdjustmentEvent.TRACK:
				// ネイティブ側の値をそのまま使用する
				break;
				
			default:
				throw new IllegalArgumentException("eventType");
		}
		
		// [最小値 - 最大値]の範囲内に収まるように調整する
		int max = adjustable.getMaximum() - adjustable.getVisibleAmount();
		newvalue = (newvalue > max) ? max : newvalue;
		newvalue = (newvalue < 0) ? 0 : newvalue;
		
		// Adjustableに新しい値を設定する
		ScrollPaneAdjustable spa = (ScrollPaneAdjustable) adjustable;
		if (spa.getValueIsAdjusting() != isAdjusting
				|| spa.getValue() != newvalue) {
			spa.setValue(newvalue);
			spa.setValueIsAdjusting(isAdjusting);
			// イベントを通知する
			notifyAdjustmentEvent((ScrollPaneAdjustable) adjustable,
								  eventType,
								  newvalue,
								  isAdjusting);
		}
		
		// 設定した新しい値を返す
		return newvalue;
	}
	
	
	public void setScrollPosition(int x, int y) {
		ScrollPane sp = (ScrollPane) getComponent();
		Dimension vs = sp.getViewportSize();
		Component child = sp.getComponent(0);

		ScrollPane scrollPane = (ScrollPane) getComponent();

		Adjustable adjustable = scrollPane.getHAdjustable();
		// イベントを通知する
		if (x != adjustable.getValue()) {
			notifyAdjustmentEvent((ScrollPaneAdjustable) adjustable,
								  AdjustmentEvent.TRACK,
								  x,
								  false);
		}
		
		adjustable = scrollPane.getVAdjustable();
		// イベントを通知する
		if (y != adjustable.getValue()) {
			notifyAdjustmentEvent((ScrollPaneAdjustable) adjustable,
								  AdjustmentEvent.TRACK,
								  y,
								  false);
		}
		if (x != adjustable.getValue() || y != adjustable.getValue()) {
			// 現在位置を取得する
			int oldX = -child.getX();
			int oldY = -child.getY();
			
			// ネイティブコンポーネントにスクロールを指示する
			setNativeScrollPosition(getWindowHandle(), oldX, oldY, x, y);
		}
	}
	
	/**
	 * ネイティブコンポーネントをスクロールさせる
	 */
	private native void setNativeScrollPosition(int windowHandle, int oldX, int oldY,
																  int newX, int newY);
	
	/**
	 * 指定されたAdjustableを、指定された値で初期化する
	 *
	 * @param	adjustable	設定対象となるAdjustable
	 * @param	visibleAmount	表示範囲
	 */
	private void setupAdjustable(Adjustable adjustable,
								 int visibleAmount) {
		// 表示領域と BlockIncrement の値は同じ
		adjustable.setBlockIncrement((visibleAmount > 1) ? visibleAmount : 1);
		try {
			visibleAmountField.setInt(adjustable, (visibleAmount > 0) ? visibleAmount : 0);
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
		}
		
		// UnitIncrementの値を調整する
		if (adjustable.getUnitIncrement() > visibleAmount) {
			adjustable.setUnitIncrement((visibleAmount) > 0 ? visibleAmount : 1);
		}
	}
	
	/**
	 * doLayout()実行時に、ScrollPaneから呼び出される
	 */
	public void childResized(int width, int height) {
		ScrollPane scrollPane = (ScrollPane) getComponent();
		
		// Adjustableに値を設定する
		Adjustable h = scrollPane.getHAdjustable();
		Adjustable v = scrollPane.getVAdjustable();
		Dimension viewportSize = scrollPane.getViewportSize();
		
		// Adjustableのパラメータを再設定する
		setupAdjustable(v, (int) viewportSize.getHeight());
		setupAdjustable(h, (int) viewportSize.getWidth());
		
		int vmaximum = height - v.getVisibleAmount();
		int hmaximum = width - h.getVisibleAmount();
		setMaximum(v, vmaximum);
		setMaximum(h, hmaximum);

		// ネイティブスクロールバーのパラメータを設定する
		if (scrollPane.getScrollbarDisplayPolicy()
						!= ScrollPane.SCROLLBARS_NEVER) {

			int windowHandle = getWindowHandle();
			// 水平スクロールバー
			setNativeScrollbarParameters(windowHandle,
										 Scrollbar.HORIZONTAL,
										 hmaximum,
										 h.getVisibleAmount());
			// 垂直スクロールバー
			setNativeScrollbarParameters(windowHandle,
										 Scrollbar.VERTICAL,
										 vmaximum,
										 v.getVisibleAmount());
		}
	}
	
	public void setUnitIncrement(Adjustable item, int inc) {
		// 未実装
	}
	
	public void setValue(Adjustable item, int value) {
		// 未実装
	}
	
	/**
	 * ネイティブスクロールバーの最大値／表示範囲を設定する
	 * このメソッドを呼び出した結果、スクロールバーの表示状態及び
	 * 有効／無効状態が変化する可能性がある。
	 * getInsets()の呼び出し結果もこのメソッド呼び出しの前後で
	 * 変化する可能性がある。
	 *
	 * @param	windowHandle	ネイティブScrollPaneのウインドウハンドル
	 * @param	scrollbarType	スクロールバーのタイプ。Scollbar.VERTICAL, Scrollbar.HORIZONTALのいずれか。
	 * @param	maximum			スクロールバーの最大値。（最小値は０固定）
	 * @param	visibleAmount	表示可能領域
	 */
	private native void setNativeScrollbarParameters(int windowHandle,
													 int scrollbarType,
													 int maximum,
													 int visibleAmount);

	/**
	 * 推奨サイズを返す。
	 */
	public Dimension getPreferredSize() {
		// ScrollPaneコンポーネントのサイズを利用する
		return getComponent().getSize();
	}
	

	/**
	 * Adjustableに登録されたAdjustmentListenerに
	 * イベントを通知する
	 */
	protected void notifyAdjustmentEvent(ScrollPaneAdjustable adjustable,
										 int type,
										 int value,
										 boolean adjusting) {
		AdjustmentListener[] listeners
			= adjustable.getAdjustmentListeners();
		AdjustmentEvent e = null;
		for (int i = 0; i < listeners.length; ++i) {
			if (e == null) {
				// イベントを生成する
				e = new AdjustmentEvent(adjustable,
										AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED,
										type,
										value,
										adjusting);
			}
			listeners[i].adjustmentValueChanged(e);
		}
	}
	
	/**
	 * ScrollPaneAdjustable.maximum フィールドを設定する
	 */
	private void setMaximum(Adjustable adj, int maximum) {
		try {
			if (maximumField != null) {
				maximumField.setInt(adj, maximum);
			}
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
		}
	}
}
