// Copyright 2005, Danger, Inc.  All Rights Reserved.
// This file is subject to the Danger, Inc. Sample Code License,
// which is provided in the file SAMPLE_CODE_LICENSE.
// Copies are also available from http://developer.danger.com/

package com.danger.text;

import danger.app.Event;
import danger.ui.*;
import danger.util.*;
import danger.text.span.*;

/**
 * This window has an EditText into which three other Views
 * are hackily embedded.
 */
public class
Embedded
extends DemoWindow
{
	public
	Embedded()
	{
		EditText et = new EditText();
		String s = getApplication().getString(Resources.ID_EMBEDDED_TEXT);

		et.setSize(getWidth(), getHeight());
		et.setBorderShowsScrolling(true);
		et.setText(s);
		et.show();
		addChild(et);
		setFocusedChild(et);

		int off;
		Button b;
		PopupMenu pm;

		// Add the first button.
		// Note that it is both a child of its EditText and set as a span.

		off = s.indexOf("-1-");
		b = new EmbeddedButton(getApplication().getString(Resources.ID_BUTTON1));
		b.show();
		b.setEvent(this, EVENT1);
		et.setSpan(b, off, off + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
		et.addChild(b);

		// Add the other button.

		off = s.indexOf("-2-");
		b = new EmbeddedButton(getApplication().getString(Resources.ID_BUTTON2));
		b.show();
		b.setEvent(this, EVENT2);
		et.setSpan(b, off, off + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
		et.addChild(b);

		// Add the popup menu.

		off = s.indexOf("-3-");
		pm = new EmbeddedPopupMenu();
		pm.addFromResource(getApplication().getResources(), Resources.ID_POPUP);
		pm.show();
		et.setSpan(pm, off, off + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
		et.addChild(pm);
		pm.resizeToFitLongestItem();

		setTitle(getApplication().getString(Resources.ID_DEMO_EMBEDDED));
	}

	public boolean
	receiveEvent(Event e)
	{
		switch (e.type)
		{
			case EVENT1:
				new AlertWindow(getApplication().getString(Resources.ID_BUTTON1)).show();
				return true;

			case EVENT2:
				new AlertWindow(getApplication().getString(Resources.ID_BUTTON2)).show();
				return true;
		}

		return super.receiveEvent(e);
	}

	/**
	 * Most of the real work of embedding is passed off to this class.
	 * Since popup menus and buttons can't also inherit from this class,
	 * they have to call through to it explicitly.
	 */
	private static class
	EmbedUtils
	{
		/**
		 * Drawing the text covered by the widget draws the widget instead.
		 * The messy part is clearing out the background when it has focus
		 * since it will already have drawn a rectangle behind and we want
		 * the usual rounded corners.
		 */
		public static int
		drawText(Pen p, View v, int x, int top, int baseline, int bottom)
		{
			p.push();

			if (v.isFocused())
			{
				// Ugh.  The highlight has already drawn,
				// so we need to wipe it out...

				p.push();
				p.setColor(v.getParent().getBackgroundColor());
				p.fillRect(x - 1, top, x + v.getWidth() + 1, bottom);
				p.pop();
			}

			p.offset(x, baseline - (p.getFont().getAscent() + v.getHeight()) / 2);
			v.paint(p);
			p.pop();

			return v.getWidth() + 1;
		}

		/**
		 * Center the widget vertically with respect to the middle
		 * of the text line.
		 *
		 * The return value packs the desired line height in the
		 * top 16 bits with the desired descent in the low 16 bits.
		 */
		public static int
		getVerticalDimensions(View v, Font f)
		{
			int ascent = (f.getAscent() + v.getHeight()) / 2;
			int descent = v.getHeight() - ascent;

			return ((ascent + descent) << 16) | descent;
		}

		/**
		 * Clicking fakes a widget-down and a widget-up.
		 *
		 * The stuff at the top is to set the position of the widget
		 * to whatever its actual physical position was at the time of
		 * the click so the pop-up menu will appear at the right place.
		 * There's really no good way to keep this up to date in advance --
		 * we can get notifications when the text changes, but internal
		 * scrolling can basically change without notice.
		 */
		public static boolean
		click(View v)
		{
			boolean handled = false;

			StaticTextBox stb = (StaticTextBox) v.getParent();
			Rect r = new Rect();
			stb.getPointRect(r, stb.getSpanStart(v));
			v.setPosition(r.left, r.top);

			handled = v.eventWidgetDown(Event.DEVICE_WHEEL_BUTTON, new Event(Event.EVENT_WIDGET_DOWN, Event.DEVICE_WHEEL_BUTTON)) || handled;
			handled = v.eventWidgetUp(Event.DEVICE_WHEEL_BUTTON, new Event(Event.EVENT_WIDGET_UP, Event.DEVICE_WHEEL_BUTTON)) || handled;

			return handled;
		}

		/**
		 * If we get a span-added, that should mean we took focus.
		 */
		public static void
		spanAdded(View v, Object span)
		{
			if (span instanceof ClickSpan)
				if (! v.isFocused())
					v.takeFocus(true);
		}

		/**
		 * Span-changed could mean we lost or acquired focus.
		 */
		public static void
		spanChanged(View v, Object span, Spanned text, int start, int end, int s, int e)
		{
			if (span instanceof ClickSpan)
			{
				if (v.isFocused())
				{
					if (s > text.getSpanStart(v) || e < text.getSpanEnd(v))
						v.loseFocus();
				}
				else
				{
					if (s <= text.getSpanStart(v) && e >= text.getSpanEnd(v))
						v.takeFocus(true);
				}
			}
		}

		/**
		 * If we get a span-removed, that should mean we lost focus.
		 */
		public static void
		spanRemoved(View v, Object span)
		{
			if (span instanceof ClickSpan)
				if (v.isFocused())
					v.loseFocus();
		}

		/**
		 * I never seem to be able to adequately explain what this method
		 * is supposed to do.  Basically if the View fits with in the space
		 * available, claim that all its characters fit, and if it's too wide,
		 * claim that none of them fit.
		 */
		public static boolean
		findLineBreakCandidate(View v, LineBreakerArguments args, int wid_avail, int end)
		{
			if (args.getWidthToLast() + v.getWidth() + 1 <= wid_avail)
			{
				args.recordExaminedWidth(end, args.getWidthToLast() + v.getWidth() + 1);
				args.recordWhy(LineBreakerArguments.WHY_OUT_OF_CHARACTERS);
				args.recordVisibleExamined(end);
				return true;
			}
			else
			{
				return false;
			}
		}

		/**
		 * When the width or height changes, the text has to rewrap
		 * to match.
		 */
		public static void
		setSize(View v, int width, int height)
		{
			Container c = v.getParentContainer();

			if (c != null)
			{
				WrappedText wt = (WrappedText) c;

				wt.rewrap(wt.getSpanStart(v), wt.getSpanEnd(v));
			}
		}
	}

	/**
	 * And here meanwhile is the Button subclass that calls all the
	 * utility functions above.
	 */
	private static class
	EmbeddedButton
	extends Button
	implements DrawnSpan, MetricAffectingSpan, AtomicSpan, ClickableSpan, SpanWatcherSpan
	{
		public
		EmbeddedButton(CharSequence title)
		{
			super(title);
		}

		// DrawnSpan

		public int
		drawText(Pen p, CharSequence text, int start, int len, int x, int top, int baseline, int bottom, Object[] spans)
		{
			return EmbedUtils.drawText(p, this, x, top, baseline, bottom);
		}

		public int
		getWidth(Font f, CharSequence text, int start, int len, Object[] spans)
		{
			return getWidth() + 1;
		}

		public int
		getAdvance(Font f, CharSequence text, int start, int len, Object[] spans)
		{
			return getWidth() + 1;
		}

		public int
		getVerticalDimensions(Font f, CharSequence text, int start, int end, Object[] spans)
		{
			return EmbedUtils.getVerticalDimensions(this, f);
		}

		public boolean
		findLineBreakCandidate(Font f, int wid_avail, CharSequence text, int end, LineBreakerArguments args, Object[] spans)
		{
			return EmbedUtils.findLineBreakCandidate(this, args, wid_avail, end);
		}

		// ClickableSpan

		public boolean
		click(Spanned text)
		{
			return EmbedUtils.click(this);
		}

		public Menu
		getContextMenu(Spanned text)
		{
			return null;
		}

		// SpanWatcherSpan

		public void
		spanAdded(Spannable text, Object span, int start, int end, int flags)
		{
			EmbedUtils.spanAdded(this, span);
		}

		public void
		spanChanged(Spannable text, Object span, int start, int end, int flags, int s, int e, int f)
		{
			EmbedUtils.spanChanged(this, span, text, start, end, s, e);
		}

		public void
		spanRemoved(Spannable text, Object span, int start, int end, int flags)
		{
			EmbedUtils.spanRemoved(this, span);
		}

		public void
		setSize(int width, int height)
		{
			super.setSize(width, height);
			EmbedUtils.setSize(this, width, height);
		}
	}

	/**
	 * This is exactly the same as EmbeddedButton above except that
	 * it is a PopupMenu instead.  Yay lack of multiple inheritance!
	 */
	private static class
	EmbeddedPopupMenu
	extends PopupMenu
	implements DrawnSpan, MetricAffectingSpan, AtomicSpan, ClickableSpan, SpanWatcherSpan
	{
		// DrawnSpan

		public int
		drawText(Pen p, CharSequence text, int start, int len, int x, int top, int baseline, int bottom, Object[] spans)
		{
			return EmbedUtils.drawText(p, this, x, top, baseline, bottom);
		}

		public int
		getWidth(Font f, CharSequence text, int start, int len, Object[] spans)
		{
			return getWidth() + 1;
		}

		public int
		getAdvance(Font f, CharSequence text, int start, int len, Object[] spans)
		{
			return getWidth() + 1;
		}

		public int
		getVerticalDimensions(Font f, CharSequence text, int start, int end, Object[] spans)
		{
			return EmbedUtils.getVerticalDimensions(this, f);
		}

		public boolean
		findLineBreakCandidate(Font f, int wid_avail, CharSequence text, int end, LineBreakerArguments args, Object[] spans)
		{
			return EmbedUtils.findLineBreakCandidate(this, args, wid_avail, end);
		}

		// ClickableSpan

		public boolean
		click(Spanned text)
		{
			return EmbedUtils.click(this);
		}

		public Menu
		getContextMenu(Spanned text)
		{
			return null;
		}

		// SpanWatcherSpan

		public void
		spanAdded(Spannable text, Object span, int start, int end, int flags)
		{
			EmbedUtils.spanAdded(this, span);
		}

		public void
		spanChanged(Spannable text, Object span, int start, int end, int flags, int s, int e, int f)
		{
			EmbedUtils.spanChanged(this, span, text, start, end, s, e);
		}

		public void
		spanRemoved(Spannable text, Object span, int start, int end, int flags)
		{
			EmbedUtils.spanRemoved(this, span);
		}

		public void
		setSize(int width, int height)
		{
			super.setSize(width, height);
			EmbedUtils.setSize(this, width, height);
		}
	}

	private static final int EVENT1 = 101;
	private static final int EVENT2 = 102;
}
